© Marten Deinum, Daniel Rubio, and Josh Long 2017

Marten Deinum, Daniel Rubio and Josh Long, Spring 5 Recipes, https://doi.org/10.1007/978-1-4842-2790-9_5

5. Spring MVC: Async Processing

Marten Deinum, Daniel Rubio2 and Josh Long3

(1)Meppel, Drenthe, The Netherlands

(2)F. Bahia, Ensenada, Baja California, Mexico

(3)Apartment 205, Canyon Country, California, USA

When the Servlet API was released, the majority of the implementing containers used one thread per request. This meant a thread was blocked until the request processing had finished and the response was sent to the client.

However, in those early days, there weren’t as many devices connected to the Internet as today. Because of the increased number of devices, the number of HTTP requests being handled has grown significantly, and because of this increase, for lots of web applications, keeping a thread blocked isn’t feasible anymore. As of the Servlet 3 specification, it is possible to handle an HTTP request asynchronously and release the thread that initially handled HTTP request. The new thread will run in the background, and as soon as the result is available, it will be written to the client. This, when done right, can all take place in a nonblocking way on a Servlet 3.1–compliant servlet container. Of course, all resources being used also would have to be nonblocking.

In the past couple of years, there has also been an uptick in reactive programming, and as of Spring 5, it is possible to write reactive web applications. A reactive Spring project utilizes Project Reactor (just like Spring, it is maintained by Pivotal) as an implementation of the Reactive Streams API. It goes beyond the scope of this book to do a full dive into reactive programming, but in short it is a way of doing nonblocking functional programming.

Traditionally, when working with web applications, there would be a request; HTML would be rendered on the server and then get sent back to the client. The last couple of years, the job of rendering HTML moved to the client, and communication was done not through HTML but by returning JSON, XML, or another representation to the client. This was traditionally still a request-and-response cycle although it was driven by an async call from the client through the XMLHttpRequest object. However, there are also other ways of communicating between the client and server; you could utilize server-sent events to have one-way communication from the server to the client, and for full-duplex communication, you could use the WebSocket protocol.

5-1. Handle Requests Asynchronously with Controllers and TaskExecutor

Problem

To reduce the load on the servlet container, you want to asynchronously handle a request.

Solution

When a request comes in, it is handled synchronously, which blocks the HTTP request-handling thread. The response stays open and is available to be written to. This is useful when a call, for instance, takes some time to finish. Instead of blocking threads, you can have this processed in the background and return a value to the user when finished.

How It Works

As mentioned in recipe 3-1, Spring MVC supports a number of return types from methods. In addition to the return types, the types in Table 5-1 are processed in an asynchronous way.

Table 5-1. Asynchronous Return Types

Type

Description

DeferredResult

Async result produced later from another thread

ListenableFuture<?>

Async result produced later from another thread; an equivalent alternative for DeferredResult

CompletableStage<?> / CompletableFuture<?>

Async result produced later from another thread; an equivalent alternative for DeferredResult

Callable<?>

Async computation with the result produced after the computation finishes

ResponseBodyEmitter

Can be used to write multiple objects to the response asynchronously

SseEmitter

Can be used to write a server-sent event asynchronously

StreamingResponseBody

Can be used to write to OutputStream asynchronously

The generic async return types can hold any of the return types for the controller, including an object to be added to the model, the name of the view, or even a ModelAndView object.

Configure Async Processing

To use the async processing features of Spring MVC, you first have to enable them. Async request-handling support has been added to the Servlet 3.0 specification, and to enable it, you have to tell all your filters and servlets to behave asynchronously. To do this, you can call the setAsyncSupported() method when registering a filter or servlet.

When writing a WebApplicationInitializer, you have to do the following:

public class CourtWebApplicationInitializer implements WebApplicationInitializer {

    public void onStartup(ServletContext ctx) {

        DispatcherServlet servlet = new DispatcherServlet();
        ServletRegistration.Dynamic registration = ctx.addServlet("dispatcher", servlet);
        registration.setAsyncSupported(true);
    }


}
Note

When doing async processing, all the servlet filters and servlets in your app should have this property switched to true or async processing won’t work!

Luckily, Spring helps you with this, and when using the AbstractAnnotationConfigDispatcherServletInitializer as a superclass, this property is enabled by default for the registered DispatcherServlet and filters. To change it, override isAsyncSupported() and implement the logic to determine whether it should be on or off.

Depending on your needs, you probably also need to configure an AsyncTaskExecutor and wire that in the MVC configuration.

package com.apress.springrecipes.court.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;


@Configuration
public class AsyncConfiguration extends WebMvcConfigurationSupport {


    @Override
    protected void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setDefaultTimeout(5000);
        configurer.setTaskExecutor(mvcTaskExecutor());
    }


    @Bean
    public ThreadPoolTaskExecutor mvcTaskExecutor() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        taskExecutor.setThreadGroupName("mvc-executor");
        return taskExecutor;
    }
}

To configure async processing, you need to override the configureAsyncSupport method of WebMvcConfigurationSupport; overriding this method gives you access to the AsyncSupportConfigurer and allows you to set the defaultTimeout and AsyncTaskExecutor values. The timeout is set to five seconds, and for an executor, you will use a ThreadPoolTaskExecutor (see also recipe 2-23).

Write an Asynchronous Controller

Writing a controller and having it handle the request asynchronously is as simple as changing the return type of the controller’s handler method. Let’s imagine that the call to ReservationService.query takes quite some time, but you don’t want to block the server for that.

Use a Callable

Here’s how to use a callable :

package com.apress.springrecipes.court.web;

import com.apress.springrecipes.court.Delayer;
import com.apress.springrecipes.court.domain.Reservation;
import com.apress.springrecipes.court.service.ReservationService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;


import java.util.List;
import java.util.concurrent.Callable;


@Controller
@RequestMapping("/reservationQuery")
public class ReservationQueryController {


    private final ReservationService reservationService;

    public ReservationQueryController(ReservationService reservationService) {
        this.reservationService = reservationService;
    }


    @GetMapping
    public void setupForm() {}


    @PostMapping
    public Callable<String> sumbitForm(@RequestParam("courtName") String courtName, Model model) {
        return () -> {
            List<Reservation> reservations = java.util.Collections.emptyList();
            if (courtName != null) {
                Delayer.randomDelay(); // Simulate a slow service
                reservations = reservationService.query(courtName);
            }
            model.addAttribute("reservations", reservations);
            return "reservationQuery";
        };
    }
}

If you look at the submitForm method, it now returns a Callable<String> instead of returning a String directly. Inside the newly constructed Callable<String>, there is a random wait to simulate a delay before calling the query method .

Now when making a reservation, you will see something like this in the logs:

2017-06-20 10:37:04,836 [nio-8080-exec-2] DEBUG o.s.w.c.request.async.WebAsyncManager    : Concurrent handling starting for POST [/court/reservationQuery]
2017-06-20 10:37:04,838 [nio-8080-exec-2] DEBUG o.s.web.servlet.DispatcherServlet        : Leaving response open for concurrent processing
2017-06-20 10:37:09,954 [mvc-executor-1 ] DEBUG o.s.w.c.request.async.WebAsyncManager    : Concurrent result value [reservationQuery] - dispatching request to resume processing
2017-06-20 10:37:09,959 [nio-8080-exec-3] DEBUG o.s.web.servlet.DispatcherServlet        : DispatcherServlet with name 'dispatcher' resumed processing POST request for [/court/reservationQuery]

Notice that request handling is handled on a certain thread (here nio-8080-exec-2), which is released, and then another thread does the processing and returns the result (here mvc-executor-1). Finally, the request is dispatched to the DispatcherServlet again to handle the result on yet another thread.

Use a DeferredResult

Instead of a Callable<String>, you could have used a DeferredResult<String>. When using a DeferredResult, you need to construct an instance of this class, submit a task to be async processed, and in that task fill the result of the DeferredResult using the setResult method. When an exception occurs, you can pass this exception to the setErrorResult method of the DeferredResult.

package com.apress.springrecipes.court.web;

import com.apress.springrecipes.court.Delayer;
import com.apress.springrecipes.court.domain.Reservation;
import com.apress.springrecipes.court.service.ReservationService;


import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.async.DeferredResult;


import java.util.List;

@Controller
@RequestMapping("/reservationQuery")
public class ReservationQueryController {


    private final ReservationService reservationService;
    private final TaskExecutor taskExecutor;


    public ReservationQueryController(ReservationService reservationService, AsyncTaskExecutor taskExecutor) {
        this.reservationService = reservationService;
        this.taskExecutor = taskExecutor;
    }

    @GetMapping
    public void setupForm() {}


    @PostMapping
    public DeferredResult<String> sumbitForm(@RequestParam("courtName") String courtName, Model model) {
        final DeferredResult<String> result = new DeferredResult<>();


        taskExecutor.execute(() -> {
            List<Reservation> reservations = java.util.Collections.emptyList();
            if (courtName != null) {
                Delayer.randomDelay(); // Simulate a slow service
                reservations = reservationService.query(courtName);
            }
            model.addAttribute("reservations", reservations);
            result.setResult("reservationQuery");
        });
        return result;
    }
}

The method now returns a DeferredResult<String>, which is still the name of the view to render. The actual result is set through a Runnable, which is passed to the execute method of the injected TaskExecutor. The main difference between returning a DeferredResult and a Callable is that for a DeferredResult you have to create your own Thread (or delegate it to a TaskExecutor); for a Callable, that isn’t needed.

Use a CompletableFuture

Change the signature of the method to return a CompletableFuture<String> and use the TaskExecutor to async execute the code.

package com.apress.springrecipes.court.web;

import com.apress.springrecipes.court.Delayer;
import com.apress.springrecipes.court.domain.Reservation;
import com.apress.springrecipes.court.service.ReservationService;


import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;


import java.util.List;
import java.util.concurrent.CompletableFuture;


@Controller
@RequestMapping("/reservationQuery")
public class ReservationQueryController {


    private final ReservationService reservationService;
    private final TaskExecutor taskExecutor;


    public ReservationQueryController(ReservationService reservationService, TaskExecutor taskExecutor) {
        this.reservationService = reservationService;
        this.taskExecutor = taskExecutor;
    }


    @GetMapping
    public void setupForm() {}


    @PostMapping
    public CompletableFuture<String> sumbitForm(@RequestParam("courtName") String courtName, Model model) {


        return CompletableFuture.supplyAsync(() -> {
            List<Reservation> reservations = java.util.Collections.emptyList();
            if (courtName != null) {
                Delayer.randomDelay(); // Simulate a slow service
                reservations = reservationService.query(courtName);
            }
            model.addAttribute("reservations", reservations);
            return "reservationQuery";
        }, taskExecutor);
    }
}

When calling supplyAsync (or when using void; you could use runAsync), you submit a task and get back a CompletableFuture. Here you use the supplyAsync method, which takes both a Supplier and an Executor so that you can reuse the TaskExecutor for async processing. If you use the supplyAsync method, which takes only a Supplier, it will be executed using the default fork/join pool available on the JVM.

When returning a CompletableFuture, you can take advantage of all the features of it, such as composing and chaining multiple CompletableFuture instances.

Use a ListenableFuture

Spring provides the ListenableFuture interface , which is a Future implementation that will do a callback when the Future has completed. To create a ListenableFuture, you would need to submit a task to an AsyncListenableTaskExecutor, which will return a ListenableFuture. The previously configured ThreadPoolTaskExecutor is an implementation of the AsyncListenableTaskExecutor interface.

