Skip to content

Commit c650755

Browse files
authored
Support for disconnecting clients. Resolves #39
2 parents d75fe1d + c7077be commit c650755

26 files changed

+821
-114
lines changed

Benchmark.AspNetCore.ServerSentEvents/Benchmarks/ServerSentEventsServiceBenchmarks.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ public class ServerSentEventsServiceBenchmarks
3333
#region Constructor
3434
public ServerSentEventsServiceBenchmarks()
3535
{
36-
_serverSentEventsClient = new ServerSentEventsClient(Guid.NewGuid(), new ClaimsPrincipal(), new NoOpHttpResponse());
36+
_serverSentEventsClient = new ServerSentEventsClient(Guid.NewGuid(), new ClaimsPrincipal(), new NoOpHttpResponse(), false);
3737

3838
_serverSentEventsService = new ServerSentEventsService(Options.Create<ServerSentEventsServiceOptions<ServerSentEventsService>>(null));
3939
for (int i = 0; i < MULTIPLE_CLIENTS_COUNT; i++)
4040
{
41-
_serverSentEventsService.AddClient(new ServerSentEventsClient(Guid.NewGuid(), new ClaimsPrincipal(), new NoOpHttpResponse()));
41+
_serverSentEventsService.AddClient(new ServerSentEventsClient(Guid.NewGuid(), new ClaimsPrincipal(), new NoOpHttpResponse(), false));
4242
}
4343
}
4444
#endregion

DocFx.AspNetCore.ServerSentEvents/DocFx.AspNetCore.ServerSentEvents.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk.Web">
22
<PropertyGroup>
3-
<TargetFramework>netcoreapp3.1</TargetFramework>
3+
<TargetFramework>net5.0</TargetFramework>
44
</PropertyGroup>
55
<ItemGroup>
66
<None Remove="log.txt" />
@@ -9,7 +9,7 @@
99
<Folder Include="wwwroot\" />
1010
</ItemGroup>
1111
<ItemGroup>
12-
<PackageReference Include="docfx.console" Version="2.48.0">
12+
<PackageReference Include="docfx.console" Version="2.58.8">
1313
<PrivateAssets>all</PrivateAssets>
1414
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
1515
</PackageReference>
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Disconneting Clients
2+
3+
There is an option to disconnect a client from server. The flow is based on responding with *204 No Content* status code to reconnect attempt.
4+
5+
<center>![Server-Sent Events Client Disconnect Flow Diagram](../resources/disconneting-client-flow.png)</center>
6+
7+
## Prerequisites
8+
9+
The client disconnect functionality requires registering two services, which are not registered by default.
10+
11+
### Client Identifier Provider
12+
13+
First is implementation of [`IServerSentEventsClientIdProvider`](../api/Lib.AspNetCore.ServerSentEvents.IServerSentEventsClientIdProvider.html), which responsibility is to provide an identifier for a client. This identifier should remain the same if client performs reconnect(s). There is no ready to use implementation of this provider, as the approach to this should strongly depend on context. Below is a sample cookie based implementation (which is certainly not sophisticated enough for production scenarios).
14+
15+
```cs
16+
internal class CookieBasedServerSentEventsClientIdProvider : IServerSentEventsClientIdProvider
17+
{
18+
private const string COOKIE_NAME = ".ServerSentEvents.Guid";
19+
20+
public Guid AcquireClientId(HttpContext context)
21+
{
22+
Guid clientId;
23+
24+
string cookieValue = context.Request.Cookies[COOKIE_NAME];
25+
if (String.IsNullOrWhiteSpace(cookieValue) || !Guid.TryParse(cookieValue, out clientId))
26+
{
27+
clientId = Guid.NewGuid();
28+
29+
context.Response.Cookies.Append(COOKIE_NAME, clientId.ToString());
30+
}
31+
32+
return clientId;
33+
}
34+
35+
public void ReleaseClientId(Guid clientId, HttpContext context)
36+
{
37+
context.Response.Cookies.Delete(COOKIE_NAME);
38+
}
39+
}
40+
```
41+
42+
There is a helper method which simplifies registering an implementation.
43+
44+
```cs
45+
public class Startup
46+
{
47+
...
48+
49+
public void ConfigureServices(IServiceCollection services)
50+
{
51+
services.AddServerSentEvents();
52+
53+
// Register cookie based clients identifier provider for Server Sent Events
54+
services.AddServerSentEventsClientIdProvider<CookieBasedServerSentEventsClientIdProvider>();
55+
56+
...
57+
}
58+
59+
...
60+
}
61+
```
62+
63+
### "No Reconnect" Identifiers Store
64+
65+
Second is implementation of [`IServerSentEventsNoReconnectClientsIdsStore`](../api/Lib.AspNetCore.ServerSentEvents.IServerSentEventsNoReconnectClientsIdsStore.html), which responsibility is to store identifiers which should not be allowed to reconnect. There are two ready to use implementations. First stores the identifiers in memory, while the second is backed by distributed cache. Both have ready to use methods to register them.
66+
67+
```cs
68+
public class Startup
69+
{
70+
...
71+
72+
public void ConfigureServices(IServiceCollection services)
73+
{
74+
services.AddServerSentEvents();
75+
76+
// Register cookie based clients identifier provider for Server Sent Events
77+
services.AddServerSentEventsClientIdProvider<CookieBasedServerSentEventsClientIdProvider>();
78+
79+
// Register IServerSentEventsNoReconnectClientsIdsStore backed by memory store.
80+
services.AddInMemoryServerSentEventsNoReconnectClientsIdsStore();
81+
82+
...
83+
}
84+
85+
...
86+
}
87+
```
88+
89+
## Disconnecting a Client
90+
91+
Disconnecting a client is as simple as calling `Disconnect()` on a [`IServerSentEventsClient`](../api/Lib.AspNetCore.ServerSentEvents.IServerSentEventsClient.html).

