How to use servlet 3.1 in spring mvc?

4.4k views Asked by At

There are 2 different features available:

  1. servlet 3.0 allows to process request in a thread different from the container thread.

  2. servlet 3.1 allows to read/write into socket without blocking reading/writing thread

There are a lot of examples in the internet about servlet 3.0 feature. We can use it in Spring very easily. We just have to return DefferedResult or CompletableFuture

But I can't find example of usage servlet 3.1 in spring. As far as I know we have to register WriteListener and ReadListener and do dome dirty work inside. But I can't find the example of that Listeners. I believe it is not very easy.

Could you please provide example of servlet 3.1 feature in spring with explanation of Listener implementaion ?

4

There are 4 answers

2
Mikhail Kholodkov On

If you're looking for an example of Spring/Servlet 3.1 non-blocking HTTP API declaration, try the following:

@GetMapping(value = "/asyncNonBlockingRequestProcessing")
public CompletableFuture<String> asyncNonBlockingRequestProcessing(){
        ListenableFuture<String> listenableFuture = getRequest.execute(new AsyncCompletionHandler<String>() {
            @Override
            public String onCompleted(Response response) throws Exception {
                logger.debug("Async Non Blocking Request processing completed");
                return "Async Non blocking...";
             }
        });
        return listenableFuture.toCompletableFuture();
}

Requires Spring Web 5.0+ and Servlet 3.1 support at Servlet Container level (Tomcat 8.5+, Jetty 9.4+, WildFly 10+)

9
K.Nicholas On

Shouldn't be too hard to chase down some examples. I found one from IBM at WASdev/sample.javaee7.servlet.nonblocking . Working with javax.servlet API in Spring or Spring Boot is just a matter of asking Spring to inject HttpServletRequest or HttpServletResponse. So, a simple example could be:

@SpringBootApplication
@Controller
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @RequestMapping(path = "")
    public void writeStream(HttpServletRequest request, HttpServletResponse response) throws IOException {
        ServletOutputStream output = response.getOutputStream();
        AsyncContext context = request.startAsync();
        output.setWriteListener(new WriteListener() {
            @Override
            public void onWritePossible() throws IOException {
                if ( output.isReady() ) {
                    output.println("WriteListener:onWritePossible() called to send response data on thread : " + Thread.currentThread().getName());
                }
                context.complete();
            }
            @Override
            public void onError(Throwable t) {
                context.complete();
            }
        });
    }
}

This simply creates a WriteListener and attaches it to the request output stream then returns. Nothing fancy.

EDITS: The point is that the servlet container, e.g., Tomcat, calls onWritePossible when data can be written without blocking. More on this at Non-blocking I/O using Servlet 3.1: Scalable applications using Java EE 7 (TOTD #188)..

The listeners (and writers) have callback methods that are invoked when the content is available to be read or can be written without blocking.

So therefore onWritePossible is only called when out.println can be called without blocking.

Invoking setXXXListener methods indicate that non-blocking I/O is used instead of the traditional I/O.

Presumably what you have to do check output.isReady to know if you can continue to write bytes. It seems to be that you would have to have some sort of implicit agreement with the sender/receiver about block sizes. I have never used it so I don't know, but you asked for an example of this in Spring framework and that's what is provided.

So therefore onWritePossible is only called when out.println can be called without blocking. It is sounds correct but how can I understand how many bytes can be written ? How should I control this?

EDIT 2: That is a very good question that I can't give you an exact answer to. I would assume that onWritePossible is called when the server executes the code in a separate (asynchronous) thread from the main servlet. From the example you check input.isReady() or output.isReady() and I assume that blocks your thread until the sender/receiver is ready for more. Since this is done asynchronously the server itself is not blocked and can handle other requests. I have never used this so I am not an expert.

When I said there would be some sort of implicit agreement with the sender/receiver about block sizes that means that if the receiver is capable of accepting 1024 byte blocks then you would write that amount when output.isReady is true. You would have to have knowledge about that by reading the documentation, nothing in the api about it. Otherwise you could write single bytes but the example from oracle uses 1024 byte blocks. 1024 byte blocks is a fairly standard block size for streaming I/O. The example above would have to be expanded to write bytes in a while loop like it is shown in the oracle example. That is an exercise left for the reader.

Project reactor and Spring Webflux have the concept of backpressure that may address this more carefully. That would be a separate question and I have not looked closely into how that couples senders and receivers (or vice versa).

1
Ori Marko On

For servlet 3.1 you can support non-blocking I/O by using Reactive Streams bridge

Servlet 3.1+ Container

To deploy as a WAR to any Servlet 3.1+ container, you can extend and include {api-spring-framework}/web/server/adapter/AbstractReactiveWebInitializer.html[AbstractReactiveWebInitializer] in the WAR. That class wraps an HttpHandler with ServletHttpHandlerAdapter and registers that as a Servlet.

So you should extend AbstractReactiveWebInitializer which is adding async support

registration.setAsyncSupported(true);

And the support in ServletHttpHandlerAdapter

AsyncContext asyncContext = request.startAsync();
0
VimalKumar On

Servlet 3.0 - Decouples the container thread and the processing thread. Returns DeferredResult or CompletableFuture. So controller processing can be in a different thread than the server request handling thread. The server thread pool is free to handle more incoming requests.

But still, the IO is still blocking. Reading and Writing to the Input and Output stream ( receiving and sending responses to slow clients).

Servlet 3.1 - Non-blocking all the way including IO. Using Servlet 3.1 on Spring directly means you have to use ReadListener and WriteListener interfaces which are too cumbersome. Also, you have to deviate from using Servlet API like Servlet, and Filter which are synchronous and blocking. Use Spring Webflux instead which uses Reactive Streams API and supports Servlet 3.1.