// FINAL                    
package com.apress.springrecipes.court.web;


import com.apress.springrecipes.court.Delayer;
import com.apress.springrecipes.court.domain.Reservation;
import com.apress.springrecipes.court.service.ReservationService;
import org.springframework.core.task.AsyncListenableTaskExecutor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;


import java.util.List;

@Controller
@RequestMapping("/reservationQuery")
public class ReservationQueryController {


    private final ReservationService reservationService;
    private final AsyncListenableTaskExecutor taskExecutor;


    public ReservationQueryController(ReservationService reservationService, AsyncListenableTaskExecutor taskExecutor) {
        this.reservationService = reservationService;
        this.taskExecutor = taskExecutor;
    }

    @GetMapping
    public void setupForm() {}


    @PostMapping
    public ListenableFuture<String> sumbitForm(@RequestParam("courtName") String courtName, Model model) {


        return taskExecutor.submitListenable(() -> {

            List<Reservation> reservations = java.util.Collections.emptyList();
            if (courtName != null) {
                Delayer.randomDelay(); // Simulate a slow service
                reservations = reservationService.query(courtName);
            }
            model.addAttribute("reservations", reservations);
            return "reservationQuery";
        });
    }
}

You submit a task to the taskExecutor using the submitListenable method; this returns a ListenableFuture, which in turn can be used as the result for the method.

You might wonder where the success and failure callbacks are for the created ListenableFuture. Spring MVC will adapt the ListenableFuture to a DeferredResult and upon successful completion will call DeferredResult.setResult and, when an error happens, DeferredResult.setErrorResult. This is all handled for you with one of the HandlerMethodReturnValueHandler implementations shipped with Spring; in this case, it is handled by DeferredResultMethodReturnValueHandler.

5-2. Use Response Writers

Problem

You have a service, or multiple calls, and want to send the response in chunks to the client.

Solution

Use a ResponseBodyEmitter (or its sibling SseEmitter) to send the response in chunks.

How It Works

Spring supports writing objects as plain objects using the HttpMessageConverter infrastructure, the result will be a chunked (or streaming) list to the client. Instead of objects you could also send them as events, so called Server-Sent Events.

Send Multiple Results in a Response

Spring MVC has class named ResponseBodyEmitter that is particularly useful if, instead of a single result (like a view name or ModelAndView), you want to return multiple objects to the client. When sending an object, the object is converted to a result using an HttpMessageConverter (see also recipe 4-2). To use the ResponseBodyEmitter, you have to return it from the request-handling method.

Modify the find method of the ReservationQueryController to return a ResponseBodyEmitter and send the results one by one to the client.

// FINAL                    
package com.apress.springrecipes.court.web;


import com.apress.springrecipes.court.Delayer;
import com.apress.springrecipes.court.domain.Reservation;
import com.apress.springrecipes.court.service.ReservationService;
import org.springframework.core.task.TaskExecutor;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;


import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;


@Controller
@RequestMapping("/reservationQuery")
public class ReservationQueryController {


    private final ReservationService reservationService;
    private final TaskExecutor taskExecutor;


    ...

    @GetMapping(params = "courtName")
    public ResponseBodyEmitter find(@RequestParam("courtName") String courtName) {
        final ResponseBodyEmitter emitter = new ResponseBodyEmitter();
        taskExecutor.execute(() -> {
            Collection<Reservation> reservations = reservationService.query(courtName);
            try {
                for (Reservation reservation : reservations) {
                    emitter.send(reservation);
                }
                emitter.complete();
            } catch (IOException e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }
}

First, a ResponseBodyEmitter is created and in the end returned from this method. Next, a task is executed that will query the reservations using the ReservationService.query method. All the results from that call are returned one by one using the send method of the ResponseBodyEmitter. When all the objects have been sent, the complete() method needs to be called so that the thread responsible for sending the response can complete the request and be freed up for the next response to handle. When an exception occurs and you want to inform the user of this, you call the completeWithError. The exception will pass through the normal exception handling of Spring MVC (see also recipe 3-8), and after that the response is completed.

When using a tool like httpie or curl, calling the URL http://localhost:8080/court/reservationQuery courtName=='Tennis #1' will yield something like Figure 5-1. The result will be chunked and have a status of 200 (OK).

A314861_4_En_5_Fig1_HTML.jpg
Figure 5-1. Chunked result

If you want to change the status code or add custom headers, you could also wrap the ResponseBodyEmitter in a ResponseEntity, which would allow for the customization of the return code, headers, and so on (see recipe 4-1).

@GetMapping(params = "courtName")
public ResponseEntity<ResponseBodyEmitter> find(@RequestParam("courtName") String courtName) {
    final ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    ....
    return ResponseEntity.status(HttpStatus.I_AM_A_TEAPOT)
        .header("Custom-Header", "Custom-Value")
        .body(emitter);
}

Now the status code will be changed to 418, and it will contain a custom header (see Figure 5-2).

A314861_4_En_5_Fig2_HTML.jpg
Figure 5-2. Modified chunked result

Send Multiple Results as Events

A sibling of the ResponseBodyEmitter is the SseEmitter, which can deliver events from the server to the client using server-sent events. Server-sent events are messages from the server side to the client, and they have a content type header of text/event-stream. They are quite lightweight and allow for four fields to be defined (see Table 5-2).

Table 5-2. Allowed Fields for Server-Sent Events

Field

Description

id

The ID of the event

event

The type of event

data

The event data

retry

Reconnection time for the event stream

To send events from a request-handling method, you need to create an instance of SseEmitter and return it from the request-handling method. Then you can use the send method to send individual elements to the client.

@GetMapping(params = "courtName")
public SseEmitter find(@RequestParam("courtName") String courtName) {
    final SseEmitter emitter = new SseEmitter();
    taskExecutor.execute(() -> {
        Collection<Reservation> reservations = reservationService.query(courtName);
        try {
            for (Reservation reservation : reservations) {
                Delayer.delay(125);
                emitter.send(reservation);
            }
            emitter.complete();
        } catch (IOException e) {
            emitter.completeWithError(e);
        }
    });
    return emitter;
}
Note

Here there is a delay in sending each item to the client, just so you can see the different events coming in. You wouldn’t do this in real code.

Now when using something like curl to call the URL http://localhost:8080/court/reservationQuery courtName=='Tennis #1', you will see events coming in one by one (Figure 5-3).

A314861_4_En_5_Fig3_HTML.jpg
Figure 5-3. Result of server-sent events

Note that the Content-Type header has a value of text/event-stream to indicate that you get a stream of events. You could keep the stream open and keep receiving event notifications. You will also notice that each object written is converted to JSON; this is done with an HttpMessageConverter just like with a plain ResponseBodyEmitter. Each object is written in the data tag as the event data.

If you want to add more information to the event (in other words, fill in one of the other fields mentioned in Table 5-2), you can use the SseEventBuilder. To get an instance of that, you can call the event() factory method on the SseEmitter. Let’s use it to fill in the id field with the hash code of the Reservation.

@GetMapping(params = "courtName")
public SseEmitter find(@RequestParam("courtName") String courtName) {
    final SseEmitter emitter = new SseEmitter();
    taskExecutor.execute(() -> {
        Collection<Reservation> reservations = reservationService.query(courtName);
        try {
            for (Reservation reservation : reservations) {
                Delayer.delay(120);
                emitter.send(emitter.event().id(String.valueOf(reservation.hashCode())).data(reservation));
            }
            emitter.complete();
        } catch (IOException e) {
            emitter.completeWithError(e);
        }
    });
    return emitter;
}

Now when using something like curl to call the URL http://localhost:8080/court/reservationQuery courtName=='Tennis #1', you will see events coming in one by one, and they will contain both id and data fields.

5-3. Use Asynchronous Interceptors

Problem

Servlet filters defined by the Servlet API can pre-handle and post-handle every web request before and after it’s handled by a servlet. You want to configure something with similar functions as filters in Spring’s web application context to take advantage of the container features.

Moreover, sometimes you may want to pre-handle and post-handle web requests that are handled by Spring MVC handlers and manipulate the model attributes returned by these handlers before they are passed to the views.

Solution

Spring MVC allows you to intercept web requests for pre-handling and post-handling through handler interceptors. Handler interceptors are configured in Spring’s web application context, so they can make use of any container features and refer to any beans declared in the container. A handler interceptor can be registered for particular URL mappings so that it only intercepts requests mapped to certain URLs.

As described in recipe 3-3, Spring provides the HandlerInterceptor interface, which contains three callback methods for you to implement: preHandle(), postHandle(), and afterCompletion(). The first and second methods are called before and after a request is handled by a handler. The second method also allows you to get access to the returned ModelAndView object so you can manipulate the model attributes in it. The last method is called after the completion of all request processing (i.e., after the view has been rendered).

For async processing, Spring provides the AsyncHandlerInterceptor, which contains an additional callback method for you to implement afterConcurrentHandlingStarted. This method is called as soon as the async handling starts, instead of calling postHandle and/or afterCompletion. When the async processing is done, the normal flow is called again.

How It Works

In recipe 3-3 you created a MeasurementInterceptor that measured each web request’s handling time by each request handler and added it to the ModelAndView. Let’s modify it to log the handling time for the request and response, including the thread that was used to handle the request.

package com.apress.springrecipes.court.web;

import org.springframework.web.servlet.AsyncHandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class MeasurementInterceptor implements AsyncHandlerInterceptor {

    public static final String START_TIME = "startTime";

    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (request.getAttribute(START_TIME) == null) {
            request.setAttribute(START_TIME, System.currentTimeMillis());
        }
        return true;
    }


    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        long startTime = (Long) request.getAttribute(START_TIME);
        request.removeAttribute(START_TIME);
        long endTime = System.currentTimeMillis();
        System.out.println("Response-Processing-Time: " + (endTime - startTime) + "ms.");
        System.out.println("Response-Processing-Thread: " + Thread.currentThread().getName());
    }


    @Override
    public void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {


        long startTime = (Long) request.getAttribute(START_TIME);
        request.setAttribute(START_TIME, System.currentTimeMillis());
        long endTime = System.currentTimeMillis();


        System.out.println("Request-Processing-Time: " + (endTime - startTime) + "ms.");
        System.out.println("Request-Processing-Thread: " + Thread.currentThread().getName());
     }
}

In the preHandle() method of this interceptor , you record the start time and save it to a request attribute. This method should return true, allowing DispatcherServlet to proceed with request handling. Otherwise, DispatcherServlet assumes that this method has already handled the request, so DispatcherServlet returns the response to the user directly. Next, in afterConcurrentHandlingStarted, you get the time registered and calculate the time it took to start async processing. After that, you reset the start time and print the request-processing time and thread to the console.

Then, in the postHandle() method, you load the start time from the request attribute and compare it with the current time. You then calculate the total duration and print that, together with the current thread name to the console.

To register an interceptor, you need to modify the AsyncConfiguration, which was created in the first recipe. You need to have it implement WebMvcConfigurer and override the addInterceptors method. The method gives you access to InterceptorRegistry, which you can use to add interceptors. The modified class looks like this:

package com.apress.springrecipes.court.config;

import com.apress.springrecipes.court.web.MeasurementInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;


import java.util.concurrent.TimeUnit;

@Configuration
public class AsyncConfiguration implements WebMvcConfigurer {


    ...

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MeasurementInterceptor());
    }
}

Now when running the application and when a request is handled, the logging looks something like Figure 5-4.

A314861_4_En_5_Fig4_HTML.jpg
Figure 5-4. Request/response processing times
Note

