简体   繁体   中英

consume Spring SSE with Xamarin

I have a spring boot backend for which I want to implement an SSE Endpoint. This endpoint I want to consume using an App based on Xamarin Forms.

I managed to implement some examples for both sides, however, I did not manage to receive any message on the App.

For the backend part I implemented the following example:

    @RequestMapping(value = "/event-stream", method = RequestMethod.GET)
    public ResponseEntity<ResponseBodyEmitter> streamEvents() {
        ResponseBodyEmitter emitter = new ResponseBodyEmitter();
        executor.execute(() -> {
            try {
                for (int i = 0; i < 30; i++) {
                    Thread.sleep(2000);
                    var msg = new ResponseTestObject("this is a message from " + new Date(), i);
                    emitter.send(msg, MediaType.APPLICATION_JSON);
                }
                emitter.complete();
            } catch (Exception ex) {
                emitter.completeWithError(ex);
            }
        });
        return new ResponseEntity<>(emitter, HttpStatus.OK);
    }
    @AllArgsConstructor
    public static class ResponseTestObject {
        public String message;
        public int id;
    }

NOTE: I intentionally implemented it in a way that the default 30s timeout would be reached. calling this method with postman it would load for the said 30 seconds and show all sent messages at the same time:

{
    "message": "this is a message from Thu May 05 11:36:25 CEST 2022",
    "id": 0
}{
    "message": "this is a message from Thu May 05 11:36:27 CEST 2022",
    "id": 1
}
[...]
{
    "message": "this is a message from Thu May 05 11:36:51 CEST 2022",
    "id": 13
}{
    "message": "this is a message from Thu May 05 11:36:53 CEST 2022",
    "id": 14
}

on my app part I used ServiceStack ServerEventsClient:

EventClient = new ServerEventsClient(BackendConnector.BACKEND_HOST + "/events/") {
    OnMessage = OnMessage,
    OnException = (ex) => {
        Console.WriteLine("OnException: " + ex.Message);
    }
};
// another REST backend connection is made previously in the app and I use its session cookie for authentication
EventClient.ServiceClient.SetCookie(BackendConnector.SESSION_COOKIE_VALUE, BackendConnector.BackendSessionCookieId);
EventClient.Start();

After I start the client I let it periodically post the status like that:

Console.WriteLine("SSE " + EventClient.Status);

What I can see is the following:

  1. The "subscription" to the backend works. I can see the request and it does start running the backend thread
  2. The status log shows that after a short while into "starting" the status changes to "started"
  3. The "started" status will remain this way until the timeout of 30s has been reached. then it reconnects and the circle continues
  4. The method OnMessage is never called, not even after timeout, like I could see it in postman

ADDITIONAL NOTE: I also had a server-side test implementation using SseEmitter. In this case I also could see the app request during "subscription" but it would timeout at some point with an exception and the client status would never leave "starting"

my first question obviously is: what I have missed that I will never receive a message?

my second question is: why does it timeout in the first place? According to the documentation it would send periodic heartbeats. Do I need a different approach on the spring side? Or do I need to implement a separate endpoint for the heartbeat?

thanks for your help!


EDIT:

following @mythz answer and accepting that using ServiceStack here is not a good idea, I followed the following example and implemented a simple way using the standard HttpClient and StreamReader: https://makolyte.com/event-driven-do.net-how-to-consume-an-sse-endpoint-with-httpclient/

However, this resulted in the same problem I experienced with postman or my browser before. All messages are sent in a bulk after the 30s timeout. Therefore I also altered the backend part and use spring web flux instead:

public Flux<ServerSentEvent<String>> streamEvents() {
        return Flux.interval(Duration.ofSeconds(1))
                .map(sequence -> {
                    var msg = new ResponseTestObject("this is a message from " + new Date(), Math.toIntExact(sequence));
                    ObjectMapper mapper = new ObjectMapper();
                    try {
                        return ServerSentEvent.<String>builder()
                                .id(String.valueOf(sequence))
                                .event("periodic-event")
                                .data(mapper.writeValueAsString(msg))
                                .build();
                    } catch (JsonProcessingException e) {
                        return null;
                    }
                });
}

I would highly recommend that you don't ServiceStack's ServerEventsClient with anything other than ServiceStack's Server Events Feature which is what all its typed Server Events clients are designed to work with.

Eg in order to enable broken.network connections and auto-retry connection feature the clients send back periodic heartbeats. This is only a feature in ServiceStack's implementation as there's no such concept in the SSE standard.

That's just one example, basically every high-level in the C# Server Events Client makes use of ServiceStack server features which wouldn't exist in any other 3rd Party implementation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM