Context Path for Application
In Spring MVC can give a context path to our application. In Spring Webflux we don't have a configuration for that. But we can set a context-path for multiple routes. Following is how we can do that.
@Configuration public class RouterWithContext { @Bean public RouterFunction<ServerResponse> routeWithContext(SampleHandler handler) { return nest(path("/context"), route(GET("/sample"), handler::handleNestedRoute)); } }
RouterFunctions.nest(...) will let you build nested routes in your application. It will take a RequestPredicate and a RouterFunction as arguments. Nested route is analogous to having a context path in Spring MVC.
Up to now we have just tried out HTTP GET Method in our application. What happen if we want to have request-body and
we want to use it in our application. Lets do add a functionality to our application to accept a request-body and echo it back
with some modifications. For this we will use a HTTP POST with a simple request body.
Our Request body should look as follows.
public class RequestBody { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
The handler function should first take out the request body and reshape the content and sends back the response. We can do it
in imperative way using ServerRequest.bodyToMono(...).block() method which will give us the request-body. And the we can
reshape it and generate the response. But since we are doing reactive programming using Mono.block() is an anti-pattern.
What we can do is we can reshape the request-body in a more functional way.
public Mono<ServerResponse> handleRequestWithABody(ServerRequest request) { return request .bodyToMono(RequestBody.class) .flatMap(requestBody -> { ResponseBody responseBody = new ResponseBody(); responseBody.setStatus("Success"); responseBody.setDescription("Successfully Handled the request"); responseBody.setRequestBody(requestBody); return ServerResponse.ok().body(fromObject(responseBody)); }) .switchIfEmpty( ServerResponse.status(HttpStatus.BAD_REQUEST).build()); }
We will first acquire the request-body as a stream using ServerRequest.bodyToMono(...). And then we can apply a 'flatmap'
on that data-stream and reshape the content and get a data-stream. Finally there is some magic-code, the 'switchIfEmpty' which
will check whether the data-stream is empty and send a default ServerResponse. Now we done with the handler-function.
So, we have add the route now. This route has some specialty in it. This route consumes a request-body. So, it is preferable
if we could mention the media-type which is accepted by this route. So, we can configure our route as follows.
route(POST("/echo").and(contentType(MediaType.APPLICATION_JSON)), handler::handleRequestWithABody);
As you can see here RequestPredicates can be concatenated and return a RequestPredicate. Here we have concatenated RequestPredicate which checks for HTTP Method and a RequestPredicate which checks for Content-Type Header.
Adding a Filter
Most often than not we want to secure our APIs. Spring Security has a more precise ways of securing an application, but here
we want to have a simple key-base authentication for our application. For that we can use a simple filter function. First of all
we have write an authenticator. Authenticator is a filter-function which will take ServerRequest and a HandlerFunction as arguments and return a ServerResponse.
@Component public class Authenticator { @Value("${configuration.api.access-key}") private String apiKey; public Mono<ServerResponse> filterRoute(ServerRequest request, HandlerFunction<ServerResponse> handlerFunction){ if (request.headers().asHttpHeaders().containsKey("x-api-key")) { if (request.headers().asHttpHeaders().get("x-api-key").get(0).equals(apiKey)) { return handlerFunction.handle(request); } return ServerResponse.status(HttpStatus.UNAUTHORIZED).build(); } else { return ServerResponse.status(HttpStatus.UNAUTHORIZED).build(); } } }
Finally you can add the filter to the router.
@Bean public RouterFunction<ServerResponse> routeWithContext(SampleHandler handler) { return nest(path("/context"), sampleRoute(handler) .and(handleRequestBodyRoute(handler))) .filter(authenticator::filterRoute); }
You can find the sample code here.
No comments:
Post a Comment