Server-sent events aren’t supported on Microsoft browsers (Internet Explorer or Edge). To make them work with a Microsoft browser, you would have to use a polyfill to add the support.

5-4. Use WebSockets

Problem

You want to use bidirectional communication from the client to the server over the Web.

Solution

Use WebSockets to communicate from the client to the server, and vice versa. The WebSocket technology provides a full-duplex communication, unlike HTTP.

How It Works

A full explanation of the WebSocket technology goes beyond the scope of this recipe; one thing worth mentioning, though, is that the relation between HTTP and the WebSocket technology is actually quite thin. The only usage of HTTP for the WebSocket technology is that the initial handshake uses HTTP. This upgrades the connection from plain HTTP to a TCP socket connection.

Configure WebSocket Support

Enabling the use of the WebSocket technology is just a matter of adding @EnableWebSocket to a configuration class.

@Configuration
@EnableWebSocket
public class WebSocketConfiguration {}

For further configuration of the WebSocket engine, you can add a ServletServerContainerFactoryBean object to configure things such as buffer size, timeouts, and so on.

@Bean
public ServletServerContainerFactoryBean configureWebSocketContainer() {
    ServletServerContainerFactoryBean factory = new ServletServerContainerFactoryBean();
    factory.setMaxBinaryMessageBufferSize(16384);
    factory.setMaxTextMessageBufferSize(16384);
    factory.setMaxSessionIdleTimeout(TimeUnit.MINUTES.convert(30, TimeUnit.MILLISECONDS));
    factory.setAsyncSendTimeout(TimeUnit.SECONDS.convert(5, TimeUnit.MILLISECONDS));
    return factory;
}

This will configure the text and binary buffer size to 16KB, set asyncSendTimeout to 5 seconds, and set the session timeout to 30 minutes.

Create a WebSocketHandler

To handle WebSocket messages and life-cycle events (handshake, connection established, etc.), you need to create a WebSocketHandler and register it to an endpoint URL.

WebSocketHandler defines five methods that you need to implement (see Table 5-3) if you want to implement this interface directly. However, Spring already provides a nice class hierarchy that you can use to your advantage. When writing your own custom handlers, it is often enough to extend one of TextWebSocketHandler or BinaryWebSocketHandler, which, as their names imply, can handle either text or binary messages.

Table 5-3. WebSocketHandler Methods

Method

Description

afterConnectionEstablished

Invoked when the WebSocket connection is open and ready for use.

handleMessage

Called when a WebSocket message arrives for this handler.

handleTransportError

Called when an error occurs.

afterConnectionClosed

Invoked after the WebSocket connection has been closed.

supportsPartialMessages

Invoked if this handler supports partial messages. If set to true, WebSocket messages can arrive over multiple calls.

Create EchoHandler by extending TextWebSocketHandler and then implement the afterConnectionEstablished and handleMessage methods.

package com.apress.springrecipes.websocket;

import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;


public class EchoHandler extends TextWebSocketHandler {

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        session.sendMessage(new TextMessage("CONNECTION ESTABLISHED"));
    }


    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message)
        throws Exception {
        String msg = message.getPayload();
        session.sendMessage(new TextMessage("RECEIVED: " + msg));
    }
}

When a connection is established, a TextMessage will be sent back to the client telling it that the connection was established. When a TextMessage is received, the payload (the actual message) is extracted and prefixed with RECEIVED: and sent back to the client.

Next you need to register this handler with a URI. To do so, you can create an @Configuration class that implements WebSocketConfigurer and register it in the registerWebSocketHandlers method. Let’s add this interface to the WebSocketConfiguration class, as shown here:

package com.apress.springrecipes.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;


import java.util.concurrent.TimeUnit;

@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {


    @Bean
    public EchoHandler echoHandler() {
        return new EchoHandler();
    }


    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(echoHandler(), "/echo");
    }
}

First you register the EchoHandler as a bean so that it can be used to attach it to a URI. In the registerWebSocketHandlers, you can use the WebSocketHandlerRegistry to register the handler. Using the addHandler method, you can register the handler to a URI, in this case /echo. With this configuration, you could use the ws://localhost:8080/echo-ws/echo URL to open a WebSocket connection from the client.

Now that the server is ready, you need a client to connect to your WebSocket endpoint. For this you will need some JavaScript and HTML. Write the following app.js:

var ws = null;
var url = "ws://localhost:8080/echo-ws/echo";


function setConnected(connected) {
    document.getElementById('connect').disabled = connected;
    document.getElementById('disconnect').disabled = !connected;
    document.getElementById('echo').disabled = !connected;
}


function connect() {
    ws = new WebSocket(url);


    ws.onopen = function () {
        setConnected(true);
    };


    ws.onmessage = function (event) {
        log(event.data);
    };


    ws.onclose = function (event) {
        setConnected(false);
        log('Info: Closing Connection.');
    };
}


function disconnect() {
    if (ws != null) {
        ws.close();
        ws = null;
    }
    setConnected(false);
}
function echo() {
    if (ws != null) {
        var message = document.getElementById('message').value;
        log('Sent: ' + message);
        ws.send(message);
    } else {
        alert('connection not established, please connect.');
    }
}


function log(message) {
    var console = document.getElementById('logging');
    var p = document.createElement('p');
    p.appendChild(document.createTextNode(message));
    console.appendChild(p);
    while (console.childNodes.length > 12) {
        console.removeChild(console.firstChild);
    }
    console.scrollTop = console.scrollHeight;
}

There are a few functions here. The first connect will be invoked when clicking the Connect button. This will open a WebSocket connection to ws://localhost:8080/echo-ws/echo, which is the URL to the handler created and registered earlier. Connecting to the server will create a WebSocket JavaScript object, which gives you the ability to listen to messages and events on the client. Here the onopen, onmessage, and onclose callbacks are defined. The most important is the onmessage callback because that will be invoked whenever a message comes in from the server; this method simply calls the log function, which will add the received message to the logging element on the screen.

Next there is disconnect, which will close the WebSocket connection and clean up the JavaScript objects. Finally, there is the echo function, which will be invoked whenever the Echo Message button is clicked. The given message will be sent to the server (and eventually will be returned).

To use app.js, add the index.html file shown here:

<!DOCTYPE html>
<html>
<head>
    <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css" />
    <script type="text/javascript" src="app.js"></script>
</head>
<body>
<div>
    <div id="connect-container" class="ui centered grid">
        <div class="row">
            <button id="connect" onclick="connect();" class="ui green button ">Connect</button>
            <button id="disconnect" disabled="disabled" onclick="disconnect();" class="ui red button">Disconnect</button>
        </div>
        <div class="row">
            <textarea id="message" style="width: 350px" class="ui input" placeholder="Message to Echo"></textarea>
        </div>
        <div class="row">
            <button id="echo" onclick="echo();" disabled="disabled" class="ui button">Echo message</button>
        </div>
    </div>
    <div id="console-container">
        <h3>Logging</h3>
        <div id="logging"></div>
    </div>
</div>
</body>
</html>

Now when deploying the application , you can connect to the echo WebSocket service and send some messages and have them sent back (see Figure 5-5).

A314861_4_En_5_Fig5_HTML.jpg
Figure 5-5. WebSocket client output

Use STOMP and MessageMapping

When using the WebSocket technology to create an application, that more or less implies messaging. Although you can use the WebSocket protocol as is, the protocol also allows you to use subprotocols. One of those protocols, supported by Spring WebSocket, is STOMP.

STOMP is a simple text-oriented protocol that was created for scripting languages like Ruby and Python to connect to message brokers. STOMP can be used over any reliable bidirectional network protocol like TCP and also WebSocket. The protocol itself is text-oriented, but the payload of the messages isn’t strictly bound to this; it can also contain binary data.

When configuring and using STOMP with Spring WebSocket support, the WebSocket application acts as a broker for all connected clients. The broker can be an in-memory broker or an actual full-blown enterprise solution that supports the STOMP protocol (like RabbitMQ or ActiveMQ). In the latter case, the Spring WebSocket application will act as a relay for the actual broker. To add messaging over the WebSocket protocol, Spring uses Spring Messaging (see Chapter 14 for more recipes on messaging).

To be able to receive messages, you need to mark a method in an @Controller with @ MessageMapping and tell it from which destination it will receive messages. Let’s modify the EchoHandler to work with annotations.

package com.apress.springrecipes.websocket;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;


@Controller
public class EchoHandler {


    @MessageMapping("/echo")
    @SendTo("/topic/echo")
    public String echo(String msg) {
        return "RECEIVED: " + msg;
    }
}

When a message is received on the /app/echo destination, it will be passed on to the @MessageMapping annotated method . Notice the @SendTo("/topic/echo") annotation on the method as well; this instructs Spring to put the result, a String, on said topic.

Now you need to configure the message broker and add an endpoint for receiving messages. For this, add @EnableWebSocketMessageBroker to the WebSocketConfiguration and let it extend the AbstractWebSocketMessageBrokerConfigurer (which implements the WebSocketMessageBrokerConfigurer, which is used to do further configuration for WebSocket messaging).

package com.apress.springrecipes.websocket;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;


@Configuration
@EnableWebSocketMessageBroker
@ComponentScan
public class WebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {


    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic");
        registry.setApplicationDestinationPrefixes("/app");
    }


    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/echo-endpoint");
    }
}

The @EnableWebSocketMessageBroker annotation will enable the use of STOMP over WebSocket. The broker is configured in the configureMessageBroker method; here we are using the simple message broker. To connect to an enterprise broker, use registry.enableStompBrokerRelay to connect to the actual broker. To distinquish between messages handled by the broker versus by the app, there are different prefixes. Anything on a destination starting with /topic will be passed on to the broker, and anything on a destination starting with /app will be sent to a message handler (i.e., the @MessageMapping annotated method).

The final part is the registration of a WebSocket endpoint that listens to incoming STOMP messages; in this case, the endpoint is mapped to /echo-endpoint. This registration is done in the registerStompEndpoints method, which can be overridden.

Finally, you need to modify the client to use STOMP instead of plain WebSocket. The HTML can remain pretty much the same; you need an additional library to be able to work with STOMP in the browser. This recipe uses webstomp-client ( https://github.com/JSteunou/webstomp-client ), but there are different libraries that you can use.

<head>                    
    <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.2.10/semantic.min.css" />


    <script type="text/javascript" src="webstomp.js"></script>
    <script type="text/javascript" src="app.js"></script>


</head>

The biggest change is in the app.js file.

var ws = null;
var url = "ws://localhost:8080/echo-ws/echo-endpoint";


function setConnected(connected) {
    document.getElementById('connect').disabled = connected;
    document.getElementById('disconnect').disabled = !connected;
    document.getElementById('echo').disabled = !connected;
}


function connect() {
    ws = webstomp.client(url);
    ws.connect({}, function(frame) {
        setConnected(true);
        log(frame);
        ws.subscribe('/topic/echo', function(message){
            log(message.body);
        })
    });
}


function disconnect() {
    if (ws != null) {
        ws.disconnect();
        ws = null;
    }
    setConnected(false);
}
function echo() {
    if (ws != null) {
        var message = document.getElementById('message').value;
        log('Sent: ' + message);
        ws.send("/app/echo", message);
    } else {
        alert('connection not established, please connect.');
    }
}


function log(message) {
    var console = document.getElementById('logging');
    var p = document.createElement('p');
    p.appendChild(document.createTextNode(message));
    console.appendChild(p);
    while (console.childNodes.length > 12) {
        console.removeChild(console.firstChild);
    }
    console.scrollTop = console.scrollHeight;
}

The connect function now uses webstomp.client to create a STOMP client connection to your broker. When connected, the client will subscribe to /topic/echo and receive the messages put on the topic. The echo function has been modified to use the send method of the client to send the message to the /app/echo destination, which in turn will be passed on to the @ MessageMapping annotated method.

When starting the application and opening the client, you are still able to send and receive messages but now using the STOMP subprotocol. You could even connect multiple browsers, and each browser would see the messages on the /topic/echo destination as it acts like a topic.

When writing @ MessageMapping annotated methods, you can use a variety of method arguments and annotations (see Table 5-4) to receive more or less information about the message. By default, it is assumed that a single argument will map to the payload of the message, and a MessageConverter will be used to convert the message payload to the desired type. (See recipe 14-2 for converting messages.)

Table 5-4. Supported Method Arguments and Annotations

Type

Description

Message

The actual underlying message including the headers and body

@Payload

The payload of the message (default); arguments can also be annotated with @Validated to be validated

@Header

Gets the given header from Message

@Headers

Can be placed on a Map argument to get all Message headers

MessageHeaders

All the Message headers

Principal

The current user, if set

5-5. Develop a Reactive Application with Spring WebFlux

Problem

You want to develop a simple reactive web application with Spring WebFlux to learn the basic concepts and configurations of this framework.

Solution

The lowest component of Spring WebFlux is HttpHandler. This is an interface with a single handle method.

public interface HttpHandler {

    Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response);

}

