How to use servlet 3.1 in spring mvc?
Asked Answered
N

4

19

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 ?

Nygaard answered 1/7, 2019 at 12:48 Comment(2)
My tip would be WebFlux for this.Karolynkaron
@Karolynkaron correct advice but I want to know more about alternatives. I asked that question because I want to know why WebFlux better than pure servlet 3.1Nygaard
D
1

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();
Danit answered 18/7, 2019 at 14:23 Comment(1)
Async is a feature of servlet 3.0. My question about servlet 3.1 feature - non-blocking IONygaard
G
0

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+)

Gratification answered 4/7, 2019 at 19:6 Comment(2)
Could you comment upon that code please? It is not transparent for me why that code non-blocking. Also I don't understand what that code doesNygaard
This example is not about Servlet 3.1, when you return CompletableFuture from Controller it just release Tomcat thread and put this task into a separate thread poll, so I don't think this answer is relevantJonejonell
L
0

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).

Livengood answered 9/7, 2019 at 16:58 Comment(9)
but your listener is just a stub and does nothingNygaard
It writes to the stream and closes it. What do you think it's supposed to do? output.println("WriteListener:onWritePossible() called to send response data on thread : " + Thread.currentThread().getName());Livengood
From the documentation of WriteListener - "Invoked when it it possible to write data without blocking. The container will invoke this method the first time for a request as soon as data can be written." And from ServletOutputStream.setWriteListener - "Sets the {@link WriteListener} for this {@link ServletOutputStream} and thereby switches to non-blocking IO." I think covers all the points you asked.Howenstein
I want to sea non empty body of listener. For example writing some long json string (5 mb)Nygaard
I believe that ServletOutputStream#println is blocking methodNygaard
Asyncronously is not equals non-blocking.Nygaard
servlet 3.1 was designed for non-blocking communication but your code breaks that design so it is bad example of usage servlet 3.1Nygaard
Could you please elaborate a bit? are you disagree that servlet 3.1 related with non-blocking?Nygaard
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?Nygaard
G
0

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.

Glee answered 11/10, 2022 at 14:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.