I added an async endpoint to a existing spring-mvc application:
#RestController
public class MyController {
#PostMapping("/")
public Mono<String> post(Object body) {
return Mono.just("test");
//webClient.retrieve().bodyToMono(String.class);
}
}
I want to create a global interceptor/filter that will log the request body payload. But how can I get access to it?
I tried adding a HandlerInterceptorAdapter, but the payload is always empty:
static class LoggingInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
ContentCachingRequestWrapper wrapper = new ContentCachingRequestWrapper(request);
byte[] buf = wrapper.getContentAsByteArray();
System.out.println(buf);
System.out.println(buf.length);
return true;
}
}
Maybe the payload is not yet present in the request, or has already been read. So how can I access the body in this async case?
Unfortunately in Webflux you cannot use HandlerInterceptorAdapter because it came from web mvc module and works only with the servlets.
I found a good article with solutions.
P.S. You must to remove spring-mvc dependencies if going to use reactive endpoins.
Related
I have a simple Spring Boot REST service for the IFTTT platform. Each authorized request will contain a header IFTTT-Service-Key with my account's service key and I will use that to either process the request or return a 401 (Unauthorized). However, I only want to do this for select endpoints -- and specifically not for ANY of the Spring actuator endpoints.
I have looked into Spring Security, using filters, using HandlerInterceptors, but none seem to fit what I am trying to do exactly. Spring security seems to come with a lot of extra stuff (especially the default user login), filters don't really seem to match the use case, and the handler interceptor works fine but I would have to code logic in to watch specific URLs and ignore others.
What is the best way to achieve what I am trying to do?
For reference, this is the code I have now:
public class ServiceKeyValidator implements HandlerInterceptor {
private final String myIftttServiceKey;
public ServiceKeyValidator(#Value("${ifttt.service-key}") String myIftttServiceKey) {
this.myIftttServiceKey = myIftttServiceKey;
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// TODO will have to put logic in to skip this when actuator endpoints are added
String serviceKeyHeader = request.getHeader("IFTTT-Service-Key");
if (!myIftttServiceKey.equals(serviceKeyHeader)) {
var error = new Error("Incorrect value for IFTTT-Service-Key");
var errorResponse = new ErrorResponse(Collections.singletonList(error));
throw new UnauthorizedException(errorResponse);
}
return HandlerInterceptor.super.preHandle(request, response, handler);
}
}
You need to add filtering for the required endpoints in the place where you register your HandlerInterceptor.
For example:
#EnableWebMvc
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new ServiceKeyValidator())
.addPathPatterns("/ifttt/**")
.excludePathPatterns("/actuator/**");
}
}
You can use different URLs path matchers to filter which URL endpoints must be handled by your interceptor and which are not. As the method addPathPatterns returns InterceptorRegistration object that configures this.
I have a series of Rest API Controllers in my Spring boot application with Request Mappings that match certain URLs.
I need to change my implementation to always make sure that a specific custom header is in place for all requests. If header is not there I want to fail the request. If it is I want to forward to the appropriate controller which would be the same as my current implementation.
Is there a way to do this in Spring Boot without modifying my existing controllers at all? Could I try to use something like Spring Security, even though my header is not related to security at all?
Thank you.
Web MVC defines an abstraction called "HandlerInterceptor" and its no-op implementation HandlerInterceptorAdapter
So you can register the bean that looks like this:
#Component
public class RequestProcessingTimeInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
// check the headers, extract them from request, whatever
return true; // if you want to proceed to controller
return false;// otherwise :)
}
}
This will instruct spring mvc to call the method before the flow gets to the controller.
You can configure a Filter as a #Service.
#Service
#NoArgsConstructor #Log4j2
public class FilterImpl implements Filter {
#Override
public void init(FilterConfig config) throws ServletException { }
#Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (request.getHeader("required-header-name") != null) {
chain.doFilter(request, response);
} else {
log.info("Rejected {}", request);
}
}
#Override
public void destroy() {
}
}
How do I log a JSON or XML request in a database or log file before processing in Spring boot using #RequestBody annotation?
Using which class can I perform this?
Or any link would be helpful.
You can use filter (CommonsRequestLoggingFilter class) approach or you can use below code with custom implementation
#Component
public class AppRequestInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) {
HttpServletRequest requestCacheWrapperObject = new ContentCachingRequestWrapper(request);
//your implementation
//sample method you can use: requestCacheWrapperObject.getParameterMap(); requestCacheWrapperObject.getContentAsByteArray();
return true;
}
#Override
public void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
//your implementation
}
}
#Configuration
public class AppMVCConfig implements WebMvcConfigurer {
#Autowired
private AppRequestInterceptor appRequestInterceptor;
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(appRequestInterceptor)
.addPathPatterns("/**");
}
}
In order to log request payloads, you can use Spring provided filter CommonsRequestLoggingFilter.
Add following bean into your Spring-Boot config and change log level of org.springframework.web.filter package to DEBUG.
#Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
CommonsRequestLoggingFilter rlFilter = new CommonsRequestLoggingFilter();
rlFilter.setIncludePayload(true);
return rlFilter;
}
Also, Spring Boot provides Actuator Endpoint (/actuator/httptrace) for HTTP request logging out of the box. Check the below link for more details on this:
Spring Boot Actuator
Depends on request body content I need to redirect http requests to URL_1 or URL_2.
I started controller implementation:
#RestController
public class RouteController {
#Autowired
private RestTemplate restTemplate;
#RequestMapping(value = "/**")
public HttpServletResponse route(HttpServletRequest request) {
String body = IOUtils.toString(request.getReader());
if(isFirstServer(body)) {
//send request to URL_1 and get response
} else {
//send request to URL_2 and get response
}
}
}
Request might be GET or POST ot PUT or PATCH etc.
Could you help me to write that code?
I've asked a somehow similar question a while ago. Plea see Server side redirect for REST call for more context.
The best way (to my current understanding) you could achieve this is by manually invoking the desired endpoints from your initial endpoint.
#RestController
public class RouteController {
#Value("${firstUrl}")
private String firstUrl;
#Value("${secondUrl}")
private String secondUrl;
#Autowired
private RestTemplate restTemplate;
#RequestMapping(value = "/**")
public void route(HttpServletRequest request) {
String body = IOUtils.toString(request.getReader());
if(isFirstServer(body)) {
restTemplate.exchange(firstUrl,
getHttpMethod(request),
getHttpEntity(request),
getResponseClass(request),
getParams(params));
} else {
restTemplate.exchange(secondUrl,
getHttpMethod(request),
getHttpEntity(request),
getResponseClass(request),
getParams(params))
}
}
}
Example implementation for getHttpMethod:
public HttpMethod getHttpMethod(HttpServletRequest request) {
return HttpMethod.valueOf(request.getMethod());
}
Similar implementations for getHttpEntity, getResponseClass and getParams. They are used for converting the data from the HttpServletRequest request to the types required by the exchange method.
There seem to be a lot of better ways of doing this for a Spring MVC app, but I guess that it does not apply to your context.
Another way you could achieve this would be defining your own REST client and adding the routing logic there.
I'm trying to send a request to an HTTP endpoint using Camel Jetty or Camel HTTP component. I was wondering if it's possible to convert the request and response to HttpServletRequest and HttpServletResponse objects.
I have the following route:
from("direct://processing/callEndpoint")
.to("jetty:http://www.google.com")
.to("bean:processHttpResponse")
I'm using Guice for dependency injection and "bean:processHttpResponse" is the following:
public class HttpResponseBean {
public void processResponse(Exchange exchange) {
HttpServletRequest request = exchange.getIn().getBody(HttpServletRequest.class);
HttpServletResponse response = exchange.getOut().getBody(HttpServletResponse.class);
}
}
Both request and response objects are null. When debugging/inspecting the Exchange, the In Message is a byte array (byte[]) and the Out Message is null. Not sure how to proceed..
Refer to the Camel Jetty goto the Consumer Example.
Try to call your bean like this.
from("jetty:http://localhost:{{port}}/myapp/myservice").bean(HttpResponseBean,"processResponse");
public class HttpResponseBean {
public void processResponse(Exchange exchange) {
// we have access to the HttpServletRequest here and we can grab it if we need it
HttpServletRequest req = exchange.getIn().getBody(HttpServletRequest.class);
}
}
You should implement a processor like this one:
public class MyBean implements Processor {
public void process(Exchange exchange) throws Exception {
HttpServletRequest request = exchange.getIn().getBody(HttpServletRequest.class);
HttpServletResponse response = exchange.getOut().getBody(HttpServletResponse.class);
}
}
If you have a #Configuration Spring context like this
#Configuration
class ApplicationConfiguration {
#Bean
public MyBean myBean() {
return new MyBean();
}
}
you should be able to refer to it in the process statement like this .process("bean:myBean").
If you are building your route using a RouteBuilder, please use a SpringCamelContext in order to let Camel access the beans.