The handle method returns Mono<Void>, which is the reactive way of saying it returns void. It takes both a ServerHttpRequest object and a ServerHttpResonse object from the org.springframework.http.server.reactive package. These are again interfaces, and depending on the container used for running an instance of the interface is created. For this, several adapters or bridges for containers exist. When running on a Servlet 3.1 container (supporting nonblocking I/O), ServletHttpHandlerAdapter (or one of its subclasses) is used to adapt from the plain servlet world to the reactive world. When running on a native reactive engine like Netty, ReactorHttpHandlerAdapter is used.

When a web request is sent to a Spring WebFlux application, HandlerAdapter first receives the request. Then it organizes the different components configured in Spring’s application context that are needed to handle the request.

To define a controller class in Spring WebFlux, a class has to be marked with the @Controller or @RestController annotation (just like with Spring MVC; see Chapters 3 and 4).

When an @Controller annotated class (i.e., a controller class) receives a request, it looks for an appropriate handler method to handle the request. This requires that a controller class map each request to a handler method by one or more handler mappings. To do so, a controller class’s methods are decorated with the @RequestMapping annotation, making them handler methods.

The signature for these handler methods—as you can expect from any standard class—is open ended. You can specify an arbitrary name for a handler method and define a variety of method arguments. Equally, a handler method can return any of a series of values (e.g., String or void), depending on the application logic it fulfills. The following is only a partial list of valid argument types, just to give you an idea.

  • ServerHttpRequest or ServerHttpResponse

  • Request parameters from the URL of arbitrary type, annotated with @RequestParam

  • Model attributes of arbitrary type, annotated with @ModelAttribute

  • Cookie values included in an incoming request, annotated with @CookieValue

  • Request header values of arbitraty type, annotated with @RequestHeader

  • Request attribute of arbitrary type, annotated with @RequestAttribute

  • Map or ModelMap, for the handler method to add attributes to the model

  • Errors or BindingResult, for the handler method to access the binding and validation result for the command object

  • WebSession, for the session

Once the controller class has picked an appropriate handler method, it invokes the handler method’s logic with the request. Usually, a controller’s logic invokes back-end services to handle the request. In addition, a handler method’s logic is likely to add or remove information from the numerous input arguments (e.g., ServerHttpRequest, Map, or Errors) that will form part of the ongoing flow.

After a handler method has finished processing the request, it delegates control to a view, which is represented as the handler method’s return value. To provide a flexible approach, a handler method’s return value doesn’t represent a view’s implementation (e.g., user.html or report.pdf) but rather a logical view (e.g., user or report)—note the lack of file extension.

A handler method’s return value can be either a String, representing a logical view name, or void, in which case a default logical view name is determined on the basis of a handler method’s or controller’s name.

To pass information from a controller to a view, it’s irrelevant that a handler’s method returns a logical view name—String or a void—since the handler method input arguments will be available to a view.

For example, if a handler method takes Map and Model objects as input parameters—modifying their contents inside the handler method’s logic—these same objects will be accessible to the view returned by the handler method.

When the controller class receives a view, it resolves the logical view name into a specific view implementation (e.g., user.html or report.fmt) by means of a view resolver. A view resolver is a bean configured in the web application context that implements the ViewResolver interface. Its responsibility is to return a specific view implementation for a logical view name.

Once the controller class has resolved a view name into a view implementation, per the view implementation’s design, it renders the objects (e.g., ServerHttpRequest, Map, Errors, or WebSession) passed by the controller’s handler method. The view’s responsibility is to display the objects added in the handler method’s logic to the user.

How It Works

Let’s write a reactive version of the course reservation system mentioned in Chapter 3. First you write the following domain classes, which are regular classes (nothing reactive so far):

package com.apress.springrecipes.reactive.court;

public class Reservation {

    private String courtName;

    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
    private LocalDate date;
    private int hour;
    private Player player;
    private SportType sportType;


    // Constructors, Getters and Setters
    ...
}


package com.apress.springrecipes.court.domain;

public class Player {

    private String name;
    private String phone;


    // Constructors, Getters and Setters
    ...
}


package com.apress.springrecipes.court.domain;

public class SportType {

    private int id;
    private String name;


    // Constructors, Getters and Setters
    ...
}

Then you define the following service interface to provide reservation services to the presentation layer:

package com.apress.springrecipes.reactive.court;

import reactor.core.publisher.Flux;

public interface ReservationService {

    Flux<Reservation> query(String courtName);
}

Notice the return type of the query method that returns a Flux<Reservation>, which means zero or more reservations.

In a production application, you should implement this interface with data store persistence and preferably one that has reactive support. But for simplicity’s sake, you can store the reservation records in a list and hard-code several reservations for testing purposes.

package com.apress.springrecipes.reactive.court;

import reactor.core.publisher.Flux;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;


public class InMemoryReservationService implements ReservationService {
    public static final SportType TENNIS = new SportType(1, "Tennis");
    public static final SportType SOCCER = new SportType(2, "Soccer");


    private final List<Reservation> reservations = new ArrayList<>();

    public InMemoryReservationService() {

        reservations.add(new Reservation("Tennis #1", LocalDate.of(2008, 1, 14), 16,
            new Player("Roger", "N/A"), TENNIS));
        reservations.add(new Reservation("Tennis #2", LocalDate.of(2008, 1, 14), 20,
            new Player("James", "N/A"), TENNIS));
    }


    @Override
    public Flux<Reservation> query(String courtName) {
        return Flux.fromIterable(reservations)
            .filter(r -> Objects.equals(r.getCourtName(), courtName));
    }
}

The query method returns a Flux based on the embedded list of Reservations, and the Flux will filter the reservations that don’t match.

Set Up a Spring WebFlux Application

To be able to handle request in a reactive way, you need to enable WebFlux. This is done by adding @ EnableWebFlux to an @Configuration class (much like @EnableWebMvc for normal request processing).

package com.apress.springrecipes.websocket;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;


@Configuration
@EnableWebFlux
@ComponentScan
public class WebFluxConfiguration implements WebFluxConfigurer { ... }

The @EnableWebFlux annotation is what is turning on reactive processing. To do more WebFlux configuration, you can implement WebFluxConfigurer and add additional converters and so on.

Bootstrap the Application

Just as with a regular Spring MVC application , you need to bootstrap the application. How to bootstrap the application depends a little on the runtime you choose to run on. For all supported containers (see Table 5-5), there are different handler adapters so that the runtime can work with the HttpHandler abstraction for Spring WebFlux.

Table 5-5. Supported Runtimes and HandlerAdapter

Runtime

Adapter

Servlet 3.1 container

ServletHttpHandlerAdapter

Tomcat

ServletHttpHandlerAdapter or TomcatHttpHandlerAdapter

Jetty

ServletHttpHandlerAdapter or JettyHttpHandlerAdapter

Reactor Netty

ReactorHttpHandlerAdapter

RxNetty

RxNettyHttpHandlerAdapter

Undertow

UndertowHttpHandlerAdapter

Before adapting to the runtime , you would need to bootstrap the application using AnnotationConfigApplicationContext and use that to configure a HttpHandler. You can create it using the WebHttpHandlerBuilder.applicationContext factory method. It will create a HttpHandler and configure it using the passed in ApplicationContext.

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(WebFluxConfiguration.class);
HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build();

Next you would adapt HttpHandler to the runtime.

For Reactor Netty, it would be something like this:

ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(handler);
HttpServer.create(host, port).newHandler(adapter).block();

First you create a ReactorHttpHandlerAdapter component, which is the component that knows how to adapt from the Reactor Netty handling to the internal HttpHandler. Next you register this adapter as a handler to the newly created Reactor Netty server.

When deploying an application to a servlet container, you can create a class implementing WebApplicationInitializer and do the setup manually.

public class WebFluxInitializer implements WebApplicationInitializer {

    public void onStartup(ServletContext servletContext) throws ServletException {}
        AnnotationConfigApplicationContext context =
            new AnnotationConfigApplicationContext(WebFluxConfiguration.class);
        HttpHandler handler = WebHttpHandlerBuilder.applicationContext(context).build();
        ServletHttpHandlerAdapter handlerAdapter = new ServletHttpHandlerAdapter(httpHandler)
        ServletRegistration.Dynamic registration = servletContext.addServlet("dispatcher-handler", handlerAdapter);
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
        registration.setAsyncSupported(true);
    }
}

First you create an AnnotationConfigApplicationContext because you want to use annotations for configuration and pass that your WebFluxConfiguration class. Next you need an HttpHandler to handle and dispatch the request. This HttpHandler needs to be registered to the servlet container you are using as a servlet. For this, you wrap it in a ServletHttpHandlerAdapter. To be able to do reactive processing, asyncSupported needs to be true.

To make this configuration easier, Spring WebFlux provides a few convenience implementations for you to extend. In this case, you can use AbstractAnnotationConfigDispatcherHandlerInitializer as a base class. The configuration now looks like this:

package com.apress.springrecipes.websocket;

import org.springframework.web.reactive.support.AbstractAnnotationConfigDispatcherHandlerInitializer;

public class WebFluxInitializer extends AbstractAnnotationConfigDispatcherHandlerInitializer {

    @Override
    protected Class<?>[] getConfigClasses() {
        return new Class<?>[] {WebFluxConfiguration.class};
    }
}

The only thing required is the getConfigClasses method; all the moving parts are now handled by the base configuration provided by Spring WebFlux.

Now you are ready to run your application on a regular servlet container.

Create Spring WebFlux Controllers

An annotation-based controller class can be an arbitrary class that doesn’t implement a particular interface or extend a particular base class. You can annotate it with the @Controller or @RestController annotation. There can be one or more handler methods defined in a controller to handle single or multiple actions. The signature of the handler methods is flexible enough to accept a range of arguments. (See also recipe 3-2 for more information on request mapping.)

The @RequestMapping annotation can be applied to the class level or the method level. The first mapping strategy is to map a particular URL pattern to a controller class and then a particular HTTP method to each handler method.

package com.apress.springrecipes.reactive.court.web;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import reactor.core.publisher.Mono;


