A new Indy HTTP client / JAX-RS server example is now available on GitHub. The server side generates Server-sent events. Server-sent events (SSE) is a technology enabling a browser to receive automatic updates from a server via HTTP connection.
The example code uses TIdHTTP and TIdEventStream to connect to the server, and writes the incoming events to the console window.
Requirements
- Delphi or Lazarus IDE
- Indy 10.6.2
- Java IDE
- Java EE 8 application server
Client
The SSE client setup in the TIndySSEClient.Create creates a TIdHTTP instance and a TIdEventStream instance.
As recommended, the Accept header is set to 'text/event-stream'
. It also sets the Cache-Control header to 'no-store'
to prevent proxy servers from caching the result.
Our event handler TIndySSEClient.MyOnWrite
is assigned to the TIdEventStreamOnWrite
property.
constructor TIndySSEClient.Create; begin inherited Create; SSE_URL := URL; EventStream := TIdEventStream.Create; EventStream.OnWrite := MyOnWrite; IdHTTP := TIdHTTP.Create; IdHTTP.Request.Accept := 'text/event-stream'; IdHTTP.Request.CacheControl := 'no-store'; end;
The OnWrite handler decodes the UTF-8 encoded event data and writes it to the console:
procedure TIndySSEClient.MyOnWrite; begin WriteLn('Received ' + IntToStr(Length(ABuffer)) + ' bytes'); WriteLn; WriteLn(IndyTextEncoding_UTF8.GetString(ABuffer)); ... end;
Server (main REST method)
The server generates the server-sent event and places a Stock instance in its data part:
@GET @Path("prices") @Produces(MediaType.SERVER_SENT_EVENTS) public void getStockPrices(@Context SseEventSink sseEventSink) { int lastEventId = 1; while (running) { Stock stock = getNextTransaction(); System.out.println("Send event ..."); OutboundSseEvent sseEvent = this.eventBuilder .name("stock") .id(String.valueOf(lastEventId)) .mediaType(MediaType.APPLICATION_JSON_TYPE) .data(Stock.class, stock) .reconnectDelay(3000) .comment("price change") .build(); sseEventSink.send(sseEvent); lastEventId++; } sseEventSink.close(); }
Additional notes
- this is the first draft of a SSE client, which does not support features such as reconnect
- the example assumes that every call of MyOnWrite contains exactly one event, teminated by a sequence of two line separators (cr lf / cr / lf) so that no extra code is required to do proper event stream parsing
Full source code
https://github.com/michaelJustin/indy-sse-jaxrs
References
- https://www.indyproject.org/2016/01/10/new-tidhttp-flags-and-onchunkreceived-event/
- https://html.spec.whatwg.org/multipage/server-sent-events.html
- https://www.html5rocks.com/en/tutorials/eventsource/basics/
- https://www.baeldung.com/java-ee-jax-rs-sse
- https://www.jcp.org/en/jsr/detail?id=370
Discover more from Habarisoft Blog
Subscribe to get the latest posts sent to your email.
NOTE: this approach works ONLY if 1) the server uses HTTP’s “chunked” transfer encoding when sending push events, 2) each chunk is a self-contained event, and 3) a TStream is passed to TIdHTTP for it to write to, otherwise the OnChunkedReceived event will not be called.
There are other ways to handle server-side pushes, such as with MIME’s “multipart/x-mixed-replace” media type, which will not work with the OnChunkReceived event, you would have to enable TIdHTTP’s hoNoReadMultipartMIME flag and then manually read the MIME data after TIdHTTP has fully exited (see https://www.indyproject.org/2014/03/05/new-tidhttp-honoreadmultipartmime-flag/).
Many thanks! I have updated the code to use TIdEventStream instead of OnChunkReceived. I understand that it is not guaranteed to receive acomplete event in a single TIdEventStream.OnWrite, need to read about chunked HTTP transfer, and SSE implementations.
Nice article. But, how we generate server-sent events with indy server? Is it possible?
Indy 10 does not support SSE as far as I know. Maybe you can submit a feature request on the Indy Sockets GitHub page – https://github.com/IndySockets/Indy/issues