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.
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.
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.
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.
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
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())));
}
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