import java.time.LocalDate;

@Controller
@RequestMapping("/welcome")
public class WelcomeController {


    @GetMapping
    public String welcome(Model model) {
        model.addAttribute("today", Mono.just(LocalDate.now()));
        return "welcome";
    }


}

This controller creates a java.util.Date object to retrieve the current date and then adds it to the input Model as an attribute so the target view can display it. Although this controller looks like a regular controller, the main difference is the way things are added to the model. Instead of directly adding it to the model, the current date will eventually appear in the model, due to the use of Mono.just(...).

Since you’ve already activated annotation scanning on the com.apress.springrecipes.reactive.court package, the annotations for the controller class are detected upon deployment.

The @Controller annotation defines the class as a controller. The @RequestMapping annotation is more interesting since it contains properties and can be declared at the class or handler method level. The first value in this class— ("/welcome")—is used to specify the URL on which the controller is actionable, meaning any request received on the /welcome URL is attended by the WelcomeController class.

Once a request is attended by the controller class, it delegates the call to the default HTTP GET handler method declared in the controller. The reason for this behavior is that every initial request made on a URL is of the HTTP GET kind. So when the controller attends to a request on the /welcome URL, it subsequently delegates to the default HTTP GET handler method for processing.

The annotation @GetMapping is used to decorate the welcome method as the controller’s default HTTP GET handler method. It’s worth mentioning that if no default HTTP GET handler method is declared, a ServletException is thrown. That’s why it’s important that a Spring MVC controller have at a minimum a URL route and default HTTP GET handler method.

Another variation to this approach can be declaring both values—URL route and default HTTP GET handler method—in the @GetMapping annotation used at the method level. This declaration is illustrated next:

@Controller
public class WelcomeController {


    @GetMapping("/welcome")
    public String welcome(Model model) { ... }


}

This last controller illustrates the basic principles of Spring MVC. However, a typical controller may invoke back-end services for business processing. For example, you can create a controller for querying reservations of a particular court as follows:

package com.apress.springrecipes.reactive.court.web;

import com.apress.springrecipes.reactive.court.Reservation;
import com.apress.springrecipes.reactive.court.ReservationService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;


@Controller
@RequestMapping("/reservationQuery")
public class ReservationQueryController {


    private final ReservationService reservationService;

    public ReservationQueryController(ReservationService reservationService) {
        this.reservationService = reservationService;
    }

    @GetMapping
    public void setupForm() {
    }


    @PostMapping
    public String sumbitForm(ServerWebExchange serverWebExchange, Model model) {


        Flux<Reservation> reservations =             serverWebExchange.getFormData()
                .map(form -> form.get("courtName"))
                .flatMapMany(Flux::fromIterable)
                .concatMap(courtName -> reservationService.query(courtName));
        model.addAttribute("reservations", reservations);
        return "reservationQuery";
    }
}

As outlined earlier, the controller then looks for a default HTTP GET handler method. Since the public void setupForm() method is assigned the necessary @GetMapping annotation for this purpose, it’s called next.

Unlike the previous default HTTP GET handler method, notice that this method has no input parameters, has no logic, and has a void return value. This means two things. By having no input parameters and no logic, a view only displays data hard-coded in the implementation template (e.g., JSP) since no data is being added by the controller. By having a void return value, a default view name based on the request URL is used; therefore, since the requesting URL is /reservationQuery, a view named reservationQuery is assumed.

The remaining handler method is decorated with the @PostMapping annotation. At first sight, having two handler methods with only the class-level /reservationQuery URL statement can be confusing, but it’s really simple. One method is invoked when HTTP GET requests are made on the /reservationQuery URL; the other is invoked when HTTP POST requests are made on the same URL.

The majority of requests in web applications are of the HTTP GET kind, whereas requests of the HTTP POST kind are generally made when a user submits an HTML form. So, revealing more of the application’s view (which we will describe shortly), one method is called when the HTML form is initially loaded (i.e., HTTP GET), whereas the other is called when the HTML form is submitted (i.e., HTTP POST).

Looking closer at the HTTP POST default handler method, notice the two input parameters. First is the ServerWebExchange declaration, used to extract a request parameter named courtName. In this case, the HTTP POST request comes in the form /reservationQuery?courtName=<value>. This declaration makes said value available in the method under the variable named courtName. Second is the Model declaration, used to define an object in which to pass data onto the returning view. In a regular Spring MVC controller, you could have used @RequestParam("courtName") String courtName (see recipe 3-1) to obtain the parameter, but for Spring WebFlux that will not work for parameters passed as part of the form data; it will work only for parameters that are part of the URL. Hence, ServerWebExchange is needed to get the form data, obtain the parameter, and invoke the service.

The logic executed by the handler method consists of using the controller’s reservationService to perform a query using the courtName variable. The results obtained from this query are assigned to the Model object, which will later become available to the returning view for display.

Finally, note that the method returns a view named reservationQuery. This method could have also returned void, just like the default HTTP GET, and have been assigned to the same reservationQuery default view on account of the requesting URL. Both approaches are identical.

Now that you are aware of how Spring MVC controllers are constituted, it’s time to explore the views to which a controller’s handler methods delegate their results.

Create Thymeleaf Views

Spring WebFlux supports several types of views for different presentation technologies. These include HTML, XML, JSON, Atom and RSS feeds, JasperReports, and other third-party view implementations. Here you will use Thymeleaf to write a few simple HTML-based templates. For this you need to add some additional configuration to the WebFluxConfiguration class to set up Thymeleaf and to register a ViewResolver, which will return the view name, returned from the controller, into the actual resource to load.

Here’s the Thymeleaf configuration:

@Bean
public SpringResourceTemplateResolver thymeleafTemplateResolver() {


    final SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
    resolver.setPrefix("classpath:/templates/");
    resolver.setSuffix(".html");
    resolver.setTemplateMode(TemplateMode.HTML);
    return resolver;
}


@Bean
public ISpringWebFluxTemplateEngine thymeleafTemplateEngine(){


    final SpringWebFluxTemplateEngine templateEngine = new SpringWebFluxTemplateEngine();
    templateEngine.setTemplateResolver(thymeleafTemplateResolver());
    return templateEngine;
}

Thymeleaf uses a template engine to convert templates into actual HTML. Next you need to configure the ViewResolver, which knows how to work with Thymeleaf. ThymeleafReactiveViewResolver is the reactive implementation of ViewResolver. Finally, you need to make the WebFlux configuration aware of the new view resolver. This is done by overriding the configureViewResolvers method and adding it to ViewResolverRegistry.

@Bean
public ThymeleafReactiveViewResolver thymeleafReactiveViewResolver() {


    final ThymeleafReactiveViewResolver viewResolver = new ThymeleafReactiveViewResolver();
    viewResolver.setTemplateEngine(thymeleafTemplateEngine());
    viewResolver.setResponseMaxChunkSizeBytes(16384);
    return viewResolver;
}


@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
    registry.viewResolver(thymeleafReactiveViewResolver());
}

The templates are resolved through a template resolver; here SpringResourceTemplateResolver uses the Spring resource-loading mechanism to load the templates. The templates are going to be in the src/main/resources/templates directory. For instance, for the welcome view, the actual src/main/resources/templates/welcome.html file will be loaded and parsed by the template engine.

Let’s write the welcome.html template.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Welcome</title>
</head>
<body>
<h2>Welcome to Court Reservation System</h2>
Today is <strong th:text="${#temporals.format(today, 'dd-MM-yyyy')}">21-06-2017</strong>
</body>
</html>

In this template, you make use of the temporals object in EL to format the today model attribute into the pattern dd-MM-yyyy.

Next, you can create another JSP template for the reservation query controller and name it reservationQuery.html to match the view name.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Reservation Query</title>
</head>
<body>
<form method="post">
    Court Name
    <input type="text" name="courtName" value="${courtName}"/>
    <input type="submit" value="Query"/>
</form>


<table border="1">
    <thead>
    <tr>
        <th>Court Name</th>
        <th>Date</th>
        <th>Hour</th>
        <th>Player</th>
    </tr>
    </thead>
    <tbody>
    <tr th:each="reservation : ${reservations}">
        <td th:text="${reservation.courtName}">Court</td>
        <td th:text="${#temporals.format(reservation.date, 'dd-MM-yyyy')}">21-06-2017</td>
        <td th:text="${reservation.hour}">22</td>
        <td th:text="${reservation.player.name}">Player</td>
    </tr>
    </tbody>
</table>
</body>
</html>

In this template , you include a form for users to input the court name they want to query and then use the th:each tag to loop the reservations model attribute to generate the result table.

Run the Web Application

Depending on the runtime, either you can just run the application by executing the main method or you can build a WAR archive and deploy it to a servlet container. Here you will do the latter and use Apache Tomcat 8.5.x as the web container.

Tip

The project can also create a Docker container with the app. Run ../gradlew buildDocker to get a container with Tomcat and the application. You can then start a Docker container to test the application (docker run -p 8080:8080 spring-recipes-4th/court-rx/welcome).

5-6. Handle Forms with Reactive Controllers

Problem

In a web application, you often have to deal with forms. A form controller has to show a form to a user and also handle the form submission. Form handling can be a complex and variable task.

Solution

When a user interacts with a form, it requires support for two operations from a controller. First, when a form is initially requested, it asks the controller to show a form with an HTTP GET request, which renders the form view to the user. Then, when the form is submitted, an HTTP POST request is made to handle things such as validation and business processing for the data present in the form. If the form is handled successfully, it renders the success view to the user. Otherwise, it renders the form view again with errors.

How It Works

Suppose you want to allow a user to make a court reservation by filling out a form. To give you a better idea of the data handled by a controller, we will introduce the controller’s view (i.e., the form) first.

Create a Form’s Views

Let’s create the form view called reservationForm.html. The form relies on the Thymeleaf form tag library because this simplifies a form’s data binding, display of error messages, and the redisplay of original values entered by the user in the case of errors.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Reservation Form</title>
    <style>
        .error {
            color: #ff0000;
            font-weight: bold;
        }
    </style>
</head>


<body>
<form method="post" th:object="${reservation}">


</form>
<table>
    <tr>
        <td>Court Name</td>
        <td><input type="text" th:field="*{courtName}" required/></td>
        <td><span class="error" th:if="${#fields.hasErrors('courtName')}" th:errors="*{courtName}"></span></td>
    </tr>
    <tr>
        <td>Date</td>
        <td><input type="date" th:field="*{date}" required/></td>
        <td><span class="error" th:if="${#fields.hasErrors('date')}" th:errors="*{date}"></span></td>
    </tr>
    <tr>
        <td>Hour</td>
        <td><input type="number" min="8" max="22" th:field="*{hour}" /></td>
        <td><span class="error" th:if="${#fields.hasErrors('hour')}" th:errors="*{hour}"></span></td>
    </tr>
    <tr>
        <td colspan="3"><input type="submit" /></td>
    </tr>
</table>


</form>
</body>
</html>

This form uses Thymeleaf to bind all form fields to a model attribute named reservation because of the th:object=${reservation} tag on the form tag. Each field will bind (and display the value) of the actual field on the Reservation object. This is what the th:field tag is used for. When there are errors on the field, those are displayed through the use of the th:errors tags.

Finally, you see the standard HTML tag <input type="submit" /> that generates a Submit button and triggers the sending of data to the server.

If the form and its data are processed correctly, you need to create a success view to notify the user of a successful reservation. The reservationSuccess.html file illustrated next serves this purpose:

