springboot 在controller层获取Post请求中request.getInputStream的值

  这个有点坑,首先request的流的获取每个请求只能获取一次,之后再通过getInputStream获取流的时候就获取不到数据了,还有getInputStream和getReader和getParameter都可以获取输入流数据,但是存在冲突,也就是三者只要有一个对request获取了输入流信息,那么其他的方法之后就获取不到数据了。这就是springboot埋的一个小坑,那么怎么通过request获得Post请求的body值呢?

首先直接在controller层直接写肯定是获取不到的,即使你自己觉得你是第一次获取输入流,其实不然,在springboot启动的时候拦截器中也通过getParameter获取过输入流,导致后面没办法获取输入流

下面解决办法:重写HiddenHttpMethodFilter拦截器

我们先看下这个拦截器里面的方法
@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		HttpServletRequest requestToUse = request;

		if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
			String paramValue = request.getParameter(this.methodParam);
			if (StringUtils.hasLength(paramValue)) {
				requestToUse = new HttpMethodRequestWrapper(request, paramValue);
			}
		}

		filterChain.doFilter(requestToUse, response);
	}

这个request.getParameter()看到了吧,就是它影响了后面获取输入流,我们自己重写这个方法就好了,我自己是在这个重写的方法里将request的输入流获取然后直接用的,我贴下代码

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.springframework.boot.web.filter.OrderedHiddenHttpMethodFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

@Slf4j
@Configuration
public class WebConfig {
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new OrderedHiddenHttpMethodFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
                BufferedReader reader = new BufferedReader(
                        new InputStreamReader(request.getInputStream(), "UTF-8"));
                String line;
                String result = "";
                while ((line = reader.readLine()) != null) {
                    result += line;
                }
                log.info("业务验证提示信息! URL={} 参数:{}", request.getRequestURL().toString(),JSON.toJSON(result));
                filterChain.doFilter(request, response);
            }
        };
    }
}

然后我说一下这个坑:

一:即使重写的方法我们不用输入流,再controller也没办法获取post的输入流,我自己测了好多次都是这样,具体情况不清楚

二:即使将获取的输入流装入session中,在controller中获取session也会出错,获取不到值

三:关注我关注我

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------

经过我的测试,我们发现这样虽然能够取到post输入流,但是这样之后cotroller用于接参的对象将取不到数据,会报request body is missing.那么大家就别用这种方式取输入流吧,通过aop去获取输入参数也应该行,我没试过,但思路是对的!!!!

