Reputation: 583
I have a spring boot application. I change the request body of every post request. Is it possible to modify the request body before the request reaches the controller. Please include an example.
Upvotes: 32
Views: 68578
Reputation: 149
I had this this issue too. This thread helped me a bit and I wanted to post a simpler solution than Bijaya Bhaskar Swain.
package com.thebois.inpassering.adapters.merchant.staffrestrepo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.thebois.inpassering.adapters.merchant.FilterOrders;
import com.thebois.inpassering.adapters.merchant.facilityownerrestrepo.FacilityOwner;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;
import javax.servlet.FilterChain;
import javax.servlet.ReadListener;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import org.springframework.core.annotation.Order;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
@Component
@AllArgsConstructor
@Order(value = FilterOrders.STAFF_REQUEST_ORDER)
public class StaffCreationFilter extends OncePerRequestFilter {
ObjectMapper objectMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
RequestWrapper modifiedRequest = new RequestWrapper(request);
filterChain.doFilter(modifiedRequest, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return !(request.getServletPath().contains("/staff") && request.getMethod().equals("POST"));
}
private class RequestWrapper extends HttpServletRequestWrapper {
String body;
public RequestWrapper(HttpServletRequest request) throws IOException {
super(request);
String body = new ContentCachingRequestWrapper(request).getReader().lines().collect(Collectors.joining(System.lineSeparator()));
//get your dto
Staff staff = objectMapper.readValue(body, Staff.class);
//edit your dto
long facilityOwnerId = ((Number) request.getAttribute("facilityOwnerId")).longValue();
FacilityOwner facilityOwner = FacilityOwner.builder()
.facilityOwnerId(facilityOwnerId)
.build();
Staff modifiedStaff = Staff.builder()
.facilityOwner(facilityOwner)
.username(staff.getUsername())
.password(new BCryptPasswordEncoder().encode(staff.getPassword()))
.build();
//save your changes to body
this.body = objectMapper.writeValueAsString(modifiedStaff);
}
@Override
public ServletInputStream getInputStream() {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
ServletInputStream servletInputStream = new ServletInputStream() {
public int read() {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
}
Upvotes: 1
Reputation: 749
Here's how I achieved it using the RequestBodyAdvice:
@ControllerAdvice
public class CustomRequestBodyAdvice implements RequestBodyAdvice {
a. support: here, you can control which controller you are targeting, and better which request body by specifying the type of the request body
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
log.info("In supports() method of {}", getClass().getSimpleName());
return methodParameter.getContainingClass() == AuthorController.class && type.getTypeName() == AuthorDTO.class.getTypeName();
}
b. beforeBodyReady
<!-- language: lang-js -->
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
log.info("In beforeBodyRead() method of {}", getClass().getSimpleName());
return httpInputMessage;
}
c. afterBodyRead: here it is where you can modify the request body
<!-- language: lang-js -->
@Override
public Object afterBodyRead(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
log.info("In afterBodyRead() method of {}", getClass().getSimpleName());
if (body instanceof AuthorDTO) {
AuthorDTO authorDTO = (AuthorDTO) body;
authorDTO.setName("Test");
return authorDTO;
}
return body;
}
d. handleEmptyBody
<!-- language: lang-js -->
@Override
public Object handleEmptyBody(Object body, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
log.info("In handleEmptyBody() method of {}", getClass().getSimpleName());
return body;
}
Source: http://www.javabyexamples.com/quick-guide-to-requestbodyadvice-in-spring-mvc
Upvotes: 8
Reputation: 29
One way to this is by reflection. ProceedingJoinPoint contains the args object passed to method
@Aspect
@Component
public class AopInterceptor {
@Around(value = "@annotation(xyz.rpolnx.spring.web.poc.annotation.AopIntercepExample)")
public Object handler(final ProceedingJoinPoint joinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
Object[] args = joinPoint.getArgs();
Class<?> someClass = args[0].getClass();
Field field = someClass.getDeclaredField("custom");
field.setAccessible(true);
field.set(args[0], "custom");
field.setAccessible(false);
return joinPoint.proceed();
}
}
@RestController
public class SimpleController {
@PostMapping("/aop")
@AopIntercepExample
public Person handleAopIntercept(@RequestBody Person nodes) {
return nodes;
}
}
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AopIntercepExample {
}
public class Person {
private String name;
private String id;
private String custom;
}
Upvotes: 2
Reputation: 983
My answer using HTTP Filter.
RequestFilter.java
@Component
public class RequestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
RequestWrapper wrappedRequest = new RequestWrapper((HttpServletRequest) request);
chain.doFilter(wrappedRequest, response);
}
@Override
public void destroy() {
}
}
RequestWrapper.java
public class RequestWrapper extends HttpServletRequestWrapper {
private final String body;
private ObjectMapper objectMapper = new ObjectMapper();
public RequestWrapper(HttpServletRequest request) throws IOException {
// So that other request method behave just like before
super(request);
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
// Store request body content in 'requestBody' variable
String requestBody = stringBuilder.toString();
JsonNode jsonNode = objectMapper.readTree(requestBody);
//TODO -- Update your request body here
//Sample
((ObjectNode) jsonNode).remove("key");
// Finally store updated request body content in 'body' variable
body = jsonNode.toString();
}
@Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
ServletInputStream servletInputStream = new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener listener) {
}
};
return servletInputStream;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
Upvotes: 10
Reputation: 38320
Short Answer
Yes, but not easily.
Details
I know of three options to change the body of a request
"before" it arrives at the handler method in the controller;
Since you are already using spring-boot, option 3, custom Spring HandlerInterceptor, seems like the best option for you.
Here is a link to a Baeldung Article covering spring HandlerInterceptors.
The Baeldung article is not the full answer for your problem
because you can only read the InputStrem
returned by HttpServletRequest
one time.
You will need to create a wrapper class that extends HttpServletRequest
and wrap every request in your wrapper class within your custom HandlerInterceptor or in a custom Filter (Filter might be the way to go here).
Wrapper Class
HttpServletRequest
InputStream in the wrapper class constructorByteArrayOutputStream
.toByteArray
to retrieve the actual byte[]
from the stream.getInputStream
method.byte[]
in a ByteArrayInputStream every time the getInputStream
is called. Return this stream.How To Wrap the Request
Upvotes: 18
Reputation: 189
Another alternative would be adding an attribute to the HttpServletRequest object. And after that you can read that attribute in the Controller class with @RequestAttribute annotation.
In the Interceptor
@Component
public class SimpleInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws ServletException, IOException {
String parameter = request.getParameter("parameter");
if (parameter == "somevalue") {
request.setAttribute("customAttribute", "value");
}
return true;
}
}
In the Controller
@RestController
@RequestMapping("")
public class SampleController {
@RequestMapping(value = "/sample",method = RequestMethod.POST)
public String work(@RequestBody SampleRequest sampleRequest, @RequestAttribute("customAttribute") String customAttribute) {
System.out.println(customAttribute);
return "This works";
}
}
This has advantage of not modifying the request body.
Upvotes: 16