Is Servlet 3.1 (Read|Write)Listener supported by D

2020-07-17 15:23发布

问题:

I was reading the JayWay article about Async support on Servlet with Spring.

The interesting part is:

If your service is expected to receive large request or response bodies, especially if the clients write or read slowly, you would benefit from using the non-blocking IO feature introduced in Servlet 3.1, as mentioned earlier. On the ServletInputStream there is the method setReadListener where you can set a ReadListener.

I saw that you can do something with DeferredResult in terms of starting the servlet asynchronously but I cannot find information about anything related about ReadListener and WriteListener.

Or at least, I expected something on that side because it's kind on the border of my application, I just need to get the request and send a result.

回答1:

Yes, it is possible to use ReadListeners with Spring's DeferredResult, I'll outline the process below. I imagine the process for WriteListeners is similar.

Starting Async Processing

When Spring sees a DeferredResult as a return type the WebAsyncManager will call request.startAsync() after your method executes. This starts the request processing in async mode. It is important that you do not start async processing manually in your controller method, Spring cannot cope with async processing already being started and exceptions will be thrown.

Attaching your ReadListener

This will vary by container. Jetty will allow you to attach the readListener to the ServletInputStream in your controller method, before async processing has begun as follows:

DeferredResult<String> deferredResult = new DeferredResult<String>();
ReadListener readListener = new NioReadListener(request, deferredResult, modelMap);
ServletInputStream stream = request.getInputStream();
stream.setReadListener(readListener);

Tomcat, and perhaps other containers too, do not permit this. Their interpretation of the Servlet Spec is that asynchronous processing must be started before it is permissible to attach the ReadListener. Therefore, if you want your application to be container agnostic, I suggest the below technique.

Create a DeferredResultProcessingInterceptor

Spring provides a mechanism for intercepting asynchronous request processing with DeferredResultProcessingInterceptors. Create your interceptor as follows:

/**
 * Responsible for attaching a {@link NioReadListener} to the servlet input stream before async processing begins.
 */
public class NioDeferredResultInterceptor extends DeferredResultProcessingInterceptorAdapter {

private final DeferredResult<String> _deferredResult;
private final VaultServletRequest _request;
private final ModelMap _model;

    public NioDeferredResultInterceptor(ServletRequest request, DeferredResult<String> deferredResult, ModelMap model) {
        _deferredResult = deferredResult;
        _request = request;
        _model = model;
    }

    @Override
    public <T> void preProcess(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
        ReadListener readListener = new NioReadListener(_request, _deferredResult, _model);
        ServletInputStream servletInputStream = _request.getInputStream();
        servletInputStream.setReadListener(readListener);
    }

The preProcess method will be executed immediately after Spring has started async processing, but before anything else occurs, which is exactly what we want.

The DeferredResultProcessingInterceptor can be attached to your request in your controller method as follows:

DeferredResultProcessingInterceptor deferredResultInterceptor =
                    new NioDeferredResultInterceptor(request, deferredResult, modelMap);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerDeferredResultInterceptor("SomeKey", deferredResultInterceptor);