4

I've been trying to get json streaming to work in jersey 2. For the life of me nothing streams until the stream is complete.

I've tried this example trying to simulate a slow producer of data.

@Path("/foo")
@GET
public void getAsyncStream(@Suspended AsyncResponse response) {
    StreamingOutput streamingOutput = output -> {

        JsonGenerator jg = new ObjectMapper().getFactory().createGenerator(output, JsonEncoding.UTF8);
        jg.writeStartArray();

        for (int i = 0; i < 100; i++) {
            jg.writeObject(i);

            try {
                Thread.sleep(100);
            }
            catch (InterruptedException e) {
                logger.error(e, "Error");
            }
        }

        jg.writeEndArray();

        jg.flush();
        jg.close();

    };

    response.resume(Response.ok(streamingOutput).build());
}

And yet jersey just sits there until the json generator is done to return the results. I'm watching the results come through in charles proxy.

Do I need to enable something? Not sure why this won't stream out


Edit:

This may actually be working, just not how I expected it. I dont' think stream is writing things realtime which is what I wanted, its more for not having to buffer responses and immediately write them out to the client. If I run a loop of a million and no thread sleep then data does get written out in chunks without having to buffer it in memory.

devshorts
  • 8,572
  • 4
  • 50
  • 73
  • How did you solve the problem? – gkiko Sep 07 '15 at 10:38
  • @gkiko the accepted answer solved my issue. Streaming is not chunking. That was what was my confusion. – devshorts Sep 11 '15 at 01:38
  • Check out my edit. If this is something you are working on. Setting the property mentioned in the link, to small size or 0, will allow the streaming to work the way you would expect. – Paul Samsotha Dec 10 '15 at 09:25

1 Answers1

5

Your edit it correct. It is working as expected. StreamingOutput is just a wrapper that let's us write directly to the response stream, but does not actually mean the response is streamed on each server side write to the stream. Also AsyncResponse does not provide any different response as far as the client is concerned. It is simply to help increase throughput with long running tasks. The long running task should actually be done in another thread, so the method can return.

What you seem to be looking for instead is Chunked Output

Jersey offers a facility for sending response to the client in multiple more-or-less independent chunks using a chunked output. Each response chunk usually takes some (longer) time to prepare before sending it to the client. The most important fact about response chunks is that you want to send them to the client immediately as they become available without waiting for the remaining chunks to become available too.

Not sure how it will work for your particular use case, as the JsonGenerator expects an OutputStream (of which the ChuckedOutput we use is not), but here is a simpler example

@Path("async")
public class AsyncResource {

    @GET
    public ChunkedOutput<String> getChunkedStream() throws Exception {
        final ChunkedOutput<String> output = new ChunkedOutput<>(String.class);

        new Thread(() -> {
            try {
                String chunk = "Message";

                for (int i = 0; i < 10; i++) {
                    output.write(chunk + "#" + i);
                    Thread.sleep(1000);
                }
            } catch (Exception e) {
            } finally {
                try {
                    output.close();
                } catch (IOException ex) {
                    Logger.getLogger(AsyncResource.class.getName())
                          .log(Level.SEVERE, null, ex);
                }
            }
        }).start();
        return output;
    }
}

Note: I had a problem getting this to work at first. I would only get the delayed complete result. The problem seemed to have been with something completely separate from the program. It was actually my AVG causing the problem. Some feature called "LinkScanner" was stopping this chunking process to occur. I disabled that feature and it started working.

I haven't explored chunking much, and am not sure the security implications, so I am not sure why the AVG application has a problem with it.

enter image description here


EDIT

Seems the real problem is due to Jersey buffering the response in order to calculate the Content-Length header. You can see this post for how you can change this behavior

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720