调用 request.getParameter 之后再调用 request.getInputStream 取不到流数据

0x00 问题描述 & 背景

随着项目的推进,增加了权限控制模块,具体控制到接口层(controller)。每次调用接口时需要校验用户是否有权限。用的方案是spring的拦截器。

前端(APP)所有的请求都会携带参数sessionId,作为用户的标识。其中Get请求参数是通过url传递的,post请求参数是通过二进制流传递的。

拦截器在获取sessionId的时候先执行request.getParameter("sessionId"),如果没有拿到sessionId,则调用request.getInputStream()获取流信息。具体代码如下:

private String getSessionId(HttpServletRequest request) {
    String sessionId = request.getParameter("sessionId");
    if (sessionId == null) {
        // 参数也可能为sessionid
        sessionId = request.getParameter("sessionid");
    }
    if (sessionId == null) {
        // post请求
        String data = getRequestStringData(request);
        System.out.println("data = " + data);
        if (data.length() > 0) {
            // TODO get SeesionId from json
        }
    }
    return sessionId;
}

private String getRequestStringData(HttpServletRequest request) {
    StringBuffer info = new java.lang.StringBuffer();
    InputStream in = null;
    try {
        in = request.getInputStream();
        BufferedInputStream buf = new BufferedInputStream(in);
        byte[] buffer = new byte[1024];
        int iRead;
        while ((iRead = buf.read(buffer)) != -1) {
            info.append(new String(buffer, 0, iRead, "utf-8"));
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            if (null != in) {
                in.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return info.toString();
}

测试发现post请求始终拿不到sessionId,加点日志发现整个流信息都为空。

0x01 问题排查过程

  1. 用postman模拟App请求,发现能拿到流信息,初步怀疑是App的同事改了post请求发送方式;
  2. App的同事(Android和iOS)都说没有动过,并且断点调试,确实正确发送了请求;
  3. java端注掉拦截器,post请求直接达到controller,controller自然是直接获取流信息的,没有问题,能够正确拿到流信息;
  4. 查看api文档,api文档中有这样一句:

    If the parameter data was sent in the request body, such as occurs with an HTTP POST request, then reading the body directly via getInputStream() or getReader() can interfere with the execution of this method.
    
  5. 问题产生的原因已经明了,getParameter方法会影响getInputStream方法获取流信息。

0x02 解决方案

在获取sessionId的时候先判断是Post请求还是Get请求,如果是Get请求就调用getParameter方法,Post请求则调用getInputStream方法。

还有一点需要注意,流数据只能读一次,在拦截器中获取完流数据之后需要回写到request,以便controller使用。代码如下:

private String getSessionId(HttpServletRequest request) {
    String sessionId = "";
    logger.info("请求方式:"+request.getMethod());
    if ("GET".equalsIgnoreCase(request.getMethod())) {
        sessionId = request.getParameter("sessionId");
        if (sessionId == null) {
            // 参数也可能为sessionid
            sessionId = request.getParameter("sessionid");
        }
    } else if ("POST".equalsIgnoreCase(request.getMethod())) {
        // post请求
        String data = getRequestStringData(request);
        request.setAttribute("postData", data); // 流数据回写
        if (data.length() > 0) {
            JSONObject jsonObject = JSONObject.fromObject(data);
            sessionId = jsonObject.getString("sessionId");
        }
    }
    return sessionId;
}

0x03 后记 & 疑问

  1. 如果哪位看官有更深的理解,欢迎指教。
  2. App端都是调用自带SDK发送的post请求,在问题修复之前,为什么App发送的请求息拿不到流信息而postman发送的则能拿到?烦请有研究的同学告知。

你可能感兴趣的:(错题集)