[JAVA] Try implementing a WebFlux filter

We will continue to talk about WebFlux. Up to the last time, I introduced the implementation of reactive programming mainly in the Controller-Service layer.

This time, I will introduce an implementation example of a filter used as common processing before and after a request.

Implement WebFliter instead of Filter

Spring MVC implements javax.servlet.Filter, while WebFlux implements ʻorg.springframework.web.server.WebFilter`.

Below, I implemented each simple filter that only outputs logs before and after request processing. Let's see the difference.

Spring MVC Filter implementation example

Filter that outputs logs before and after request processing



@Slf4j
public class TimestampFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest)request;
        // before
        doBeforeRequest(req);
        try {
            // do request
            chain.doFilter(request, response);
            // after (success)
            doAfterRequest(req);
        } catch (Throwable throwable) {
            // after (with error)
            doAfterRequestWithError(req, throwable);
            throw throwable;
        }
    }

    private void doBeforeRequest(HttpServletRequest request) {
        String uri = request.getRequestURI();
        long start = System.currentTimeMillis();
        // start log(ex: /hoge [IN]: 1582989973940)
        log.info(String.format("%s [IN]: %d", uri, start));
    }

    private void doAfterRequest(HttpServletRequest request) {
        String uri = request.getRequestURI();
        long end = System.currentTimeMillis();
        // end log(ex: /hoge [OUT]: 1582989974053)
        log.info(String.format("%s [OUT]: %d", uri, end));
    }

    private void doAfterRequestWithError(HttpServletRequest request, Throwable throwable) {
        String uri = request.getRequestURI();
        long end = System.currentTimeMillis();
        // end with error log(ex: /hoge [OUT]: 1582989974053 with Error(java.lang.RuntimeException))
        log.info(String.format("%s [OUT]: %d with Error(%s)", uri, end, throwable.toString()));
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }
}

The following is the implementation of the equivalent process with WebFilter.

WebFilter implementation example of Spring WebFlux

Filter that outputs logs before and after request processing (WebFilter)



@Slf4j
@Component
public class TimestampFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange).transformDeferred(call -> doFilter(exchange, call));
    }

    private Publisher<Void> doFilter(ServerWebExchange exchange, Mono<Void> call) {
        // before
        return Mono.fromRunnable(() -> doBeforeRequest(exchange))
                // do request
                .then(call)
                // after (success)
                .doOnSuccess((done) -> doAfterRequest(exchange))
                // after (with error)
                .doOnError((throwable -> doAfterRequestWithError(exchange, throwable)));
    }

    private void doBeforeRequest(ServerWebExchange exchange) {
        String uri = exchange.getRequest().getURI().toString();
        long start = System.currentTimeMillis();
        // start log(ex: /hoge [IN]: 1582989973940)
        log.info(String.format("%s [IN]: %d", uri, start));
    }

    private void doAfterRequest(ServerWebExchange exchange) {
        String uri = exchange.getRequest().getURI().toString();
        long end = System.currentTimeMillis();
        // end log(ex: /hoge [OUT]: 1582989974053)
        log.info(String.format("%s [OUT]: %d", uri, end));
    }

    private void doAfterRequestWithError(ServerWebExchange exchange, Throwable throwable) {
        String uri = exchange.getRequest().getURI().toString();
        long end = System.currentTimeMillis();
        // end with error log(ex: /hoge [OUT]: 1582989974053 with Error(java.lang.RuntimeException))
        log.info(String.format("%s [OUT]: %d with Error(%s)", uri, end, throwable.toString()));
    }
}

(Mono # compose (Function) has been deprecated, so it has been modified to Mono # transformDeferred (Function). 2020/04/01)

Mono.fromRunnable (~) reactively processes the process (void) that has no return value, and describes the subsequent process in then (~). In the example, .then (call) is specified and processing is transferred to the next filter or controller.

Furthermore, post-processing is implemented by connecting the following with doOnSuccess (~) and doOnError (~).

Finally, let's take a look at a slightly more practical filter implementation sample.

Implementation example of custom authentication filter

The following is an implementation example of a so-called authentication check filter that checks if a user has been authenticated when accessing a protected resource.

Custom authentication check filter



@Slf4j
@Component
@Order(1)
public class AuthFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange).transformDeferred(call -> doFilter(exchange, call));
    }

    private Publisher<Void> doFilter(ServerWebExchange exchange, Mono<Void> call) {
        return exchange.getSession().flatMap(webSession -> {
            Map<String, Object> attributes = webSession.getAttributes();
            //Get user information from session
            Optional<User> optionalUser =
                    Optional.ofNullable((User)attributes.get(User.class.getName()));
            return optionalUser
                    //Process request if there is a user
                    .map(user -> call)
                    //Throw unauthenticated error if no user
                    .orElseThrow(UnauthorizedException::new);
        });
    }
}

Getting the first session information ʻexchange.getSession () returns Mono `, so the subsequent writing is the previously introduced Multiple Sequential API Calls / fb18be963da6cac03ee9 #% E8% A4% 87% E6% 95% B0% E3% 81% AE% E9% A0% 86% E6% AC% A1api% E3% 82% B3% E3% 83% BC% E3% 83% I think AB) will be helpful.

Also, in this example, a custom error (UnauthorizedException) is thrown when unauthenticated, so it is assumed that a common ExceptionHandler will convert it to a 401: Unauthrized response.

If you want to generate the response in the filter, you can implement it as follows.

Generate response in WebFilter


    ...abridgement...

    private Publisher<Void> doFilter(ServerWebExchange exchange, Mono<Void> call) {
        return exchange.getSession().flatMap(webSession -> {
            Map<String, Object> attributes = webSession.getAttributes();
            //Get user information from session
            Optional<User> optionalUser =
                    Optional.ofNullable((User)attributes.get(User.class.getName()));
            return optionalUser
                    //Process request if there is a user
                    .map(user -> call) // do request
                    //Generate unauthenticated error response if no user
                    .orElse(writeUnauthorizedResponse(exchange));
        });
    }

    private Mono<Void> writeUnauthorizedResponse(ServerWebExchange exchange) {
        // 401:Generate JSON response as Unauthorized error
        ServerHttpResponse response = exchange.getResponse();
        String body = "{\"message\":\"Login authentication is required.\"}";
        return writeResponse(response, HttpStatus.UNAUTHORIZED, body);
    }

    private Mono<Void> writeResponse(ServerHttpResponse response, HttpStatus status, String jsonBody) {
        response.setStatusCode(status);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json;charset=UTF-8");
        DataBufferFactory dbf = response.bufferFactory();
        return response.writeWith(Mono.just(dbf.wrap(jsonBody.getBytes())));
    }

This summary

Filters in WebFlux

--Implemented as a subclass of WebFilter --The contents still describe the process reactively (see Article before last)

It will be.

Recommended Posts

Try implementing a WebFlux filter
Try implementing a WebFlux session
[Java] Create a filter
Try sending a notification.
Try implementing Yubaba in Kinx
[Swift] Apply a gradation filter
Try the Spring WebFlux tutorial
Try implementing Android Hilt in Java
Try implementing asynchronous processing in Azure
Try to make a simple callback
Try implementing GraphQL server in Java
Try implementing recaptcha with Jetty embedded.
Try Spring WebFlux (mainly Router Functions)
Try to create a server-client app
Try to make a peepable iterator