美文网首页
Servlet输入流输出流只能消费一次的问题

Servlet输入流输出流只能消费一次的问题

作者: 我本佛山人 | 来源:发表于2017-09-04 16:04 被阅读0次
    image.png

    servlet的requestbody以及response的body一旦流被读取了,就无法再次消费了,因此这对于有要拦截请求,记录相关信息的时候,带来一个潜在的坑。那么如何处理这个呢,利用filter,wrapper一层,然后proceed,最后response完之后在把cached的body设置回原始响应。如果你的项目应用到了Spring,那么Spring已帮你实现了此类需求。直接上代码(基于Spring BOOT)

    1、拦截器

    /**
     * Filter配置
     *
     * @author lengyu
     * @date 2017-04-21 21:56
     */
    @Configuration
    public class FilterConfig {
        @Bean
        public FilterRegistrationBean streamFilterRegistration() {
            FilterRegistrationBean registration = new FilterRegistrationBean();
            registration.setDispatcherTypes(DispatcherType.REQUEST);
            registration.setFilter(new StreamFilter());
            registration.addUrlPatterns("/*");
            registration.setName("streamFilter");
            registration.setOrder(3);
            return registration;
        }
    }
    

    2、拦截器实现类

    /**
     * 使用ContentCachingRequestWrapper来包装HttpServletRequest,
     * 使用ContentCachingResponseWrapper来包装HttpServletResponse,
     *
     * @author lengyu
     * @date
     */
    public class StreamFilter implements Filter {
        private static final Logger log = LoggerFactory.getLogger(StreamFilter.class);
    
        @Override
        public void init(FilterConfig config) throws ServletException {
        }
    
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            HttpServletRequest req = (HttpServletRequest) request;
            String contentType = request.getContentType();
            HttpServletResponse resp = (HttpServletResponse) response;
            String method = req.getMethod();
    
            boolean hasApiUri = req.getRequestURI().startsWith("/api/");
            boolean auth = StringUtils.isBlank(HttpContextUtils.getRequestToken(req, Const.AUTHORIZATION_KEY));
            if ((!(request instanceof HttpServletRequest) || auth) && !hasApiUri) {
                chain.doFilter(request, response);
                return;
            }
    
            if ((contentType != null) && (contentType.startsWith("multipart/form-data")) && ("POST".equalsIgnoreCase(method))) {
                log.info("==> 当前请求为文件上传,不作请求日志收集");
            } else {
                Date startTime = new Date();
                ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(req);
                ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(resp);
                try {
                    chain.doFilter(requestWrapper, responseWrapper);
                } finally {
                    String requestBody = req.getQueryString();
                    if (StringUtils.isEmpty(requestBody)) {
                        requestBody = new String(requestWrapper.getContentAsByteArray());
                    }
                    String responseBody = new String(responseWrapper.getContentAsByteArray());
                    LogInfo logInfo = LogInfo.build(req, startTime, requestBody, responseBody);
                    String json = logInfo.toJson();
    
                    VhscProperties vhscProperties = (VhscProperties) SpringHelper.getBean("vhscProperties");
                    boolean isNotice = logInfo.getWasteTime() > vhscProperties.getRespNoticeTime();
                    log.info("[日志归档] - {}", json);
                    if (isNotice) {
                        log.warn("[性能告警] - {}", json);
                    }
                    responseWrapper.copyBodyToResponse();
                }
            }
        }
        @Override
        public void destroy() {
        }
    }
    

    3、日志BEAN

    
    /**
     * 请求日志信息收集
     *
     * @author lengyu
     * @version 2.0
     * @date 2017/9/2 22:45
     */
    public class LogInfo implements Serializable {
        private String threadToken;//请求线程ID
        private String scheme;//协议
        private String host;//主机
        private int port;//端口
        private String method;//请求方法
        private String path;//请求路径
        private Object params;//请求参数
        private Object result;//返回值
        private String clientIp;//客户端IP
        private String serverIp;//当前服务节点IP
        private Map<String, String> headers;//请求头
        private String remoteUser;//当前登陆人
    
        @JSONField(format = "yyyy-MM-dd HH:mm:ss.SSS")
        private Date startTime = new Date();//请求开始时间
        @JSONField(format = "yyyy-MM-dd HH:mm:ss.SSS")
        private Date endTime;//请求结束时间
    
        public String toJson() {
            return JSONObject.toJSONString(this);
        }
    
    
        /**
         * 消耗时长=请求开始时间-请求结束时间
         *
         * @return
         */
        public Long getWasteTime() {
            return this.endTime.getTime() - this.startTime.getTime();
        }
    
        /**
         * 生成日志对象
         *
         * @param req
         */
        public static LogInfo build(HttpServletRequest req, Date startTime, String requestBody, String responseBody) {
            LogInfo info = new LogInfo();
            info.threadToken = LogThreadTokenConvert.getThreadToken();
            info.scheme = req.getScheme();
            info.host = req.getServerName();
            info.port = req.getServerPort();
            info.path = req.getRequestURI();
            info.method = req.getMethod();
            info.clientIp = IPUtils.getIpAddr(req);
            info.serverIp = req.getLocalAddr();
            info.startTime = startTime;
            info.endTime = new Date();
    
            try {
                SysUserEntity sysUserEntity = ShiroUtils.getUserEntity();
                if (sysUserEntity != null) {
                    info.remoteUser = sysUserEntity.getUserId() + ":" + sysUserEntity.getUsername();
                }
            } catch (Exception e) {
                info.remoteUser = req.getRemoteUser();
                e.printStackTrace();
            }
    
            VhscProperties vp = (VhscProperties) SpringHelper.getBean("vhscProperties");
            if (vp.getHasHeaders()) {
                info.headers = getHeaderMap(req);
            }
            if (vp.getHasParams()) {
                try {
                    info.params = JSONObject.parseObject(requestBody);
                } catch (JSONException e) {
                    info.params = requestBody;
                }
            }
            if (vp.getHasResult()) {
                try {
                    info.result = JSONObject.parseObject(responseBody);
                } catch (JSONException e) {
                    info.result = responseBody;
                }
            }
            return info;
        }
    
        public static Map<String, String> getHeaderMap(HttpServletRequest req) {
            Map<String, String> map = new HashMap<>();
            Enumeration enu = req.getHeaderNames();//取得全部头信息
            while (enu.hasMoreElements()) {//以此取出头Name
                String headerName = (String) enu.nextElement();
                String headerValue = req.getHeader(headerName);//取出头信息Value
                map.put(headerName, headerValue);
            }
            return map;
        }
    /***省略GET/SET***/
    }
    

    相关文章

      网友评论

          本文标题:Servlet输入流输出流只能消费一次的问题

          本文链接:https://www.haomeiwen.com/subject/otuejxtx.html