Lib.AspNetCore.ServerSentEvents

repository·main·Indexed 43 days ago

https://github.com/tpeczek/lib.aspnetcore.serversentevents

A library providing specialized support for implementing Server-Sent Events (SSE) within ASP.NET Core applications. It includes features for mapping SSE endpoints, broadcasting messages to all or specific clients, handling auto-reconnects via LastEventId, and managing client disconnections using IServerSentEventsClientIdProvider and IServerSentEventsNoReconnectClientsIdsStore. It also supports ASP.NET Core policy-based authorization and custom HTTP response header configuration via ServerSentEventsOptions.

Tokens
5.1K
Snippets
16
Records
20
Agent score
55%

What's inside Lib.AspNetCore.ServerSentEvents

  1. Handle SSE auto-reconnect and missed messages

    main

    SSE supports automatic reconnection and tracking the last seen message ID. When a client reconnects, they can provide the ID of the last message they received so the server can retransmit missed data.

    Approach 1: Using the ClientConnected event

    Subscribe to IServerSentEventsService.ClientConnected. The event arguments (ServerSentEventsClientConnectedArgs) provide:

    • LastEventId: The identifier of the last message the client received.
    • Client: The IServerSentEventsClient instance to which you can send the missed messages.

    Approach 2: Overriding OnReconnectAsync

    When implementing a custom ServerSentEventsService, you can override OnReconnectAsync(IServerSentEventsClient client, string lastEventId). Note: You must call base.OnReconnectAsync(client, lastEventId) within your override to ensure the ClientConnected event is still triggered.

  2. Implement and register a "No Reconnect" Identifiers Store

    main

    The disconnection flow works by responding with a 204 No Content status code to a reconnection attempt. To make this work, you must implement IServerSentEventsNoReconnectClientsIdsStore to track which identifiers are forbidden from reconnecting.

    There are two built-in implementations available:

    1. In-Memory Store: Use AddInMemoryServerSentEventsNoReconnectClientsIdsStore() for local, single-instance scenarios.
    2. Distributed Cache Store: Use the distributed cache-backed implementation (method name not explicitly shown in snippet, but available via documentation) for multi-instance/scaled environments.

    Both require IServerSentEventsClientIdProvider to be registered first.

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddServerSentEvents();
    
        // 1. Register the identifier provider
        services.AddServerSentEventsClientIdProvider<CookieBasedServerSentEventsClientIdProvider>();
    
        // 2. Register the store (In-Memory example)
        services.AddInMemoryServerSentEventsNoReconnectClientsIdsStore();
    }
  3. Install and configure Lib.AspNetCore.ServerSentEvents

    main

    To add Server-Sent Events (SSE) support to an ASP.NET Core application, you must register the required services and map the middleware.

    Basic Setup

    Use AddServerSentEvents() in ConfigureServices to register the default service and MapServerSentEvents(path) in Configure to map it to an endpoint.

    Creating Distinguishable Endpoints

    By default, all endpoints mapped via MapServerSentEvents share the same service instance, making them indistinguishable. To create unique endpoints with unique services (e.g., for different notification types), you must register a custom implementation of ServerSentEventsService and map it using the generic overload.

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            // Register default service
            services.AddServerSentEvents();
    
            // Register a custom service for a specific endpoint
            services.AddServerSentEvents<INotificationsServerSentEventsService, NotificationsServerSentEventsService>();
        }
    
        public void Configure(IApplicationBuilder app)
        {
            // Map the default endpoint
            app.MapServerSentEvents("/default-sse-endpoint");
    
            // Map a specific endpoint to the custom service
            app.MapServerSentEvents<NotificationsServerSentEventsService>("/notifications-sse-endpoint");
        }
    }
    
    // Custom service implementation example
    internal interface INotificationsServerSentEventsService : IServerSentEventsService { }
    
    internal class NotificationsServerSentEventsService : ServerSentEventsService, INotificationsServerSentEventsService
    {
        public NotificationsServerSentEventsService(IOptions<ServerSentEventsServiceOptions<NotificationsServerSentEventsService>> options)
            : base(options.ToBaseServerSentEventsServiceOptions())
        { }
    }
  4. Configure Nginx proxy for Server-Sent Events

    main

    When running behind an Nginx proxy, you must ensure that Nginx does not buffer the SSE stream, otherwise, events will not be delivered to the client in real-time. To achieve this, use the OnPrepareAccept callback in ServerSentEventsOptions to append the X-Accel-Buffering: no header to the response.

    app.MapServerSentEvents("/default-sse-endpoint", new ServerSentEventsOptions
    {
        OnPrepareAccept = response =>
        {
            response.Headers.Append("Cache-Control", "no-cache");
            response.Headers.Append("X-Accel-Buffering", "no");
        }
    });
  5. Configure HTTP response headers via ServerSentEventsOptions.OnPrepareAccept

    main

    You can customize the HTTP response headers sent during the SSE connection handshake by using the OnPrepareAccept delegate within the ServerSentEventsOptions object. This is useful for setting specific headers required by clients or intermediate proxies.

    app.MapServerSentEvents("/default-sse-endpoint", new ServerSentEventsOptions
    {
        OnPrepareAccept = response =>
        {
            response.Headers.Append("Cache-Control", "no-cache");
            response.Headers.Append("X-Accel-Buffering", "no");
        }
    });
  6. Apply simple authorization to SSE endpoints

    main

    You can control access to a Server-Sent Events endpoint using the Authorization property within ServerSentEventsOptions. Setting Authorization to ServerSentEventsAuthorization.Default restricts access to any authenticated user.

    app.MapServerSentEvents("/default-sse-endpoint", new ServerSentEventsOptions
    {
        Authorization = ServerSentEventsAuthorization.Default
    });
  7. Implement and register a Client Identifier Provider

    main

    To enable client disconnection, you must provide a way to identify clients consistently across reconnect attempts. You do this by implementing IServerSentEventsClientIdProvider.

    This provider must implement two methods:

    1. AcquireClientId(HttpContext context): Returns a unique identifier (e.g., a Guid) for the client. This ID must persist if the client reconnects.
    2. ReleaseClientId(Guid clientId, HttpContext context): Cleans up the identifier when appropriate.

    Use the AddServerSentEventsClientIdProvider<T> extension method to register your implementation in IServiceCollection.

    internal class CookieBasedServerSentEventsClientIdProvider : IServerSentEventsClientIdProvider
    {
        private const string COOKIE_NAME = ".ServerSentEvents.Guid";
    
        public Guid AcquireClientId(HttpContext context)
        {
            Guid clientId;
    
            string cookieValue = context.Request.Cookies[COOKIE_NAME];
            if (String.IsNullOrWhiteSpace(cookieValue) || !Guid.TryParse(cookieValue, out clientId))
            {
                clientId = Guid.NewGuid();
                context.Response.Cookies.Append(COOKIE_NAME, clientId.ToString());
            }
    
            return clientId;
        }
    
        public void ReleaseClientId(Guid clientId, HttpContext context)
        {
            context.Response.Cookies.Delete(COOKIE_NAME);
        }
    }
    
    // Registration in Startup.cs
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddServerSentEvents();
        services.AddServerSentEventsClientIdProvider<CookieBasedServerSentEventsClientIdProvider>();
    }
  8. Restrict SSE endpoint access by authorization policies and claims

    main

    For fine-grained control, such as checking for specific claims, you must first register a named policy using the standard ASP.NET Core AddAuthorization method. Once the policy is registered, apply it to the SSE endpoint by setting the Policy property on ServerSentEventsAuthorization to the name of your policy.

    // 1. Register the policy in ConfigureServices
    services.AddAuthorization(options =>
    {
        options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
    });
    
    // ...
    
    // 2. Apply the policy in Configure
    app.MapServerSentEvents("/default-sse-endpoint", new ServerSentEventsOptions
    {
        Authorization = new ServerSentEventsAuthorization { Policy = "EmployeeOnly" }
    });