Asynchronous non-blocking request handling

Mu Server optionally provides non-blocking reading of requests and writing of responses. Any handler can become an async handler by invoking the following method which returns a AsyncHandle:

AsyncHandle handle = request.handleAsync();

Calling the complete() method on that handle marks the response as complete.

Writing responses asynchronously

With an AsyncHandle, you can write byte buffers using one of two non-blocking methods, depending on whether you prefer a future or a callback to be notified when the write has completed:

Tip: to convert a string to a ByteBuffer, use Mutils.toByteBuffer(String)

In the following example, when a request for a JPG is made, the file is opened and segments of the file are read asynchronously. When some bytes are read from the file, they are passed on to the response. The write method with the DoneCallback is used so that after the bytes from the byte-buffer have been written to the response, another request to read more data is made.

public class AsyncWritingExample {
    public static void main(String[] args) {
        MuServer server = httpServer()
            .addHandler(Method.GET, "/guangzhou.jpg", (request, response, pathParams) -> {

                response.contentType(ContentTypes.IMAGE_JPEG);

                AsyncHandle asyncHandle = request.handleAsync();
                Path path = Paths.get("src/test/resources/guangzhou.jpg");

                AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);
                FileChannelToResponsePump pump = new FileChannelToResponsePump(asyncHandle, channel);
                pump.pumpIt();

            })
            .start();

        System.out.println("Download the file at " + server.uri().resolve("/guangzhou.jpg"));

    }

    private static class FileChannelToResponsePump implements CompletionHandler<Integer, Object> {

        private final AsynchronousFileChannel channel;
        private final AsyncHandle asyncHandle;
        private final ByteBuffer buffer = ByteBuffer.allocate(8192);
        private long bytesSent = 0;

        FileChannelToResponsePump(AsyncHandle asyncHandle, AsynchronousFileChannel channel) {
            this.asyncHandle = asyncHandle;
            this.channel = channel;
        }

        public void pumpIt() {
            // Read from the file into the buffer, starting at position 0, and call 'this' instance's
            // completed method when read.
            channel.read(buffer, 0, null, this);
        }

        @Override
        public void completed(Integer bytesRead, Object attachment) {
            buffer.flip();
            if (bytesRead != -1) {

                // Write the jpg bytes from the buffer to the response channel
                asyncHandle.write(buffer, new DoneCallback() {

                    @Override
                    public void onComplete(Throwable errorIfFailed) throws Exception {
                        if (errorIfFailed == null) {
                            // Clear the buffer, and then start reading the next segment of data
                            buffer.clear();
                            bytesSent += bytesRead;
                            channel.read(buffer, bytesSent, null, FileChannelToResponsePump.this);
                        } else {
                            // client probably disconnected so stop reading and close the request
                            asyncHandle.complete();
                        }
                    }

                });
            } else {
                // File reading is complete. End the response and close the file channel.
                asyncHandle.complete();
                try {
                    channel.close();
                } catch (IOException ignored) { }
            }
        }

        @Override
        public void failed(Throwable exc, Object attachment) {
            // File reading failed. Log an error and send a 500 to the client.
            asyncHandle.complete(exc);
        }
    }
}
(see full file)

Note: the file handler described on the static resources page is uses the above approach so it's recommended you use that if you need to serve files.

Reading request bodies asynchronously

If you expect a request body, it can be read asynchronously by adding a RequestBodyListener with the AsyncHandle's setReadListener method:

public class AsyncRequestBodyExample {
    public static void main(String[] args) {
        MuServer server = httpServer()
            .addHandler(Method.POST, "/upload", new AsyncRequestBodyHandler())
            .addHandler(ResourceHandlerBuilder.classpathHandler("/samples"))
            .start();

        System.out.println("Upload a file at " + server.uri().resolve("/async-upload.html"));

    }

    private static class AsyncRequestBodyHandler implements RouteHandler {
        @Override
        public void handle(MuRequest request, MuResponse response, Map<String, String> pathParams) {

            AsyncHandle asyncHandle = request.handleAsync();

            long expectedSize = Long.parseLong(request.headers().get("content-length", "-1"));

            response.contentType("text/plain;charset=utf-8");
            AtomicLong total = new AtomicLong();

            asyncHandle.setReadListener(new RequestBodyListener() {

                public void onDataReceived(ByteBuffer buffer, DoneCallback doneCallback) {
                    int dataSize = buffer.remaining();
                    String message = "Received " + dataSize + " bytes - total so far is "
                        + total.addAndGet(dataSize) + " of " + expectedSize + " bytes\n";
                    // Note: do not call any blocking response writing methods from a data-received
                    // callback. Instead use the async writer on the handle.
                    asyncHandle.write(Mutils.toByteBuffer(message), doneCallback);
                    System.out.print(message);
                }

                public void onComplete() {
                    String message = "\n\nUpload complete. Received " + total.get() + " bytes.";
                    System.out.println(message);
                    asyncHandle.write(ByteBuffer.wrap(message.getBytes(UTF_8)));
                    asyncHandle.complete();
                }

                public void onError(Throwable t) {
                    System.out.println("\n\nError while reading: " + t);
                    asyncHandle.complete();
                }
            });

        }
    }
}
(see full file)

Note that if you do not need non-blocking request reading, the blocking request body readers can still be used by async handlers.