BlueCloud
BlueCloud

Reputation: 583

How to modify request body before reaching controller in spring boot

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

Answers (6)

D. O.
D. O.

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

Amy Doxy
Amy Doxy

Reputation: 749

Here's how I achieved it using the RequestBodyAdvice:

  1. Create a class that implements RequestBodyAdvice and annotate it with @ControllerAdvice
@ControllerAdvice
public class CustomRequestBodyAdvice implements RequestBodyAdvice {
  1. You will have to implements 4 methods:

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

Rodrigo Pereira
Rodrigo Pereira

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

Bijaya Bhaskar Swain
Bijaya Bhaskar Swain

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

DwB
DwB

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;

  1. Use AOP to change the request before the method is called.
  2. Create an HTTP filter.
  3. Create a custom Spring HandlerInterceptor.

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

  1. Read the HttpServletRequest InputStream in the wrapper class constructor
  2. Modify the body per your requirements.
  3. Write the modified body to a ByteArrayOutputStream.
  4. Use toByteArray to retrieve the actual byte[] from the stream.
  5. Close the ByteArrayOutputStream (try-with-resources is good for this).
  6. Override the getInputStream method.
  7. Wrap the byte[] in a ByteArrayInputStream every time the getInputStream is called. Return this stream.

How To Wrap the Request

  1. In your Filter, instantiate your wrapper class and pass in the original request (which is a parameter to the doFilter method).
  2. Pass the wrapper to the chain.doFilter method (not the original request).

Upvotes: 18

Abhinay Dronavally
Abhinay Dronavally

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

Related Questions