【踩坑】:request.getInputStream()获取的值为空

1.【踩坑】:request.getInputStream()获取的值为空

场景描述

需求:前端发送请求为POST,请求的context-type强制为"application/x-www-form-urlencoded"

传输的数据包含userName,deptName,groupNanme三个参数,需要在Filter内将三个参数的值合并起来为一个新的参数fullName,然后将该参数加入到request请求传输的数据内,传输给controller方法

出现的情况

无论是在filter还是在controller内调用request.getInputStream(),发现获得的InputStream流内的数据都是null,这个让我一时间十分懵逼,于是我参考网上资料和博客。

资料

1.request的inputStream是只能被读取一次,当读取一次后,之后再次读取request的inputStream的话那就只能读取为空。
2.inputStream只能被读取一次的原因是,该类内部定义了一个指针,该指针指向当前读取内容的位置,当第一次读取完毕后,该指针指向数据末尾,所以之后再次读取的话就只能读取为null
3.当请求以表单格式传输数据的时候,数据初始都是保存在inputStream内的
4.当第一次调用request.getParameter()或使用注解@RequestParameter时,会从ParameterMap内获取值,ParameterMap则是SpringMVC从request的inputStream内获取数据创建的(所以当ParameterMap创建的时候就代表request的inputStream已经被读取一次了)
5.request.getParameter()和使用注解@RequestParameter都是从ParameterMap内获取值
6.request.getParameter()是调用getParameter方法从ParameterMap内获取值;注解@RequestParameter是调用getParameterValue方法从ParameterMap内获取值的
7.@RequestBody获取的是请求内body的内容,适用于请求context-type为json的情况,不适用表单上传的情况
8.@RequestParameter获取的是ParameterMap内的数据,适用于context-type为application/x-www-form-urlencoded(表单)的情况

解决过程

    1. 网络上给出的解决方案大多是自定义一个继承HttpServletRequestWrapper类的wrapper类,该类可以作为request类的包装类,在该类内部将request的inputStream缓存,然后重写getInputStream方法,外部调用该方法的时候返回的是缓存的数据而不是父类默认的数据,在filter放行的时候代替request请求传输下去。
    1. 我之后采纳了该方案,但是结果却差强人意,还是获取不到数据,但是理论上应该是获取得到数据才对的,于是我就打上了断点调试程序对request进行监控。结果我发现当我request请求进入filter的时候他的inputStream的数据就已经为了,这个时候我又测试发现了ParameterMaprequest加入filter的时候就已经有数据了

      1)该方案会使request的inputStream可以重复读取

      2)该方案内部定义了一个byte[] 数组缓存inputStream内的数据,重写getInputStream方法默认返回byte[]数组的数据

    1. 在发现以上情况后,虽然我不是很了解为什么我没有调用request.getInputStream()和使用注解@RequestParameter,ParameterMap还是会创建读取InputStream。但是我也算是了解该问题出现的核心问题:request的inputStream的值提前被读取到ParameterMap内,那么我直接从ParameterMap内获取值不就行了,在读取值之后将值缓存到wrapper类的byte[]数组就ok了
    1. 于是我修改了下wrapper
      • 增加了属性private final Map allParameters = new TreeMap<>()来代替原先的ParameterMap
      • 并且写了个构造函数来获取外界传入的ParameterMap从而给内部定义的allParameters赋值
      • 重写了getParameter,getParameterMap,getParameterNames,getParameterValues几个方法(因为我使用自定义的map来代替原生的ParameterMap所以为了之后能正常获取map内的值,需要重写这些相关的方法)
    1. 调试程序,解决问题!

程序代码

myFilter

@Slf4j
public class myFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        EnhancedHttpRequest wrapper;
        // 注意: 原生parameterMap是添加了final关键字的的,无法对其本身的数据进行更改
        Map<String, String[]> parameterMap = httpServletRequest.getParameterMap();
        //该map用来复制承接parameterMap的数据
        Map<String, String[]> parameterMap2 = new HashMap<>();
        parameterMap2.putAll(parameterMap);
        // fullName = userName+deptName+groupName
        Enumeration<String> parameterNames = httpServletRequest.getParameterNames();
        StringBuilder builder = new StringBuilder();
        while (parameterNames.hasMoreElements()){
            String s = parameterNames.nextElement();
            String parameterValue = httpServletRequest.getParameter(s);
            builder.append(parameterValue);
        }
        // 将fullName加入到map内
        parameterMap2.put("fullName",new String[]{builder.toString()});
        // 创建wrapper,将修改过的map传入构造函数,缓存map和inputStream
        wrapper = new EnhancedHttpRequest(httpServletRequest,parameterMap2);
        // 注意:放行的是wrapper而不是request了
        filterChain.doFilter(wrapper,httpServletResponse);
    }
}

EnhancedHttpRequest

@Slf4j
// 自定义一个HttpServletRequest的包装类,在内部实现对request的body内容的特性化处理
public class EnhancedHttpRequest extends HttpServletRequestWrapper
{
    // 自定义ParameterMap来代替默认的map
    private final Map<String, String[]> allParameters = new TreeMap<>();
    // 用于缓存inputStream流的数据 然后重写getInputStream方法,返回该body的数据
    private byte[] body;
    public EnhancedHttpRequest(HttpServletRequest request,Map<String, String[]> map){
        super(request);
        allParameters.putAll(map);
        // 将map内数据缓存到InputStream内
        StringBuilder builder = new StringBuilder();
        for (Map.Entry<String, String[]> stringEntry : allParameters.entrySet()) {
            String key = stringEntry.getKey();
            for(String value:stringEntry.getValue()){
                builder.append(key+"="+value+"&");
            }
        }
        String temp = builder.toString();
        body = temp.substring(0,temp.length()-1).getBytes();


    }
	// 重写getInputStream 设置返回的是wrapper类内部缓存的body的数据
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return inputStream.read();
            }
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }
    // ****
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
    // 根据key获取参数值
    @Override
    public String getParameter(final String name)
    {
        return getParameterMap().containsKey(name)?getParameterMap().get(name)[0]:null;
    }

    // 返回一个不可修改的ParameterMap集合
    @Override
    public Map<String, String[]> getParameterMap()
    {
        return Collections.unmodifiableMap(allParameters);
    }

    @Override
    public Enumeration<String> getParameterNames()
    {
        return Collections.enumeration(getParameterMap().keySet());
    }

    @Override
    public String[] getParameterValues(final String name)
    {
        return getParameterMap().get(name);
    }
}

controller方法:

@PostMapping(value = "/test/{userId}/{deptId}",consumes = {"application/x-www-form-urlencoded"})
    public myResult testMethod(
            @PathVariable Long userId,
            @PathVariable Long deptId,
            @RequestParam String fullName,HttpServletRequest request) throws IOException {
        log.info("token:{}",request.getAttribute("token"));
        log.info("userId:{},deptId:{},fullName:{}",userId,deptId,fullName);
        byte[] b = new byte[1024];
        request.getInputStream().read(b);
        return new myResult().ok(fullName);
    }

你可能感兴趣的:(踩坑记录,spring,java,后端)