Use Server-Sent Event in Spring 4.2

Today, the Spring Framework released to 4.2 RC2. In Spring 4.2, better application events and Server-Sent Event(SSE) are supported.

In this article, I'll introduce you the two new features.

What's Server Sent Event

Server-sent events (SSE) is a technology where a browser receives automatic updates from a server via HTTP connection. The Server-Sent Events EventSource API is standardized as part of HTML5 by the W3C.

Server-sent events is a standard describing how servers can initiate data transmission towards clients once an initial client connection has been established. They are commonly used to send message updates or continuous data streams to a browser client and designed to enhance native, cross-browser streaming through a JavaScript API called EventSource, through which a client requests a particular URL in order to receive an event stream.

Web browser support for Server-Sent Events

Browser Supported Notes
Internet Explorer No
Mozilla Firefox Yes Starting with Firefox 6.0
Google Chrome Yes Starting with Chrome 6
Opera Yes Starting with Opera 11
Safari Yes Starting with Safari 5.0

Setup Maven

Since the Spring 4.2 hasn't released to the Maven central repository, we need to setup our own repository.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <properties>
        <spring.version>4.2.0.RC2</spring.version>
    </properties>
    <repositories>
        <repository>
            <id>Springframework Milestone</id>
            <url>http://repo.spring.io/milestone</url>
        </repository>
    </repositories>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        ...
    </dependencies>
    ...
</project>

Use Server Sent Event in Spring Controller

First of all, let me introduce you the basic usage of Server Sent Event.
The Spring provide us a SseEmitter class with can return a HTTP response whose content type is text/event-stream.

Before we start, we need to add sync-support for Spring. To do this, you can edit the web.xml as following.

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
    <async-supported>true</async-supported>
</servlet>

Then, we can add a method in the Spring Controller.

@RequestMapping("/getRealTimeMessage.action")
public SseEmitter getRealTimeMessageAction(
        HttpServletRequest request) {
    SseEmitter sseEmitter = new SseEmitter();
    // You can send message here
    sseEmitter.send("Message #1");
    return sseEmitter;
}

// Do something with sseEmitter
// You can also send message in another method
sseEmitter.send("Message #2");

// Complete Send Message
sseEmitter.complete();

Use Server-Sent Event with Message Queue

In a project, I used ActiveMQ to communicate with another process. When I receive a message from message queue, onMessage() method will be triggered. I want to push the message to the browser using Server-Sent Event. In this section, I'll tell you how to do it.

First of all, let's take a quick glance at onMessage() method.

@Component
public class MessageReceiver implements MessageListener {
    /* (non-Javadoc)
     * @see javax.jms.MessageListener#onMessage(javax.jms.Message)
     */
    public void onMessage(Message message) {
        if ( message instanceof MapMessage ) {
            final MapMessage mapMessage = (MapMessage) message;

            try {
                String event = mapMessage.getString("event");
                Long submissionId = mapMessage.getLong("submissionId");

                eventPublisher.publish(new SubmissionEvent(this, submissionId, "Message"))
            } catch (JMSException ex) {
                LOGGER.catching(ex);
            }
        }
    }

    /**
     * New feature in Spring 4.2.
     */
    @Autowired
    private ApplicationEventPublisher eventPublisher;
}

Here's the definition of SubmissionEvent.

public class SubmissionEvent extends ApplicationEvent {
    public SubmissionEvent(Object source, long submissionId, String message) {
        super(source);
        this.submissionId = submissionId;
        this.message = message;
    }

    // getters and setters

    private final long submissionId;

    private final String message;
}

As you see, I used ApplicationEventPublisher in the class to publish a event. This event will be catched in a listener.

@Component
public class ApplicationEventListener {
    @EventListener
    public void submissionEventHandler(SubmissionEvent event) throws IOException {
        long submissionId = event.getSubmissionId();
        String message = event.getMessage();
        SseEmitter sseEmitter = sseEmitters.get(submissionId);

        if ( sseEmitter == null ) {
            LOGGER.warn(String.format("CANNOT get the SseEmitter for submission #%d.", submissionId));
            return;
        }
        sseEmitter.send(message);
    }

    public void addSseEmitters(long submissionId, SseEmitter sseEmitter) {
        sseEmitters.put(submissionId, sseEmitter);
    }

    /**
     * The list of the objects of SseEmitter.
     * The key of the map stands for submissionId.
     * The value of the map is the corresponding SseEmitter object.
     */
    private static Map<Long, SseEmitter> sseEmitters = new Hashtable<Long, SseEmitter>();
}

At last, register the SseEmitter to ApplicationEventListener in controller.

@RequestMapping("/getRealTimeMessage.action")
public SseEmitter getRealTimeMessageAction(
        @RequestParam(value = "submissionId", required = true) long submissionId,
        HttpServletRequest request) throws IOException {
    Submission submission = submissionService.getSubmission(submissionId);

    if ( submission == null ) {
        throw new ResourceNotFoundException();
    }
    SseEmitter sseEmitter = new SseEmitter();
    applicationEventListener.addSseEmitters(submissionId, sseEmitter);
    return sseEmitter;
}

Try it out!

Sever-Sent-Event

Reference

  • http://stackoverflow.com/questions/31229015/sse-implementation-in-spring-rest
  • https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2
  • http://docs.spring.io/spring/docs/4.2.0.RC2/spring-framework-reference/htmlsingle/#mvc-ann-async-http-streaming
  • https://en.wikipedia.org/wiki/Server-sent_events
Contact Us
  • Room 614, Zonghe Building, Harbin Institute of Technology
  • cshzxie [at] gmail.com