Spring webflux and the servlet API

Although Spring WebFlux is a perfect match with async runtimes such as Netty and Undertow (more on these in a next article) most of us still use some kind of servlet container to run our web applications. Spring’s Reactive Webflux project requires at least servlet API 3.1+ and there are some good reasons for that. In this article we look at the servlet API features that allow for reactive programming in a web environment and which Spring Webflux builds upon.

 

Servlet API

When I say: ‘Java web app’, you say? Servlets!

The servlet API has been among us for a long time and is still the most commenly used java API for web applications, supported by many application servers and frameworks. But changes in architecture (ex. microservices), the evolution of web 2.0 technologies (ex. ajax, single page applications, …) and an increased number of application users have drastically changed the traffic profile between Web clients (such as browsers) and Web servers. Figuring out how to make Web servers more scalable is an ongoing challenge.

History

A servlet container has to handle incoming requests from clients and handles this by assigning threads from a server-managed thread pool. Once a response is send to the client, the dedicated thread is recycled back to the pool and is ready to serve other tasks.

Before servlet 3.0 the servlet handling was synchronous and improvements were made towards a more fine-grained thread-per-request model, making use of the non-blocking I/O capability in Java to detach threads from a HTTP connection when not in use for optimization. I suggest reading the article “Asynchronous processing support in Servlet 3.0” for more details.

But with the use of more simultaneous requests (Ajax applications) we cause more threads to be consumed, which cancels out the benefit of the thread-per-request approach to a high degree. Add some slow running (backend) resources to that (ex. a request could be blocked by a depleted JDBC connection pool or REST call to another microservice) and we’re back to square one.

Servlet 3.0

Servlet 3.0 introduced Async servlet-handling support. Slow tasks can now be handled in a separate thread without blocking the container thread pool and, when finished, the container is notified assigning a new container-thread to send the result back to the client. Sweet!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@WebServlet(name="myServlet", urlPatterns={"/myurl"}, asyncSupported=true)
public class MyServlet extends HttpServlet {

//1. container calls the servlet
protected void doGet(final HttpServletRequest req, HttpServletResponse resp) {

//2. servlet tells the container that the request should be asynchronous
final AsyncContext context = req.startAsync();

//3. asynchronous call
ctx.start(new Runnable() {
public void run() {
try {
ctx.getResponse().getWriter().write(
MessageFormat.format("<h1>Processing task in thread:[{0}]</h1>", Thread.currentThread().getId()));
}
catch (IOException e) {
log("Problem processing task", e);
}
//4. Tell container the request is processed by the servlet
ctx.complete();
}
});
}

Spring MVC 3.2+ also implemented this Async servlet support allowing your controllers to return a Callable or Deferred result.

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/callablerequest")
public Callable<String> callable(){
Callable<String> result = new Callable<String>() {
@Override
public String call() throws Exception {
return services.doProcess();
}
};

return result;
}

Check this article for more info on how to use Async servlet support in Spring.

Servlet 3.1+

It goes without saying that the above Async servlet handling is crucial in achieving an async pipeline for handling http requests in a Reactive manner. But wait, there’s something missing. In reactive programming we want data to start flowing as soon as it becomes available. In the example above that’s not the case. The response is sent to the client when it is complete.

Servlet 3.1 (Java EE 7) introduced non-blocking IO for the request and response bodies. By using the newly added setReadListener(ReadListener readListener)/setWriteListener(WriteListener writeListener) on ServletInputStream/ServletOutputStream respectively, we can now start reading/writing data as soon as it is available. Reactive here we come!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
ServletInputStream servletInputStream = request.getInputStream();

public interface ReadListener extends EventListener {

void onAllDataRead();

void onDataAvailable();

void onError(Throwable);

}

public interface WriteListener extends EventListener {

void onError(Throwable);

void onWritePossible();

}

The careful reader will notice a great similarity with the reactive streams API (http://www.reactive-streams.org)

1
2
3
4
5
6
7
Flow.Subscriber<T>

onComplete​()

onNext​(T item)

onError​(Throwable throwable)

Spring webflux

Now that we have all the background information we can dive into the internals of Spring webflux for servlet support!

Spring webflux can run on different runtimes (Servlet 3.1+ container, Undertow, Reactor, …) and has different adapters for each of these runtimes wrapping the runtime specific request & response in the (new) reactive abstractions: ServerHttpRequest and ServerHttpResponse

  • UndertowHttpHandlerAdapter
  • TomcatHttpHandlerAdapter
  • ServletHttpHandlerAdapter

 

These abstractions can be found under the package: org.springframework.http.server.reactive and expose the body of the request/response as a reactive datatype with backpressure support.

1
2
3
org.springframework.http.server.reactive.ServerHttpRequest

Flux<DataBuffer> getBody()
1
2
3
4
5
org.springframework.http.server.reactive.ServerHttpResponse

Mono<java.lang.Void> setComplete()

Mono<java.lang.Void> writeWith(Publisher<? extends DataBuffer> body)

In the specific case of running our reactive web application on a servlet containter you’ll notice in the ServletHttpHandlerAdapater that an AsyncContext is started (servlet 3.0 support).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ServletHttpHandlerAdapter implements Servlet {

@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {

...

AsyncContext asyncContext = request.startAsync();

...

ServerHttpRequest httpRequest = createRequest(((HttpServletRequest) request), asyncContext);
ServerHttpResponse httpResponse = createResponse(((HttpServletResponse) response), asyncContext);

...

}

Summary

Spring Webflux abstracts the underlying runtime and you can either go for a traditional servlet container (3.1+), or one of the async runtimes (netty, undertow, …). By now, we know why Servlet 3.1 is the minimum requirements for Spring Webflux. Also reactive REST-style JSON/XML serialization and deserialization is supported on top as a Flux<Object>, and so is HTML view rendering and Server-Sent Events.