How to gracefully read and write the request body of HttpServletRequest and HttpServletResponse

Recently, many interactions have to deal with native HttpServletRequest and HttpServletResponse. Read the body data from HttpServletRequest and encapsulate it into a data structure; Write data to HttpServletResponse and respond. The traditional way of writing is not elegant. Today I'll introduce you a more elegant way.

HttpMessageConverter

HttpMessageConverter is a message converter model provided by the Spring framework, which is a policy interface for converting between HTTP requests and responses. It can read the input message HttpInputMessage; You can also write the output message HttpOutputMessage.

The message transformation of Spring MVC is completed through the implementation of this interface. There are many implementations of HttpMessageConverter:

Generally, the handling of Form submission, JSON, XML, string and even Protobuf in Spring MVC is completed by the implementation of HttpMessageConverter. The body parameters passed from the front end to the back end and the data returned from the back end to the front end are converted by this interface. In Spring IoC (Spring MVC environment), there is also a container for HttpMessageConverters. HttpMessageConverters:

    @Bean
    @ConditionalOnMissingBean
    public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
        return new HttpMessageConverters((Collection)converters.orderedStream().collect(Collectors.toList()));
    }

We can use it directly. So how to use it? First, find out what HttpInputMessage and HttpOutputMessage are used for.

HttpInputMessage

HttpInputMessage represents an HTTP input message, which is composed of a request header and a readable request body. It is usually implemented by the server-side HTTP request handle or the client-side HTTP response handle.

HttpServletRequest is the extension interface of ServletRequest, which provides the request information of HTTP Servlet and also includes the request header and request body, so the two are related. As long as we find out the actual relationship between the two, we can let the HttpMessageConverter read and process the request information carried by HttpServletRequest.

ServletServerHttpRequest

To tell you the truth, I really found:

ServletServerHttpRequest is not only the implementation of HttpInputMessage, but also holds an HttpServletRequest instance property. All operations of ServletServerHttpRequest are based on HttpServletRequest. We can inject an HttpServletRequest instance into it by constructing, so that the HttpMessageConverter can indirectly process HttpServletRequest.

Actual combat of extracting request body

The focus here is to use HttpMessageConverter in Servlet filter. It is not recommended to operate HttpServletRequest in Spring MVC. I chose FormHttpMessageConverter, which is usually used to process application/x-www-form-urlencoded requests. We write a filter to intercept the request and extract the body:

/**
 * Processing application/x-www-form-urlencoded requests
 *
 * @author  felord.cn
 */

@Component
public class FormUrlencodedFilter implements Filter {
    private final FormHttpMessageConverter formHttpMessageConverter = new FormHttpMessageConverter();
    private static final Logger log = LoggerFactory.getLogger(FormUrlencodedFilter.class);

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException {
        String contentType = request.getContentType();
        MediaType type= StringUtils.hasText(contentType)? MediaType.valueOf(contentType):null;
        ServletServerHttpRequest serverHttpRequest = new ServletServerHttpRequest((HttpServletRequest) request);
        
        if (formHttpMessageConverter.canRead(MultiValueMap.class,type)) {
            MultiValueMap<String, String> read = formHttpMessageConverter.read(null, serverHttpRequest);
             log.info("Print the read request body:{}",read);
        }
    }
}

Then execute a POST request with content type of application/x-www-form-urlencoded:

POST /ind HTTP/1.1
Host: localhost:8080
Content-Type: application/x-www-form-urlencoded
Content-Length: 20

a=b123&c=d123&e=f123

The console will print:

2021-12-30 6:43:56.409  INFO 12408 --- [nio-8080-exec-1] sfds: Print the read request body:{a=[b123], c=[d123], e=[f123]}

ServletServerHttpResponse

If there is a ServletServerHttpRequest, there is a ServletServerHttpResponse. The general principle is similar. It is just opposite to ServletServerHttpRequest. If we need to deal with the response problem, for example, if we want to write a JSON response through HttpServletResponse, we can probably write as follows:

ServletServerHttpResponse servletServerHttpResponse = new ServletServerHttpResponse(response);
// Using json converter
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
//  authentication refers to the object instance that needs to be written
mappingJackson2HttpMessageConverter.write(authentication, MediaType.APPLICATION_JSON,servletServerHttpResponse);

summary

HttpMessageConverter abstracts the strategy of HTTP message conversion, which can help us deal with some request response problems gracefully. However, it should be noted that the request body can only be read once. Even if it is wrapped in ServletServerHttpRequest, pay attention to the difference between it and HttpServletRequestWrapper.

Pay attention to the official account: Felordcn for more information

Personal blog: https://felord.cn

Keywords: Java

Added by joejoejoe on Sat, 01 Jan 2022 15:17:02 +0200