<think>好的,我现在需要解决用户关于Spring HandlerInterceptor中第二次调用request.getParameter()返回null的问题。首先,我得理解用户的疑问,然后逐步分析可能的原因和解决方案。 用户提到在HandlerInterceptor中,第二次获取请求参数时得到null。这可能是因为请求输入流只能被读取一次,之后流的位置已经到达末尾,再次读取时自然没有数据了。我需要回忆一下Servlet的Request对象的工作机制。 首先,确认一下request.getParameter()的工作原理。当调用request.getParameter()时,Servlet容器会解析请求体中的数据,并将参数存储在一个Map中。但如果请求体是输入流(比如POST请求中的表单数据),一旦输入流被读取过一次,后续的读取就会返回null,因为流不可重复读取。 接下来,考虑HandlerInterceptor中的情况。如果在拦截器的preHandle方法中调用了request.getParameter(),这可能已经触发了输入流的读取。如果在后续的处理中(比如Controller或其他拦截器)再次调用getParameter(),这时候可能会因为流已经关闭而导致无法读取数据,进而返回null。 然后,需要验证这个猜想。用户可能是在拦截器里多次调用getParameter(),或者在拦截器和Controller中都调用了这个方法。这时候第二次调用就会失败。此外,如果请求是PUT或POST,并且参数是通过请求体传递的,这种情况更容易出现。 接下来,思考解决方案。常见的办法是将请求体缓存起来,以便多次读取。可以使用HttpServletRequest的装饰器模式,将输入流的内容读取并保存到一个字节数组或字符串中,之后通过重写getInputStream()和getReader()方法,使得每次读取都从这个缓存中获取数据。这样,即使多次调用getParameter(),也能正确获取参数。 另外,需要注意Spring的某些组件(比如@RequestBody)可能会提前读取输入流,导致后续无法读取。这时候即使没有在拦截器中调用getParameter(),也可能出现同样的问题。这时候同样需要包装请求对象来缓存请求体。 还需要提醒用户,在实现缓存时,要确保线程安全,并且正确处理字符编码等问题。同时,应避免在拦截器中过早读取参数,除非确实需要,否则最好让参数在Controller中处理,或者在需要时使用包装后的请求对象。 最后,可能需要提供具体的代码示例,帮助用户理解如何实现请求的包装和缓存。例如,可以自定义一个ContentCachingRequestWrapper,并在拦截器中将原始请求替换为该包装器,确保后续的读取操作都是基于缓存的副本。 总结一下,问题的核心在于请求体的输入流只能读取一次,导致后续的getParameter()调用失效。解决方案是缓存请求体,使得可以多次读取。用户需要在自己的拦截器中对请求进行包装,并在后续处理中使用包装后的请求对象。</think>在Spring框架中,使用`HandlerInterceptor`时,若第二次调用`request.getParameter()`返回`null`,通常是因为**HTTP请求输入流(`InputStream`)只能被读取一次**。以下是逐步分析及解决方案: --- ### **问题原因** 1. **Servlet请求体的单次读取特性** 当HTTP请求的`InputStream`被读取后(例如通过`request.getParameter()`或`request.getReader()`),流的位置会到达末尾。后续再尝试读取时,流中已无数据,导致返回`null`。 2. **触发流读取的常见场景** - 在`HandlerInterceptor`的`preHandle()`方法中调用了`request.getParameter()`。 - 在过滤器链(Filter)或其他拦截器中提前读取了请求体。 - 使用`@RequestBody`注解的参数处理(Spring会提前读取流)。 --- ### **解决方案** #### 1. **缓存请求体(推荐)** 通过包装`HttpServletRequest`,将请求体内容缓存到内存中,支持多次读取。 **实现步骤:** 1. **创建自定义的`HttpServletRequestWrapper`** ```java public class ContentCachingRequestWrapper extends HttpServletRequestWrapper { private final ByteArrayOutputStream cachedContent; public ContentCachingRequestWrapper(HttpServletRequest request) throws IOException { super(request); cachedContent = new ByteArrayOutputStream(); // 将原始请求体内容复制到缓存 IOUtils.copy(request.getInputStream(), cachedContent); } @Override public ServletInputStream getInputStream() { // 从缓存中返回新的输入流 return new CachedServletInputStream(cachedContent.toByteArray()); } @Override public BufferedReader getReader() { // 从缓存中返回新的Reader return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(cachedContent.toByteArray()))); } } ``` 2. **在拦截器中替换原始请求** ```java @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 包装原始请求 ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); // 后续操作使用包装后的请求 String param = wrappedRequest.getParameter("key"); return true; } ``` 3. **确保后续处理使用包装后的请求** 在Controller或其他组件中,确保使用的是包装后的`request`对象。 --- #### 2. **避免在拦截器中读取参数** 如果不需要在拦截器中提前处理参数,尽量避免调用`request.getParameter()`,将参数处理逻辑移到Controller中。 --- #### 3. **检查请求方法类型** - **GET请求**:参数通过URL传递,`request.getParameter()`不会触发流读取,可多次调用。 - **POST/PUT请求**:参数在请求体中,必须通过缓存方案解决。 --- ### **代码示例** ```java public class CacheRequestBodyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 包装请求以缓存请求体 ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request); // 第一次读取参数(正常) String firstRead = wrappedRequest.getParameter("param"); // 第二次读取参数(正常,因为缓存) String secondRead = wrappedRequest.getParameter("param"); // 将包装后的请求传递给后续流程 request = wrappedRequest; return true; } } ``` --- ### **注意事项** 1. **性能影响** 缓存请求体会增加内存消耗,需权衡是否必要。 2. **文件上传请求** 若请求包含大文件,缓存可能导致内存溢出,需特殊处理。 通过上述方案,可解决`HandlerInterceptor`中多次调用`request.getParameter()`返回`null`的问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值