Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring rest service with SseEmitter

I'm trying to notify a simple html page when I call a controller on my server. I have an android application who calls my controller and when this is done I would like to notify my webpage that the controller was called.

Here is some of my code:

    @RequestMapping("/user") 
public class UserController {

    /**
     * Returns user by id.
     * 
     * @param user IMEI
     * @return
     */
    @RequestMapping(value = "/{imei}", method = RequestMethod.GET)
    public User getUser(@PathVariable String imei) {

        User myUser = null;
        try {
            myUser = DbConnector.getUserWithImei(imei);
        } catch (Exception e) {
            System.out.println("Couldn't get user from database");
            e.printStackTrace();
        }
        SseEmitter emitter = new SseEmitter();
        try {
            emitter.send("Hallokes");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        emitter.complete();
        return myUser;
    }
}

All tutorials I see, the controller returns SseEmitter but I have to return a User. Must I make another controller with another mapping and listen on that url? How would I call that controller method within my existing controller? To what URL must my EventSource listen?

Thanks in advance for your help!

Kind regards.

like image 416
Allinone51 Avatar asked Feb 19 '16 11:02

Allinone51


People also ask

What is spring SseEmitter?

SseEmitter classThe SseEmitter can deliver events from the server to the client. Server-Sent-Events are messages from the server to the client. They have a Content-Type header of text/event-stream.

How do SSE events work?

SSE is designed to use the JavaScript EventSource API in order to subscribe to a stream of data in any popular browser. Through this interface a client requests a particular URL in order to receive an event stream. SSE is commonly used to send message updates or continuous data streams to a browser client.

How do you use EventSource?

An EventSource instance opens a persistent connection to an HTTP server, which sends events in text/event-stream format. The connection remains open until closed by calling EventSource. close() . Once the connection is opened, incoming messages from the server are delivered to your code in the form of events.

What is StreamingResponseBody?

StreamingResponseBody. A controller method return value type for asynchronous request processing where the application can write directly to the response OutputStream without holding up the Servlet container thread.


2 Answers

I think you are almost there, Allinone51.

Your call to SseEmitter.send() should probably be in the getUser method. The general pattern is that when you create the SseEmitter you need to "store" it somewhere for other code to get hold of it. You correctly return the SseEmitter from the getSseEmitter method, you just forgot to store it for the other method to be able to call 'send' on it.

Adjusting on your example above, it could be something like this:

//...
private SseEmitter emitter;

@RequestMapping(value = "/{imei}", method = RequestMethod.GET)
public User getUser(@PathVariable String imei) { 
    User myUser = null;

    // .. do resolving of myUser (e.g. database etc).

    // Send message to "connected" web page:
    if (emitter != null) {
        emitter.send(myUser.toString()); // Or format otherwise, e.g. JSON.
    }

    // This return value goes back as a response to your android device
    // i.e. the caller of the getUser rest service.
    return myUser;
}

@RequestMapping(value = "/sse")
public SseEmitter getSseEmitter() {
    emitter = new SseEmitter();
    return emitter;
}

Of course, the code above caters for only one single connection/emitter. There are more intelligent ways of storing that emitter. For example, in my online game application I hook the emitter into each Player object. That way whenever the player object on my server has something to tell to the player device, it has access to the correct emitter inside itself.

like image 99
Leif John Avatar answered Oct 05 '22 11:10

Leif John


I managed to make the SseEmitter work. I'll explain how I did it, maybe it will help someone else.

The first thing I found out is that EventSource("url") actually just 'calls' that url (correct me if I'm wrong). So in the case of a Restcontroller it would just call that controller over and over. Now when you use SseEmitter.send, the onMessage() method of the EventSource get triggered. So what I did is add another controller method with mapping "/sse" and let my eventSource call that method. Now when my original controller method which returns a user is called, I just set a global variable to true, and if that variable is true, I send a message. Code will explain this better:

My method called within my android app follow by the controller method for SSE:

@RequestMapping(value = "/{imei}", method = RequestMethod.GET)
    public User getUser(@PathVariable String imei) { // @PathVariable for
                                                        // passing variables
                                                        // in uri.
                                                        // (<root-url>/service/greeting/myVariable)
        User myUser = null;
        try {
            myUser = DbConnector.getUserWithImei(imei);
        } catch (Exception e) {
            System.out.println("Couldn't get user from database");
            e.printStackTrace();
        }
        if (myUser != null) {
            if (myUser.getAccess() == 1) {
                calledImei = true;
            }
        }
        return myUser;
    }

    @RequestMapping(value = "/sse")
    public SseEmitter getSseEmitter() {
        SseEmitter sseEmitter = new SseEmitter();
        if(calledImei) {
            try {
                sseEmitter.send("Message 1");
                calledImei = false;
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        sseEmitter.complete();
        return sseEmitter;
    }
}

And now the HTML page with the EventSource:

<body>
    <script>
            var source = new EventSource("rest/user/sse");
            source.onmessage = function(event) {
                 //Do what you need to do when message received.
            }
    </script>
</body>

Hope this will help some other people.

like image 33
Allinone51 Avatar answered Oct 05 '22 11:10

Allinone51