DocFx.AspNetCore.ServerSentEvents/docfx.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"articles/getting-started.md",
2727
"articles/authorization.md",
2828
"articles/groups.md",
29+
"articles/disconneting-clients.md",
2930
"articles/advanced.md"
3031
]
3132
}
@@ -34,7 +35,8 @@
3435
{
3536
"files": [
3637
"resources/svg/logo.svg",
37-
"resources/ico/favicon.ico"
38+
"resources/ico/favicon.ico",
39+
"resources/disconneting-client-flow.png"
3840
]
3941
}
4042
],

DocFx.AspNetCore.ServerSentEvents/index.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# Lib.AspNetCore.ServerSentEvents
2+
[![NuGet Version](https://img.shields.io/nuget/v/Lib.AspNetCore.ServerSentEvents?label=Lib.AspNetCore.ServerSentEvents&logo=nuget)](https://www.nuget.org/packages/Lib.AspNetCore.ServerSentEvents)
3+
[![NuGet Downloads](https://img.shields.io/nuget/dt/Lib.AspNetCore.ServerSentEvents?label=⭳)](https://www.nuget.org/packages/Lib.AspNetCore.ServerSentEvents)
24

35
Lib.AspNetCore.ServerSentEvents is a library which provides Server-Sent Events (SSE) support for ASP.NET Core.
46

@@ -24,8 +26,8 @@ There are some blog posts available which describe implementation details and so
2426
- [Server-Sent Events (or WebSockets) broadcasting in load balancing scenario with Redis](https://www.tpeczek.com/2017/09/server-sent-events-or-websockets.html)
2527
- [Server-Sent Events and ASP.NET Core - You may need keep alives](https://www.tpeczek.com/2018/08/server-sent-events-and-aspnet-core-you_9.html)
2628

27-
## Donating
29+
## Sponsor this project
2830

29-
My blog and open source projects are result of my passion for software development, but they require a fair amount of my personal time. If you got value from any of the content I create, then I would appreciate your support by [buying me a coffee](https://www.buymeacoffee.com/tpeczek).
31+
My blog and open source projects are result of my passion for software development, but they require a fair amount of my personal time. If you got value from any of the content I create, then I would appreciate your support by sponsoring me.
3032

31-
<a href="https://www.buymeacoffee.com/tpeczek"><img src="https://www.buymeacoffee.com/assets/img/custom_images/black_img.png" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" target="_blank"></a>
33+
<iframe src="https://github.com/sponsors/tpeczek/button" title="Sponsor tpeczek" height="35" width="116" style="border: 0;"></iframe>
16.1 KB
Loading

DocFx.AspNetCore.ServerSentEvents/toc.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
# [Groups](articles/groups.md)
88

9+
# [Disconneting Clients](articles/disconneting-clients.md)
10+
911
# [Advanced](articles/advanced.md)
1012

1113
# [API Reference](api/Lib.AspNetCore.ServerSentEvents.html)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Threading.Tasks;
3+
using Microsoft.Extensions.Caching.Distributed;
4+
5+
namespace Lib.AspNetCore.ServerSentEvents
6+
{
7+
internal class DistributedServerSentEventsNoReconnectClientsIdsStore : IServerSentEventsNoReconnectClientsIdsStore
8+
{
9+
private readonly IDistributedCache _cache;
10+
private readonly byte[] _dummyItem = new byte[0];
11+
12+
public DistributedServerSentEventsNoReconnectClientsIdsStore(IDistributedCache cache)
13+
{
14+
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
15+
}
16+
17+
public async Task AddClientIdAsync(Guid clientId)
18+
{
19+
await _cache.SetAsync(clientId.ToString(), _dummyItem);
20+
}
21+
22+
public async Task<bool> ContainsClientIdAsync(Guid clientId)
23+
{
24+
return (await _cache.GetAsync(clientId.ToString())) is null;
25+
}
26+
27+
public async Task RemoveClientIdAsync(Guid clientId)
28+
{
29+
await _cache.RemoveAsync(clientId.ToString());
30+
}
31+
}
32+
}

Lib.AspNetCore.ServerSentEvents/IServerSentEventsClient.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ public interface IServerSentEventsClient
2828
#endregion
2929

3030
#region Methods
31+
/// <summary>
32+
/// Disconnects client.
33+
/// </summary>
34+
/// <remarks>
35+
/// This requires registering implementations of <see cref="IServerSentEventsClientIdProvider"/> and <see cref="IServerSentEventsNoReconnectClientsIdsStore"/>.
36+
/// </remarks>
37+
void Disconnect();
38+
3139
/// <summary>
3240
/// Sends event to client.
3341
/// </summary>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using Microsoft.AspNetCore.Http;
3+
4+
namespace Lib.AspNetCore.ServerSentEvents
5+
{
6+
/// <summary>
7+
/// Contract for provider of identifiers for <see cref="IServerSentEventsClient"/> instances based on <see cref="HttpContext"/>.
8+
/// </summary>
9+
public interface IServerSentEventsClientIdProvider
10+
{
11+
/// <summary>
12+
/// Acquires an identifier which will be used by <see cref="IServerSentEventsClient"/>.
13+
/// </summary>
14+
/// <param name="context">The context.</param>
15+
/// <returns>The identifier.</returns>
16+
Guid AcquireClientId(HttpContext context);
17+
18+
/// <summary>
19+
/// Releases an identifier so it no longer represents any active <see cref="IServerSentEventsClient"/>.
20+
/// </summary>
21+
/// <param name="clientId">The identifier.</param>
22+
/// <param name="context">The context.</param>
23+
void ReleaseClientId(Guid clientId, HttpContext context);
24+
}
25+
}

0 commit comments

Comments
 (0)