Supporting server-sent events in RESTful web services

The Server-sent event (SSE) is a mechanism where a client (browser) receives automatic updates from a server via a long-living HTTP connection that was established when the client contacted the server for the first time. The SSE client subscribes to a stream of updates generated by a server, and whenever a new event occurs, a notification is sent to the client via the existing HTTP connection. The connection is then left open until the server has some data to send. In this section, we will discuss how the SSE technology can be used in a REST API to send continuous updates to the client.

Server-sent events use a single, unidirectional, persistent connection between the client and the server. This is suited for the one-way publish-subscribe model, whenever the server pushes updates to the clients.

In a typical HTTP client-server communication model, the client opens the connection and sends a request to the server. The server then responds back with the result and closes the connection. With SSE, the server leaves the connection open even after sending the first response back to the client. This connection will be left open as long as the server wants and can be used for sending notifications to all the connected clients.

Let's see how we can implement SSE in RESTful web services by using Jersey APIs. The following are the core Jersey classes that you may want to use while building SSE-enabled RESTful resources:

  • org.glassfish.jersey.media.sse.SseBroadcaster: This is a utility class, used for broadcasting SSE to multiple subscribers. Each subscriber is represented by an EventOutput instance, which is explained next.
  • org.glassfish.jersey.media.sse.EventOutput: One instance of this class corresponds with exactly one HTTP connection. When a client subscribes to SSEs by calling the designated REST API method, the server (Jersey application) generates EventOutput and returns it to the caller. When EventOutput is returned from the resource method, the underlying HTTP connection will be left open for later use by the server. The server can use this HTTP connection to broadcast messages whenever needed.

Let's build a simple example to learn how to enable SSE support in RESTful web services.

The first step is to add the required dependencies for SSE (the jersey-media-sse JAR) to the project. If you use Maven for building your source, then the dependency to jersey-media-sse may look like the following:

<dependency> 
  <groupId>org.glassfish.jersey.media</groupId> 
  <artifactId>jersey-media-sse</artifactId> 
    
   <!-specify right version --> 
  <version>RELEASE</version>  
  <type>jar</type> 
 
   <!-- container(GlassFish) provides dependency  
         for this example --> 
   <scope>provided</scope> 
</dependency> 

The Jersey runtime will automatically discover and register the Jersey SSE module if it is found in the application class path. You do not need to do anything explicitly in order to enable this feature.

In the following example, we will use an SSE event mechanism to notify the subscribed clients whenever the department resource gets modified on the server.

Let's take a closer look at the implementation to understand how SSE is leveraged in this example via the Jersey APIs:

Let's understand the image in detail:

  1. The client subscribes to SSEs by calling the manageSSEEvents(...) RESTful web service method, which is accessible via the following URI: GET departments/events HTTP/1.1.
  2. When clients subscribe to events, we add them to the SseBroadcaster instance.
  3. Later, when the event needs to be broadcast, we ask SseBroadcaster to broadcast the events to all the subscribed parties.
  4. This example uses a singleton resource class. The reason why we use a singleton resource class is primarily to make sure that API calls, which need access to SseBroadcaster, use the same SseBroadcaster instance. You can improve this example by adding proper thread synchronization blocks.
  5. Later, when a client updates a department resource, this example will create an OutboundEvent instance and broadcast it to all the listeners via the SseBroadcaster instance discussed in Step 1. The OutboundEvent object holds the relevant message about the modifications made on the department.
  6. Let's see how the client deals with the SSE client. An SSE client keeps track of the most recently received event identifier. If the connection fails, the client will attempt to reconnect and send a special Last-Event-ID HTTP header containing the event ID. This header can be used as a synchronization mechanism between the client and the server in the event of a temporary connection failure. The following method manageSSEEvents accepts the Last-Event-ID  sent by the client and replays all the missed events:     public EventOutput manageSSEEvents( @HeaderParam(SseFeature.LAST_EVENT_ID_HEADER) @DefaultValue("-1") int lastEventId).
  7. When the client closes a connection and does not respond within the reconnect delay window, then the broadcaster identifies the corresponding EventOutput and releases it along with all the resources associated with it.
  8. Alternatively, the server can close all the open connections by invoking the closeAll() method on the SseBroadcaster object.

The following is the code snippet used for building an SSE-enabled RESTful web API discussed in this example:

//Other imports are omitted for brevity 
import org.glassfish.jersey.media.sse.EventOutput; 
import org.glassfish.jersey.media.sse.OutboundEvent; 
import org.glassfish.jersey.media.sse.SseBroadcaster; 
import org.glassfish.jersey.media.sse.SseFeature;  
 
@Path("departments/events") 
@Singleton 
public class SSEEnabledDeptResource { 
 
    @PersistenceContext(unitName = "SSE_PU") 
    private EntityManager entityManager; 
 
    private static ArrayList<String> modifiedDepts = new  
        ArrayList<String>(); 
    private static final SseBroadcaster broadcaster = new  
        SseBroadcaster(); 
    
    // Client subscribes to SSEs by calling  
    // this RESTful web service method. 
    @GET 
    @Produces(SseFeature.SERVER_SENT_EVENTS) 
    public EventOutput manageSSEEvents( 
        @HeaderParam(SseFeature.LAST_EVENT_ID_HEADER)  
        @DefaultValue("-1") int lastEventId) { 
 
        EventOutput eventOutput = new EventOutput(); 
        if (lastEventId > 0) { 
            replayMissedUpdates(lastEventId, eventOutput); 
        } 
        if (!broadcaster.add(eventOutput)) { 
             // Let's try to force a 5-s delayed client  
             // reconnect attempt 
            throw new ServiceUnavailableException(5L); 
        } 
        return eventOutput; 
    } 

  private void replayMissedUpdates(final int lastEventId,  
       final EventOutput eventOutput) { 
    try { 
      for (int i = lastEventId;  
                     i < modifiedDepts.size(); i++) { 
        eventOutput.write(createItemEvent(i,  
                           modifiedDepts.get(i))); 
      } 
    } catch (IOException ex) { 
      throw new InternalServerErrorException 
                     ("Error replaying missed events", ex); 
    } 
  } 
 
   //This method generates an SSE whenever any client 
    //invokes it to modify the department resource  
    //identified by path parameter 
    @PUT 
    @Path("{id}") 
    @Consumes(MediaType.APPLICATION_JSON) 
    public void edit(@PathParam("id") Short id,  
        Department entity) { 
        entityManager.merge(entity); 
        final int modifiedListIndex = modifiedDepts.size() + 1; 
        broadcaster.broadcast(createItemEvent 
            (modifiedListIndex, entity.getDepartmentName())); 
        modifiedDepts.add(entity.getDepartmentName()); 
    } 
     
    private OutboundEvent createItemEvent(final int eventId, 
        final String name) { 
         return  
             new OutboundEvent.Builder() 
            .id(String.valueOf(eventId)) 
            .data(String.class, name).build(); 
    } 
} 

Remember that an SSE is unidirectional by nature and is appropriate for a publish-subscribe communication model. Here are some advantages of using SSEs:

  • With an SEE, a message is transported over the popular HTTP instead of a custom protocol. This keeps both the server and the client simple and easy to work on.
  • HTTP has a built-in support for reconnection and event-id used in an SSE.
  • SSE is a simple and lightweight protocol.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.247.68