<html>                    
<head>
<title>Reservation Success</title>
</head>


<body>
Your reservation has been made successfully.
</body>
</html>

It’s also possible for errors to occur because of invalid values being submitted in a form. For example, if the date is not in a valid format or an alphabetic character is presented for the hour field, the controller is designed to reject such field values. The controller will then generate a list of selective error codes for each error to be returned to the form view; these values are placed in the th:errors tag.

For example, for an invalid value input in the date field, the following error codes are generated by the data binding:

typeMismatch.command.date
typeMismatch.date
typeMismatch.java.time.LocalDate
typeMismatch

If you have a ResourceBundleMessageSource object defined, you can include the following error messages in your resource bundle for the appropriate locale (e.g., messages.properties for the default locale); see also recipe 3-5 on how to externalize localization concerns:

typeMismatch.date=Invalid date format
typeMismatch.hour=Invalid hour format

The corresponding error codes and their values are what are returned to a user if a failure occurs when processing form data.

Now that you know the structure of the views involved with a form, as well as the data handled by it, let’s take a look at the logic that handles the submitted data (i.e., the reservation) in a form.

Create a Form’s Service Processing

This is not the controller but rather the service used by the controller to process the form’s data reservation. First define a make() method in the ReservationService interface.

package com.apress.springrecipes.court.service;
...
public interface ReservationService {
    ...
    Mono<Reservation> make(Mono<Reservation> reservation)
        throws ReservationNotAvailableException;
}

Then you implement this make() method by adding a Reservation item to the list that stores the reservations. You throw a ReservationNotAvailableException in the case of a duplicate reservation.

package com.apress.springrecipes.reactive.court;
...
public class InMemoryReservationService implements ReservationService {
    ...
    @Override
    public Mono<Reservation> make(Reservation reservation) {


        long cnt = reservations.stream()
            .filter(made -> Objects.equals(made.getCourtName(), reservation.getCourtName()))
            .filter(made -> Objects.equals(made.getDate(), reservation.getDate()))
            .filter(made -> made.getHour() == reservation.getHour())
            .count();


        if (cnt > 0) {
            return Mono.error(new ReservationNotAvailableException(reservation
                .getCourtName(), reservation.getDate(), reservation
                .getHour()));
        } else {
            reservations.add(reservation);
            return Mono.just(reservation);
        }
    }
}

Now that you have a better understanding of the two elements that interact with a controller—a form’s views and the reservation service class—let’s create a controller to handle the court reservation form.

Create a Form’s Controller

A controller used to handle forms makes use of practically the same annotations you’ve already used in the previous recipes. So, let’s get right to the code.

package com.apress.springrecipes.reactive.court.web;
...


@Controller
@RequestMapping("/reservationForm")
public class ReservationFormController {


    private final ReservationService reservationService;

    @Autowired
    public ReservationFormController(ReservationService reservationService) {
        this.reservationService = reservationService;
    }


    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(Model model) {
        Reservation reservation = new Reservation();
        model.addAttribute("reservation", reservation);
        return "reservationForm";
    }


    @RequestMapping(method = RequestMethod.POST)
    public String submitForm(
        @ModelAttribute("reservation") Reservation reservation,
        BindingResult result) {
            reservationService.make(reservation);
            return "redirect:reservationSuccess";
    }
}

The controller starts by using the standard @Controller annotation, as well as the @RequestMapping annotation that allows access to the controller through the following URL:

http://localhost:8080/court-rx/reservationForm

When you enter this URL in your browser, it will send an HTTP GET request to your web application. This in turn triggers the execution of the setupForm method, which is designated to attend to this type of request based on its @GetMapping annotation.

The setupForm method defines a Model object as an input parameter, which serves to send model data to the view (i.e., the form). Inside the handler method, an empty Reservation object is created that is added as an attribute to the controller’s Model object. Then the controller returns the execution flow to the reservationForm view, which in this case is resolved to reservationForm.jsp (i.e., the form).

The most important aspect of this last method is the addition of an empty Reservation object. If you analyze the form reservationForm.html, you will notice the form tag declares the th:object="${reservation}" attribute. This means that upon rendering the view, the form expects an object named reservation to be available, which is achieved by placing it inside the handler method’s Model. In fact, further inspection reveals that the th:field=*{expression} values for each input tag correspond to the field names belonging to the Reservation object. Since the form is being loaded for the first time, it should be evident that an empty Reservation object is expected.

Now turn your attention to submitting the form for the first time. After you have filled in the form fields, submitting the form triggers an HTTP POST request, which in turn invokes the submitForm method—on account of this method’s @PostMapping value.

The input fields declared for the submitForm method are the @ModelAttribute("reservation") Reservation reservation used to reference the reservation object and the BindingResult object that contains newly submitted data by the user.

At this juncture, the handler method doesn’t incorporate validation, which is the purpose of the BindingResult object.

The only operation performed by the handler method is reservationService.make(reservation);. This operation invokes the reservation service using the current state of the reservation object.

Generally, controller objects are first validated prior to performing this type of operation on them.

Finally, note the handler method returns a view named redirect:reservationSuccess. The actual name of the view in this case is reservationSuccess, which is resolved to the reservationSuccess.html page you created earlier.

The redirect: prefix in the view name is used to avoid a problem known as duplicate form submission.

When you refresh the web page in the form success view, the form you just submitted is resubmitted. To avoid this problem, you can apply the post/redirect/get design pattern, which recommends redirecting to another URL after a form submission is handled successfully, instead of returning an HTML page directly. This is the purpose of prefixing a view name with redirect:.

Initialize a Model Attribute Object and Prepopulate a Form with Values

The form is designed to let users make reservations. However, if you analyze the Reservation domain class, you will note the form is still missing two fields to create a complete reservation object. One of these fields is the player field, which corresponds to a Player object. Per the Player class definition, a Player object has both name and phone fields.

So, can the player field be incorporated into a form view and controller? Let’s analyze the form view first, shown here:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<form method="post" th:object="${reservation}">


    <table>
      ...
        <tr>
            <td>Player Name</td>
            <td><input type="text" th:field="*{player.name}" required/></td>
            <td><span class="error" th:if="${#fields.hasErrors('player.name')}" th:errors="*{player.name}"></span></td>
        </tr>
        <tr>
            <td>Player Phone</td>
            <td><input type="text" th:field="*{player.phone}" required/></td>
            <td><span class="error" th:if="${#fields.hasErrors('player.phone')}" th:errors="*{player.phone}"></span>
            </td>
        </tr>
        <tr>
            <td colspan="3"><input type="submit"/></td>
        </tr>
    </table>


</form>
</body>
</html>

Using a straightforward approach , you add two additional <input> tags to represent the Player object’s fields. Though these form declarations are simple, you also need to perform modifications to the controller. Recall that by using <input> tags, a view expects to have access to model objects passed by the controller, which match the path value for <input> tags.

Though the controller’s HTTP GET handler method returns an empty Reservation object to this last view, the player property is null, so it causes an exception when rendering the form. To solve this problem, you have to initialize an empty Player object and assign it to the Reservation object returned to the view.

@RequestMapping(method = RequestMethod.GET)
public String setupForm(
@RequestParam(required = false, value = "username") String username, Model model) {
    Reservation reservation = new Reservation();
    reservation.setPlayer(new Player(username, null));
    model.addAttribute("reservation", reservation);
    return "reservationForm";
}

In this case, after creating the empty Reservation object, the setPlayer method is used to assign it an empty Player object. Further note that the creation of the Person object relies on the username value. This particular value is obtained from the @RequestParam input value, which was also added to the handler method. By doing so, the Player object can be created with a specific username value passed in as a request parameter, resulting in the username form field being prepopulated with this value.

So, for example, if a request to the form is made in the following manner:

http://localhost:8080/court/reservationForm?username=Roger

this allows the handler method to extract the username parameter to create the Player object, in turn prepopulating the form’s username form field with a Roger value. It’s worth noting that the @RequestParam annotation for the username parameter uses the property required=false; this allows a form request to be processed even if such a request parameter is not present.

Provide Form Reference Data

When a form controller is requested to render the form view, it may have some types of reference data to provide to the form (e.g., the items to display in an HTML selection). Now suppose you want to allow a user to select the sport type when reserving a court—which is the final unaccounted field for the Reservation class.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>
<form method="post" th:object="${reservation}">


    <table>
        ...
        <tr>
            <td>Sport Type</td>
            <td>
                <select th:field="*{sportType}">
                    <option th:each="sportType : ${sportTypes}" th:value="${sportType.id}" th:text="${sportType.name}"/>
                </select>
            </td>
            <td><span class="error" th:if="${#fields.hasErrors('sportType')}" th:errors="*{sportType}"></span></td>
        </tr>
        <tr>
            <td colspan="3"><input type="submit"/></td>
        </tr>
    </table>


</form>
</body>
</html>

The <form:select> tag provides a way to generate a drop-down list of values passed to the view by the controller. Thus, the form represents the sportType field as a set of HTML <select> elements, instead of the previous open-ended fields—<input>—that require a user to introduce text values.

Next, let’s take a look at how the controller assigns the sportType field as a model attribute; the process is a little different than the previous fields.

First let’s define the getAllSportTypes() method in the ReservationService interface for retrieving all available sport types.

package com.apress.springrecipes.reactive.court;
...
public interface ReservationService {
    ...
    Flux<SportType> getAllSportTypes();
}

Then you can implement this method by returning a hard-coded list.

package com.apress.springrecipes.reactive.court;
...
public class InMemoryReservationService implements ReservationService {
    ...
    public static final SportType TENNIS = new SportType(1, "Tennis");
    public static final SportType SOCCER = new SportType(2, "Soccer");


    public Flux<SportType> getAllSportTypes() {
        return Flux.fromIterable(Arrays.asList(TENNIS, SOCCER));
    }
}

Now that you have an implementation that returns a hard-coded list of SportType objects, let’s take a look at how the controller associates this list for it to be returned to the form view.

package com.apress.springrecipes.court.service;
.....
    @ModelAttribute("sportTypes")
    public Flux<SportType> populateSportTypes() {
        return reservationService.getAllSportTypes();
    }


    @RequestMapping(method = RequestMethod.GET)
    public String setupForm(
    @RequestParam(required = false, value = "username") String username, Model model) {
        Reservation reservation = new Reservation();
        reservation.setPlayer(new Player(username, null));
        model.addAttribute("reservation", reservation);
        return "reservationForm";
    }

Notice that the setupForm handler method charged with returning the empty Reservation object to the form view remains unchanged.

The new addition, which is responsible for passing a SportType list as a model attribute to the form view, is the method decorated with the @ModelAttribute("sportTypes") annotation. The @ModelAttribute annotation is used to define global model attributes, available to any returning view used in handler methods. In the same way, a handler method declares a Model object as an input parameter and assigns attributes that can be accessed in the returning view.

Since the method decorated with the @ModelAttribute("sportTypes") annotation has a return type of Flux<SportType> and makes a call to reservationService.getAllSportTypes(), the hard-coded TENNIS and SOCCER SportType objects are assigned to the model attribute named sportTypes. This last model attribute is used in the form view to populate a drop-down list (i.e., <select> tag).

Bind Properties of Custom Types

When a form is submitted, a controller binds the form field values to the model object’s properties of the same name, in this case a Reservation object. However, for properties of custom types, a controller is not able to convert them unless you specify the corresponding property editors for them.

For example, the sport type selection field submits only the selected sport type ID—as this is the way HTML <select> fields operate. Therefore, you have to convert this ID into a SportType object with a property editor. First, you require the getSportType() method in ReservationService to retrieve a SportType object by its ID.

package com.apress.springrecipes.court.service;
...
public interface ReservationService {
    ...
    public SportType getSportType(int sportTypeId);
}

For testing purposes, you can implement this method with a switch/case statement.

package com.apress.springrecipes.court.service;
...
public class ReservationServiceImpl implements ReservationService {
    ...
    public SportType getSportType(int sportTypeId) {
        switch (sportTypeId) {
        case 1:
            return TENNIS;
        case 2:
            return SOCCER;
        default:
            return null;
        }
    }
}

Then you create the SportTypeConverter class to convert a sport type ID into a SportType object. This converter requires ReservationService to perform the lookup.

package com.apress.springrecipes.reactive.court.domain;

import com.apress.springrecipes.court.service.ReservationService;
import org.springframework.core.convert.converter.Converter;


public class SportTypeConverter implements Converter<String, SportType> {

    private final ReservationService reservationService;

    public SportTypeConverter(ReservationService reservationService) {
        this.reservationService = reservationService;
    }


    @Override
    public SportType convert(String source) {
        int sportTypeId = Integer.parseInt(source);
        SportType sportType = reservationService.getSportType(sportTypeId);
        return sportType;
    }
}

Now that you have the supporting SportTypeConverter class required to bind form properties to a custom class like SportType, you need to associate it with the controller. For this purpose, you can use the addFormatters method from the WebFluxConfigurer.

By overriding this method in your configuration class, custom types can be associated with a controller. This includes the SportTypeConverter class and other custom types like Date. Though we didn’t mention the date field earlier, it suffers from the same problem as the sport type selection field. A user introduces date fields as text values. For the controller to assign these text values to the Reservation object’s date field, this requires the date fields to be associated with a Date object. Given the Date class is part of the Java language, it won’t be necessary to create a special class like SportTypeConverter for this purpose. The Spring Framework already includes a custom class for this purpose.

Knowing you need to bind both the SportTypeConverter class and a Date class to the underlying controller, the following code illustrates the modifications to the configuration class:

package com.apress.springrecipes.reactive.court;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.ViewResolverRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.SpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.templatemode.TemplateMode;


@Configuration
@EnableWebFlux
@ComponentScan
public class WebFluxConfiguration implements WebFluxConfigurer {


    @Autowired
    private ReservationService reservationService;


    ...

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new SportTypeConverter(reservationService));
    }
}

The only field for this last class corresponds to reservationService, used to access the application’s ReservationService bean. Note the use of the @Autowired annotation that enables the injection of the bean. Next, you can override the addFormatters method used to bind the Date and SportTypeConverter classes. You can then find two calls to register the converter and formatter. These methods belong to the FormatterRegistry object, which is passed as an input parameter to the addFormatters method.

The first call is used to bind a Date class to the DateFormatter class . The DateFormatter class is provided by the Spring Framework and offers functionality to parse and print Date objects.

The second call is used to register the SportTypeConverter class. Since you created the SportTypeConverter class, you should know that its only input parameter is a ReservationService bean. By using this approach, every annotation-based controller (i.e., classes using the @Controller annotation) can have access to the same custom converters and formatters in their handler methods.

Validate Form Data

When a form is submitted, it’s standard practice to validate the data provided by a user before a submission is successful. Spring WebFlux, like Spring MVC, supports validation by means of a validator object that implements the Validator interface. You can write the following validator to check whether the required form fields are filled and whether the reservation hour is valid on holidays and weekdays:

package com.apress.springrecipes.reactive.court;

import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;


import java.time.DayOfWeek;
import java.time.LocalDate;


@Component
public class ReservationValidator implements Validator {


    public boolean supports(Class<?> clazz) {
        return Reservation.class.isAssignableFrom(clazz);
    }


    public void validate(Object target, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "courtName",
            "required.courtName", "Court name is required.");
        ValidationUtils.rejectIfEmpty(errors, "date",
            "required.date", "Date is required.");
        ValidationUtils.rejectIfEmpty(errors, "hour",
            "required.hour", "Hour is required.");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "player.name",
            "required.playerName", "Player name is required.");
        ValidationUtils.rejectIfEmpty(errors, "sportType",
            "required.sportType", "Sport type is required.");


        Reservation reservation = (Reservation) target;
        LocalDate date = reservation.getDate();
        int hour = reservation.getHour();
        if (date != null) {
            if (date.getDayOfWeek() == DayOfWeek.SUNDAY) {
                if (hour < 8 || hour > 22) {
                    errors.reject("invalid.holidayHour", "Invalid holiday hour.");
                }
            } else {
                if (hour < 9 || hour > 21) {
                    errors.reject("invalid.weekdayHour", "Invalid weekday hour.");
                }
            }
        }
    }
}

In this validator , you use utility methods such as rejectIfEmptyOrWhitespace() and rejectIfEmpty() in the ValidationUtils class to validate the required form fields. If any of these form fields is empty, these methods will create a field error and bind it to the field. The second argument of these methods is the property name, while the third and fourth are the error code and default error message.

You also check whether the reservation hour is valid on holidays and weekdays. If it’s invalid, you should use the reject() method to create an object error to be bound to the reservation object, not to a field.

Since the validator class is annotated with the @Component annotation, Spring attempts to instantiate the class as a bean in accordance with the class name, in this case reservationValidator.

Since validators may create errors during validation, you should define messages for the error codes for displaying to the user. If you have ResourceBundleMessageSource defined, you can include the following error messages in your resource bundle for the appropriate locale (e.g., messages.properties for the default locale); see also recipe 3-5:

required.courtName=Court name is required
required.date=Date is required
required.hour=Hour is required
required.playerName=Player name is required
required.sportType=Sport type is required
invalid.holidayHour=Invalid holiday hour
invalid.weekdayHour=Invalid weekday hour

To apply this validator, you need to perform the following modification to your controller:

package com.apress.springrecipes.court.service;
.....
    private final ReservationService reservationService;
    private final ReservationValidator reservationValidator;


    public ReservationFormController(ReservationService reservationService,
        ReservationValidator reservationValidator) {
        this.reservationService = reservationService;
        this.reservationValidator = reservationValidator;
    }


    @RequestMapping(method = RequestMethod.POST)
    public String submitForm(
        @ModelAttribute("reservation") @Validated Reservation reservation,
        BindingResult result, SessionStatus status) {
        if (result.hasErrors()) {
            return "reservationForm";
        } else {
            reservationService.make(reservation);
            return "redirect:reservationSuccess";
        }
    }


    @InitBinder
    public void initBinder(WebDataBinder binder) {
        binder.setValidator(reservationValidator);
    }

The first addition to the controller is the ReservationValidator field, which gives the controller access to an instance of the validator bean.

The next modification takes place in the HTTP POST handler method, which is always called when a user submits a form. Next to the @ModelAttribute annotation, there is now an @Validated annotation, which triggers validation of the object. After the validation, the result parameter—the BindingResult object—contains the results for the validation process. So next, a conditional based on the value of result.hasErrors() is made. If the validation class detects errors, this value is true.

If errors are detected in the validation process, the method handler returns the view reservationForm, which corresponds to the same form so that a user can resubmit information. If no errors are detected in the validation process, a call is made to perform the reservation— reservationService.make(reservation);—followed by a redirection to the success view reservationSuccess.

The registration of the validator is done in the @InitBinder annotated method, and the validator is set on the WebDataBinder so that it can be used after binding. To register the validator, you need to use the setValidator method. You can also register multiple validators using the addValidators method; this method takes a varargs argument for one or more Validator instances.

Note

The WebDataBinder object can also be used to register additional ProperyEditor, Converter, and Formatter instances for type conversion. This can be used instead of registering global PropertyEditors, Converters, or Formatters.

Tip

Instead of writing a custom Spring Validator instances, you could also utilize JSR-303 validation and annotate fields to have them validated.

5-7. Publish and Consume JSON with Reactive REST Services

Problem

You want to publish XML or JSON services in a reactive way.

Solution

Using the same declarations as described in recipes 4-1 and 4-2, you can write a reactive endpoint.

How It Works

To publish JSON you can use the @ResponseBody or a @RestController. By returning a reactive type, Mono or Flux, you can have a chuncked response. How the result is handled depends on the requested representation. When consuming JSON you can annotate a reactive method argument, of type Mono or Flux, with @ResponseBody to have this reactivly consumed.

Publish JSON

By annotating the request-handling method with @ResponseBody, the output will be returned as JSON or XML (depending on the request return type and available libraries on the classpath). Instead of annotating the method with @ResponseBody, you could use the @RestController annotation on the class level, which automatically implies this for all request-handling methods.

Let’s write a REST controller that returns all reservations in the system. You do this by annotating a class with @RestController and giving it an @GetMapping annotated method, which returns a Flux<Reservation> object.

package com.apress.springrecipes.reactive.court.web;

import com.apress.springrecipes.reactive.court.Reservation;
import com.apress.springrecipes.reactive.court.ReservationService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;


@RestController
@RequestMapping("/reservations")
public class ReservationRestController {


    private final ReservationService reservationService;

    public ReservationRestController(ReservationService reservationService) {
        this.reservationService = reservationService;
    }


    @GetMapping
    public Flux<Reservation> listAll() {
        return reservationService.findAll();
    }
}

When you return a reactive type like this, it will be streamed to the client either as streaming JSON/XML or as server-sent events (see recipe 5-2). The result depends on the Accept-Header header from the client. Using httpie and doing http http://localhost:8080/court-rx/reservations --stream will get you JSON. When adding Accept:text/event-stream, the result will be published as server-sent events.

Consume JSON

In addition to producing JSON , you can also consume it. For this, add a method argument and annotate it with @RequestBody. The incoming JSON request body will be mapped onto the object. For a reactive controller, you can wrap it in a Mono or Flux for, respectively, single or multiple results.

First create a simple POJO that takes a courtName so you can query the reservations.

package com.apress.springrecipes.reactive.court.web;

public class ReservationQuery {

    private String courtName;

    public String getCourtName() {
        return courtName;
    }


    public void setCourtName(String courtName) {
        this.courtName = courtName;
    }
}

This is just a basic POJO, which will be filled through JSON. Now it’s time for the controller. Add a method that takes Mono<ReservationQuery> as its argument.

@PostMapping
public Flux<Reservation> find(@RequestBody Mono<ReservationQuery> query) {
    return query.flatMapMany(q -> reservationService.query(q.getCourtName()));
}

Now when a request with a JSON body comes in, this will be deserialized into the ReservationQuery object. For this, Spring WebFlux uses (just like Spring MVC) a converter. The conversion is delegated to an instance of HttpMessageReader, in this case DecoderHttpMessageReader. This class will decode the reactive stream into the object. This again is delegated to a Decoder object. Because you want to use JSON (and have the Jackson 2 JSON library on the classpath), it will use Jackson2JsonDecoder for this. The HttpMessageReader and Decoder implementations are the reactive counterparts of HttpMessageConverter used by regular Spring MVC.

Using httpie and issuing the request http POST http://localhost:8080/court-rx/reservations courtName="Tennis #1" --stream, you will get back all the results for the court Tennis #1. This command will send the following JSON to the server:

{ courtName: "Tennis #1"}

5-8. Use an Asynchronous Web Client

Problem

You want to access a REST service from a third party (e.g., Google, Yahoo, or another business partner) and use its payload inside a Spring application.

Solution

Accessing a third-party REST service inside a Spring application revolves around the use of the Spring WebClient class. The WebClient class is designed on the same principles as the many other Spring *Template classes (e.g., JdbcTemplate, JmsTemplate), providing a simplified approach with default behaviors for performing lengthy tasks.

This means the processes of invoking a REST service and using its returning payload are streamlined in Spring applications.

Note

Prior to Spring 5, you would use AsyncRestTemplate; however, as of Spring 5, that has been deprecated in favor of WebClient.

How It Works

Before describing the particularities of the WebClient class, it’s worth exploring the life cycle of a REST service so you’re aware of the actual work the RestTemplate class performs. Exploring the life cycle of a REST service can best be done from a browser, so open your favorite browser on your workstation to get started.

The first thing that’s needed is a REST service endpoint. You are going to reuse the endpoint you created in recipe 5-7. This endpoint should be available at http://localhost:8080/court-rx/reservations. If you load this REST service endpoint in your browser, the browser performs a GET request, which is one of the most popular HTTP requests supported by REST services. Upon loading the REST service, the browser displays a responding payload, as shown in Figure 5-6.

A314861_4_En_5_Fig6_HTML.jpg
Figure 5-6. Resulting JSON

It’s the task of a REST service consumer (i.e., you) to know the payload structure—sometimes referred to as the vocabulary—of a REST service to appropriately process its information. Though this last REST service relies on what can be considered a custom vocabulary, a series of REST services often relies on standardized vocabularies (e.g., RSS), which makes the processing of REST service payloads uniform. In addition, it’s also worth noting that some REST services provide Web Application Description Language (WADL) contracts to facilitate the discovery and consumption of payloads.

Now that you’re familiar with a REST service’s life cycle using your browser, you can take a look at how to use the Spring WebClient class to incorporate a REST service’s payload into a Spring application. Given that the WebClient class is designed to call REST services, it should come as no surprise that its main methods are closely tied to REST’s underpinnings, which are the HTTP protocol’s methods: HEAD, GET, POST, PUT, DELETE, and OPTIONS. Table 5-6 contains the main methods supported by the RestTemplate class.

Table 5-6. WebClient Class Methods Based on HTTP’s Request Methods

Method

Description

create

Creates a WebClient; optionally you can give a default URL

head()

Prepares an HTTP HEAD operation

get()

Prepares an HTTP GET operation

post()

Prepares an HTTP POST operation

put()

Prepares an HTTP PUT operation

options()

Prepares an HTTP OPTIONS operation

patch()

Prepares an HTTP PATCH operation

delete()

Prepares an HTTP DELETE operation

As you can observe in Table 5-6, the WebClient class builder methods are modeled after HTTP protocol methods, which include HEAD, GET, POST, PUT, DELETE, and OPTIONS.

Note

By far the most common HTTP method used in REST services is GET since it represents a safe operation to obtain information (i.e., it doesn’t modify any data). On the other hand, HTTP methods such as PUT, POST, and DELETE are designed to modify a provider’s information, which makes them less likely to be supported by a REST service provider. For cases in which data modification needs to take place, many providers opt for the SOAP protocol, which is an alternative mechanism to using REST services.

Now that you’re aware of the WebClient basic builder methods, you can move on to invoking the same REST service you did with your browser previously, except this time using Java code from the Spring Framework. The following code illustrates a class that accesses the REST service and returns its contents to System.out:

package com.apress.springrecipes.reactive.court;

import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;


import java.io.IOException;

public class Main {

    public static void main(String[] args) throws IOException {
        final String url = "http://localhost:8080/court-rx";


        WebClient.create(url)
            .get()
            .uri("/reservations")
            .accept(MediaType.APPLICATION_STREAM_JSON)
            .exchange()
            .flatMapMany(cr -> cr.bodyToFlux(String.class)).subscribe(System.out::println);


        System.in.read();
    }
}
Caution

Some REST service providers restrict access to their data feeds depending on the requesting party. Access is generally denied by relying on data present in a request (e.g., HTTP headers or IP address). So, depending on the circumstances, a provider can return an access denied response even when a data feed appears to be working in another medium (e.g., you might be able to access a REST service in a browser but get an accessed denied response when attempting to access the same feed from a Spring application). This depends on the terms of use set forth by a REST provider.

The first line declares the import statement needed to access the WebClient class within a class’s body. First you need to create an instance of the WebClient class using WebClient.create. Next, you can find a call made to the get() method that belongs to the WebClient class, which as described in Table 5-6 is used to prepare an HTTP GET operation—just like the one performed by a browser to obtain a REST service’s payload. Next you extend the base URL to call because you want to call http://localhost:8080/court-rx/reservations and you want to have a stream of JSON, which is the reason for accept(MediaType.APPLICATION_STREAM_JSON).

Next, the call to exchange() will switch the configuration from setting up the request to define the response handling. As you probably get zero or more elements, you need to convert the ClientResponse body to a Flux. For this you can call the bodyToFlux method on ClientResponse (there is also a plain body method that you could use if you need custom conversion or the bodyToMono method to convert to a single-element result). You want to write each element to System.out, so you subscribe to that.

When you execute the application, the output will be the same as in the browser except that it is now printed in the console.

Retrieve Data from a Parameterized URL

The previous section showed how you can call a URI to retrieve data, but what about a URI that requires parameters? You don’t want to hard-code parameters into the URL. With the WebClient class, you can use a URL with placeholders; these placeholders will be replaced with actual values upon execution. Placeholders are defined using { and }, just as with a request mapping (see recipes 4-1 and 4-2).

The URI http://localhost:8080/court-rx/reservations/{courtName} is an example of such a parameterized URI. To be able to call this method, you need to pass in a value for the placeholder; you can do this by passing the parameters as arguments to the uri method of the WebClient class.

public class Main {

    public static void main(String[] args) throws Exception {
        WebClient.create(url)
            .get()
            .uri("/reservations/{courtName}", "Tennis")
            .accept(MediaType.APPLICATION_STREAM_JSON)
            .exchange()
            .flatMapMany(cr -> cr.bodyToFlux(String.class)).subscribe(System.out::println);


        System.in.read();
    }
}

Retrieve Data as a Mapped Object

Instead of returning a String to be used in the application , you can also (re)use your Reservation, Player, and SportType classes to map the result. Instead of passing in String.class as a parameter to the bodyToFlux method, pass Reservation.class, and the response will be mapped onto this class.

package com.apress.springrecipes.reactive.court;

import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;


import java.io.IOException;

public class Main {

    public static void main(String[] args) throws IOException {
        final String url = "http://localhost:8080/court-rx";


        WebClient.create(url)
            .get()
            .uri("/reservations")
            .accept(MediaType.APPLICATION_STREAM_JSON)
            .exchange()
            .flatMapMany(cr -> cr.bodyToFlux(Reservation.class)).subscribe(System.out::println);


        System.in.read();
    }
}

The WebClient class makes use of the same HttpMessageReader infrastructure as a controller with @ResponseBody marked methods. As JAXB 2 (as well as Jackson) is automatically detected, mapping to an object is quite easy.

5-9. Write a Reactive Handler Function

Problem

You want to write functions that react to incoming requests.

Solution

You can write a method that takes a ServerRequest, returns a Mono<ServerResponse>, and maps it as a router function.

How It Works

Instead of mapping requests to methods using @RequestMapping, you can also write functions that are essentially honoring the HandlerFunction interface.

package org.springframework.web.reactive.function.server;

import reactor.core.publisher.Mono;

@FunctionalInterface
public interface HandlerFunction<T extends ServerResponse> {


    Mono<T> handle(ServerRequest request);

}

A HandlerFunction, as shown in the previous code, is basically a method that takes a ServerRequest as an argument and returns a Mono<ServerResponse>. Both the ServerRequest and ServerResponse provide full reactive access to the underlying request and response; this is by exposing various parts of it as either Mono or Flux streams.

After a function has been written , it can be mapped to incoming requests using the RouterFunctions class. The mapping can be done on URLs, headers, methods, or custom-written RequestPredicate classes. The default available request predicates are accessible through the RequestPredicates class.

Write Handler Functions

Let’s rewrite the ReservationRestController to simple request-handling functions instead of a controller.

To do so, remove all the request-mapping annotations and add a simple @Component to the class. Next rewrite the methods to adhere to the signature outlined by the HandlerFunction interface.

package com.apress.springrecipes.reactive.court.web;

import com.apress.springrecipes.reactive.court.Reservation;
import com.apress.springrecipes.reactive.court.ReservationService;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;


@Component
public class ReservationRestController {


    private final ReservationService reservationService;

    public ReservationRestController(ReservationService reservationService) {
        this.reservationService = reservationService;
    }


    public Mono<ServerResponse> listAll(ServerRequest request) {
        return ServerResponse.ok().body(reservationService.findAll(), Reservation.class);
    }


    public Mono<ServerResponse> find(ServerRequest request) {
        return ServerResponse
            .ok()
            .body(
                request.bodyToMono(ReservationQuery.class)
                    .flatMapMany(q -> reservationService.query(q.getCourtName())), Reservation.class);
    }
}

The class still needs ReservationService as a dependency. Notice the change in the listAll and find methods. They now both return Mono<ServerResposne> and accept a ServerRequest as input. Because you want to return an HTTP status of OK (200), you can use ServerResponse.ok() to build that response. You need to add a body, Flux<Reservation> in this case, and you need to specify the type of elements, Reservation.class. The latter is needed because the reactive and generic nature type of information cannot be read when composing the function.

In the find method, something similar happens, but first you map the body of the incoming request to a ReservationQuery using bodyToMono. This result is then used to eventually call the query method on ReservationService.

Route Requests to Handler Functions

As you now have simple functions instead of annotation-based request-handling methods, routing needs to be done differently. You can use RouterFunctions to do the mapping instead.

@Bean
public RouterFunction<ServerResponse> reservationsRouter(ReservationRestController handler) {
    return RouterFunctions
        .route(GET("/*/reservations"), handler::listAll)
        .andRoute(POST("/*/reservations"), handler::find);
}

When an HTTP GET request comes in for /court-rx/reservations, the listAll method will be invoked for an HTTP POST the find method will be invoked.

Using RequestPredicates.GET is the same as writing RequestPredicates.method(HttpMethod.GET).and(RequestPredicates.path("/*/reservations")). You can combine as many RequestPredicate statements as you want. The methods in Table 5-7 are exposed through the RequestPredicates class.

Table 5-7. Default Available RequestPredicates

Method

Description

method

RequestPredicate for the HTTP METHOD

path

RequestPredicate for the URL or part of the URL

accept

RequestPredicate for the Accept header to match requested media types

queryParam

RequestPredicate to check for the existence of query parameters

headers

RequestPredicate to check for the existence of request headers

The RequestPredicates helper also provides shorthand methods for GET, POST, PUT, DELETE, HEAD, PATCH, and OPTIONS. This saves you from combining two expressions.

Summary

In this chapter, you looked at various ways to do async processing. The traditional way is to use the Servlet 3.x asynchronous support and have the controller return a DeferredResult or a Future.

For communication, you looked at server-sent events and WebSocket communication. This allowed you to communicate in an asynchronous way between the client and the server.

Then you moved on and learned how to write reactive controllers, which wasn’t all that different from what you learned in Chapters 3 and 4. This also shows the power of Spring’s abstractions; you can use almost the same programming model for a totally different technology. After writing reactive controllers, you looked at writing reactive handler functions, which can do much of the same stuff that reactive controllers can do in a more functional programming kind of way.

In between you also looked at the WebClient class to do asynchronous consumption of a REST API.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset