OncePerRequestFilter和HttpServletRequestWrapper

1,751 阅读3分钟

场景

  • 修改请求中的参数,某字段加密(接口需要解密后的数据),此处统一解密
  • 请求参数和响应结果,记录日志/存数据库
  • 在请求参数中添加 额外字段(标识某些系统等)

OncePerRequestFilter

GET方式

getParameterMap(),只能够获取到GET请求的参数

//请求参数
parameterMap = httpRequest.getParameterMap();


//下为通用
//请求方式
String requestMethod = httpRequest.getMethod();
String remoteAddr = httpRequest.getRemoteAddr();
int remotePort = httpRequest.getRemotePort();

注意:异常 No modifications are allowed to a locked ParameterMap

//类 org.apache.catalina.util.ParameterMap
@Override
    public V put(K key, V value) {
        checkLocked();
        return delegatedMap.put(key, value);
    }
private void checkLocked() {
    if (locked) {
        throw new IllegalStateException(sm.getString("parameterMap.locked"));
    }
}

//类 org.apache.tomcat.util.res.StringManager
public String getString(String key) {
        if (key == null){
            String msg = "key may not have a null value";
            throw new IllegalArgumentException(msg);
        }

        String str = null;

        try {
            // Avoid NPE if bundle is null and treat it like an MRE
            if (bundle != null) {
                str = bundle.getString(key);
            }
        } catch (MissingResourceException mre) {
            //bad: shouldn't mask an exception the following way:
            //   str = "[cannot find message associated with key '" + key +
            //         "' due to " + mre + "]";
            //     because it hides the fact that the String was missing
            //     from the calling code.
            //good: could just throw the exception (or wrap it in another)
            //      but that would probably cause much havoc on existing
            //      code.
            //better: consistent with container pattern to
            //      simply return null.  Calling code can then do
            //      a null check.
            str = null;
        }

        return str;
    }
    
/*错误码对应文件 tomcat下 org.apache.catalina.util.LocalStrings.properties
    PropertyResourceBundle bundle会在服务启动加载到内存
*/
parameterMap.locked=No modifications are allowed to a locked ParameterMap

//类 java.util.PropertyResourceBundle
private final Map<String,Object> lookup;
public PropertyResourceBundle (Reader reader) throws IOException {
        Properties properties = new Properties();
        properties.load(reader);
        lookup = new HashMap(properties);
    }
public Object handleGetObject(String key) {
    if (key == null) {
        throw new NullPointerException();
    }
    return lookup.get(key);
}

POST方式

POST的请求参数是在请求体body中,而body参数是以流形式存在的。

ServletInputStream inputStream = httpRequest.getInputStream();
InputStreamReader reader = new InputStreamReader(inputStream,StandardCharsets.UTF_8);
BufferedReader bfReader = new BufferedReader(reader);
StringBuilder sb = new StringBuilder();
String line;
while ((line = bfReader.readLine()) != null){
sb.append(line);
}
System.out.println(sb.toString());

注意:异常 request body missing

过滤器获得POST请求参数,但是controller层报错。

httpRequest.getInputStream() 只能使用一次,再次使用报错

InputStream read方法内部有一个postion标志。
当前流读取到的位置,每读取一次,位置就会移动一次,如果读到最后,InputStream.read方法会返回-1。

如果想再次读取,可调用inputstream.reset方法,position就会移动到上次调用mark的位置,mark默认是0,所以就能从头再读。

是否能reset又是由markSupported决定的,为true能reset,为false就不能reset。
从源码可以看到,markSupported是为false的,而且一调用reset就是直接异常。

需要将读取到的InputStream 保存下来,使用HttpServletRequestWrapper进行处理

继承并重写方法

@Component
@Slf4j
public class ParamFilter extends OncePerRequestFilter {

    @Value("${app.self.name}")
    private String selfName;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {

        RequestParameterWrapper requestWrapper=null;
        String method=request.getMethod();
        if("GET".equalsIgnoreCase(method)){
            try {
                requestWrapper = new RequestParameterWrapper(request, RequestMethod.GET);
                Map<String, String[]> parameterMap = new HashMap<>(requestWrapper.getParameterMap());
                log.info("get 参数={}", parameterMap.toString());
                parameterMap.put("selfName", new String[]{selfName});
                requestWrapper.setParameterMap(parameterMap);
            }catch (Exception e){
                e.printStackTrace();
            }
        }else if("POST".equals(method)){
            try {
                requestWrapper = new RequestParameterWrapper(request, RequestMethod.POST);
                //获取post消息体
                String body = requestWrapper.getBody();
                JSONObject jsonObject = new JSONObject(body);
                String dataAuthCode = (String) jsonObject.get("selfName");
                if (StringUtils.isNotBlank(dataAuthCode)) {
                    chain.doFilter(request, response);
                } else {
                    jsonObject.put("selfName", selfName);
                    requestWrapper.setBody(jsonObject);
                }

            }catch (Exception e){
                e.printStackTrace();
            }
        }

        if(requestWrapper == null) {
            chain.doFilter(request, response);
        } else {
            chain.doFilter(requestWrapper, response);
        }
    }
}

继承HttpServletRequestWrapper

public class RequestParameterWrapper extends HttpServletRequestWrapper {

    private String body;

    private Map<String, String[]> parameterMap;

    public RequestParameterWrapper(HttpServletRequest request, RequestMethod method) throws Exception{
        super(request);

        if(RequestMethod.POST.equals(method)){
            StringBuilder stringBuilder = new StringBuilder();
            try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(request.getInputStream(),"UTF-8"));) {
                char[] charBuffer = new char[128];
                int bytesRead;
                while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead);
                }
            } catch (Exception ex) {
                throw ex;
            }
            body = stringBuilder.toString();
        }else if(RequestMethod.GET.equals(method)){
            parameterMap=request.getParameterMap();
        }

    }

    public String getBody() {
        return this.body;
    }

    /**
     * post方式使用
     * @param object
     */
    public void setBody(JSONObject object) {
        this.body=object.toString();
    }



    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
        return new ServletInputStream() {
            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }

            public int read() throws IOException {
                return byteArrayInputStream.read();
            }
        };
    }


    @Override
    public Enumeration<String> getParameterNames() {
        Vector<String> vector = new Vector<String>(parameterMap.keySet());
        return vector.elements();
    }

    @Override
    public String getParameter(String name) {
        String[] results = parameterMap.get(name);
        return results[0];
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return parameterMap;
    }

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

    public void setParameterMap(Map<String, String[]> parameterMap) {
        this.parameterMap = parameterMap;
    }
}