Tomcat 日志过滤器未正确编码响应正文

Tomcat log filter not encoding response body properly

当为 Tomcat 将日志过滤器从使用 javax.servlet.servlet-api:2.5 调整为 3.0.1 时,我 运行 遇到了问题。

部分日志消息以 UTF-8 正确编码,而另一部分存在一些编码问题(未知编码)。

问题:bufferedResponse.getContent() returns一个st运行gely编码的字符串

输出:

713901 [http-bio-8080-exec-7] ERROR com.example.filters.ReqRespDumpFilter  - java.lang.NullPointerException
713962 [http-bio-8080-exec-7] DEBUG com.example.filters.ReqRespDumpFilter  - REST Request - [HTTP METHOD:GET] [PATH INFO:null] [REQUEST PARAMETERS:{}] [REQUEST BODY:] [REMOTE ADDRESS:0:0:0:0:0:0:0:1] [RESPONSE:  (�h�h&  ��
  �v
  �00h�"00�.)00��7( �����������������������������������������������������������x��x�x���w�w�w������x��w�xx��x��wx�������������x�����������������������������������������������( @���������������������������������������������������������������������������q��e��l��n��f��s��n��n��y��p��q��n��y��u��n����q��|��n��k��d��]��W��]��X��C��J��N��V��O��<��5��*��,����|}~uz}ft~\mwYYY "$���BBBBBBBBBBBBBBBBBBBBB-BB,/4BB(=A'2:1BB?7379;8.%BB(38820BB
>65!$BB ,!#BB@
 !BB&,*&+BB=    <BBBBBBBBBBBBBBBBBBBBB��������������������������������( @��������������������������������������������������������������������������������������������������������������������嘥�d��������i����������������������Ӑ��W��N��������������~��tz~ "$������n��D��,��X�����������������\mw8��J��?��*����7��]��y����������������������I��4��4��A��]��q��e����������������������ft~O��V��s��p��n��n�㓙���������������������ݹ��m��l��p��q��n�݉�����������������YYY���������d��n��k��f��y��������������������������u��j�����|��q��n��������������������������vy{������������|}~�����������������������������������������������������������������������������������������������������������������������( @����������������������������������������������������������������������������������������������������������������x��������������x������������w������p��x��w������x��wwx�����x�����wwx�������w�wwww��������w��wwx�������x���www��������������ww�����������������������w���x�����������w�����������w�x�x���x������wxw�w�����x��xxwww�����w�����w��������������wx�����������w�x�����������������������x�������������������������������������������������������������������( @�������������������������������������������������������������p��c��i��a��c��i��j��k��d��Z��]��U��T��^��k��q��r��p��q��r��m��n��n��n��q��q��o��n��m��n��l��{��~��j��j��k��e��h��e��Q��J��B��K��G��K��O��X��Z��S��0��:��;��3��6��7��+��'��"��.����s|�oy�gx�gw�S}�^z�]v�Rz�Uv�Ss�{||qy~sssku{mptgqwjnranxbmtiiiccc]t~[ozXksVlxRhtUdiMbnZ_dLZbYYYQY^PWYUUUHX_FSZMMMCCC>DF<<<59:048+./���|||||||||||||||||||||||||||||||||||||||||


RBCp||l=D   b@G@U
||B=nn
]AKJP||>ad|Zf>HHG]||b<t|q]X=KKNDo||V>Q
u||cSX>HLEENAV||    D=mTWYk>=GNLKHHKGTm||mGMMGTLENHHHHK=g5b||^;LKGXGHHHHHK?e#6||
TDGIHHNHNLGh9||

xGG>L=F=<g'$('`||Px@DoIAsv*+e0)jn   ||
    l|YOGg$/g0e2"_||
[CAV,,)17||Z:!1V/0e./||Zqn
6-,/4||
    \||tZ.*/%)P||[*5( ]||P28+&{5*02]||32|c  

Z|63||xwnz||    \Z
|||||||||||||||||||||||||||||||||||||||||( @�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������gx�X��Y��PWYZ^cA��S�����������������������������������������������������������������cntJ��=��L��^z�������X��@��������������������������XXXXXX�����������������������뮮�iv}O��-��7��q|����������������K��anx���������������cccxxx��������������������Ո��[ozK��5��5��8��ju{������������������bovK��������������NNNSSS��������������۠��ksxUw�C��*��+��.��S��QY^������������������������Zv�J��oy����CCCjjj���������gw�Vv�K��5��'��0��0��,��O��_t�������������������������������T��G��K[eT�Rz�Ss�MbnK��B��8��.��'��-��1��3��-��9��Q|�OZ`������������������������������������IZa=��"��"��9��S{�'��0��-��4��3��3��3��*��C��Siui��`kq������������������������������������mptQ��'��+��8��Tv�;��4��3��3��3��3��*��G��[sq��X��b��m�������������������������������؟��������S~�R��;��7��0��3��.��3��.��'��<��Vlxp��X��a��]��_��h�����������������������������������������69:>��<��J��'��@��:��B��H��Qhts��Z��p��l��p��c��j��jnr���������������������������������������w}�048L��S��HX_4��O��FSZ>DFm��X��n��]s}m��_��l��Udi[[[�����������������㓓����������������������Z_dSr���9��Rhtt��`��m��Xksl��^t~k��c��k��gqw�����������������ꌌ����������������������������px}Z��N��]u�c��]��q��q��h��o��n��a��^��k��������������������������������������������������������zzze��^��l��n��_w�l��m��^t~o��b��n����������������������҉��������������VVVXXX������������h��f��d��h��q��k��a��q��o��f��m��~��������������������������������tttKKK���������y|~e��`��o��o��o��h��b��c��p��n��c��s{���������������������������������ٚ�����������������rze��X��d��n��i��j��n��X��U��T��d��jtz���������������������������������������������������q{�f��l��e��m��q��l��l��n��l��g��mw}���������������������������������������������������{��l��hhh��ó�����������{{{h��{��������������������������������������������������������49:<<<���������������������ZZZ+./���������������������������������������������������������qqq������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������(0`��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������ww�����������������x�������������������x�wx��x�����������������w����x�������������w�w��px�������������ww���������pw����x��w�x��������������ww��������������www�x�����������wwwwx��������w���wwwwwwx�����������ww�wwwwwwx�����������www�wwwwww�������������ww�wwwwwx�p�����������wwwww��ww�����������wwwwww���x��������x�x�����xx�����������������x�x�������������������x�������������wx�p�����������������x�wxw��x���������������wx�w�w���������������p������������p��x�p����xw������������x�����������x�������x��ww�x��������x�������ww�w�x������������������wx��������������p��x����������������www�x�������������x�������������������p������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������(0`�
���������������������������������������������������d��k��a��e��j��k��l��e��\��[��R��l��q��p��p��q��q��p��n��o��o��o��o��A��p��q��n��o��n��n��n��l��n��j��i��g��g��f��e��Q��I��K��D��J��M��Q��0��;��3��3��6��+��'��"��.��*��%��!����?��d~�a{�R|�_w�Sy�Sv�Sq�{{{tttat}kkkccc]r|[oyQl|WiqPfs_abUemUaeOfrNcn]^aQ]dL^hLZcHWaGX`GWa]^^TXZUUUHX_KU[LQTGT\IOTCMRKKKDJLAFJFFF>EK<CF9=A7>B<<<38:47:012.23./0*,.'))&')"
(the output is too long)
]

servlet-api:3.0.1中为HttpServletResponse新增的方法:

    @Override
    public int getStatus() {
        return original.getStatus();
    }

    @Override
    public String getHeader(String s) {
        return original.getHeader(s);
    }

    @Override
    public Collection<String> getHeaders(String s) {
        return original.getHeaders(s);
    }

    @Override
    public Collection<String> getHeaderNames() {
        return original.getHeaderNames();
    }

过滤代码:

package com.example.filters;

import java.io.*;
import java.util.*;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletInputStream;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.output.TeeOutputStream;
import org.apache.log4j.Logger;

public class ReqRespDumpFilter implements Filter {


    private static final Logger logger = Logger.getLogger(ReqRespDumpFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        try {
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;

            Map<String, String> requestMap = this.getTypesafeRequestMap(httpServletRequest);
            BufferedRequestWrapper bufferedReqest = new BufferedRequestWrapper(httpServletRequest);
            BufferedResponseWrapper bufferedResponse = new BufferedResponseWrapper(httpServletResponse);

            final StringBuilder logMessage = new StringBuilder("REST Request - ")
                    .append("[HTTP METHOD:")
                    .append(httpServletRequest.getMethod())
                    .append("] [PATH INFO:")
                    .append(httpServletRequest.getPathInfo())
                    .append("] [REQUEST PARAMETERS:")
                    .append(requestMap)
                    .append("] [REQUEST BODY:")
                    .append(bufferedReqest.getRequestBody())
                    .append("] [REMOTE ADDRESS:")
                    .append(httpServletRequest.getRemoteAddr())
                    .append("]");

            chain.doFilter(bufferedReqest, bufferedResponse);
            logMessage.append(" [RESPONSE:").append(bufferedResponse.getContent()).append("]");
            logger.debug(logMessage);
        } catch (Throwable a) {
            logger.error(a);
        }
    }


    private Map<String, String> getTypesafeRequestMap(HttpServletRequest request) {
        Map<String, String> typesafeRequestMap = new HashMap<String, String>();
        Enumeration<?> requestParamNames = request.getParameterNames();
        while (requestParamNames.hasMoreElements()) {
            String requestParamName = (String) requestParamNames.nextElement();
            String requestParamValue = request.getParameter(requestParamName);
            typesafeRequestMap.put(requestParamName, requestParamValue);
        }
        return typesafeRequestMap;
    }


    @Override
    public void destroy() {
    }


    private static final class BufferedRequestWrapper extends HttpServletRequestWrapper {

        private ByteArrayInputStream bais = null;
        private ByteArrayOutputStream baos = null;
        private BufferedServletInputStream bsis = null;
        private byte[] buffer = null;


        public BufferedRequestWrapper(HttpServletRequest req) throws IOException {
            super(req);
            // Read InputStream and store its content in a buffer.
            InputStream is = req.getInputStream();
            this.baos = new ByteArrayOutputStream();
            byte buf[] = new byte[1024];
            int letti;
            while ((letti = is.read(buf)) > 0) {
                this.baos.write(buf, 0, letti);
            }
            this.buffer = this.baos.toByteArray();
        }


        @Override
        public ServletInputStream getInputStream() {
            this.bais = new ByteArrayInputStream(this.buffer);
            this.bsis = new BufferedServletInputStream(this.bais);
            return this.bsis;
        }


        String getRequestBody() throws IOException {
            BufferedReader reader = new BufferedReader(new InputStreamReader(this.getInputStream()));
            String line = null;
            StringBuilder inputBuffer = new StringBuilder();
            do {
                line = reader.readLine();
                if (null != line) {
                    inputBuffer.append(line.trim());
                }
            } while (line != null);
            reader.close();
            return inputBuffer.toString().trim();
        }

    }


    private static final class BufferedServletInputStream extends ServletInputStream {

        private ByteArrayInputStream bais;

        public BufferedServletInputStream(ByteArrayInputStream bais) {
            this.bais = bais;
        }

        @Override
        public int available() {
            return this.bais.available();
        }

        @Override
        public int read() {
            return this.bais.read();
        }

        @Override
        public int read(byte[] buf, int off, int len) {
            return this.bais.read(buf, off, len);
        }


    }

    public class TeeServletOutputStream extends ServletOutputStream {

        private final TeeOutputStream targetStream;

        public TeeServletOutputStream(OutputStream one, OutputStream two) {
            targetStream = new TeeOutputStream(one, two);
        }

        @Override
        public void write(int arg0) throws IOException {
            this.targetStream.write(arg0);
        }

        public void flush() throws IOException {
            super.flush();
            this.targetStream.flush();
        }

        public void close() throws IOException {
            super.close();
            this.targetStream.close();
        }
    }


    public class BufferedResponseWrapper implements HttpServletResponse {

        HttpServletResponse original;
        TeeServletOutputStream tee;
        ByteArrayOutputStream bos;

        public BufferedResponseWrapper(HttpServletResponse response) {
            original = response;
        }

        public String getContent() { return bos.toString(); }

        public PrintWriter getWriter() throws IOException {
            return original.getWriter();
        }

        public ServletOutputStream getOutputStream() throws IOException {
            if (tee == null) {
                bos = new ByteArrayOutputStream();
                tee = new TeeServletOutputStream(original.getOutputStream(), bos);
            }
            return tee;

        }

        @Override
        public String getCharacterEncoding() { return original.getCharacterEncoding(); }

        @Override
        public String getContentType() {
            return original.getContentType();
        }

        @Override
        public void setCharacterEncoding(String charset) {
            original.setCharacterEncoding(charset);
        }

        @Override
        public void setContentLength(int len) {
            original.setContentLength(len);
        }

        @Override
        public void setContentType(String type) {
            original.setContentType(type);
        }

        @Override
        public void setBufferSize(int size) {
            original.setBufferSize(size);
        }

        @Override
        public int getBufferSize() {
            return original.getBufferSize();
        }

        @Override
        public void flushBuffer() throws IOException {
            tee.flush();
        }

        @Override
        public void resetBuffer() {
            original.resetBuffer();
        }

        @Override
        public boolean isCommitted() {
            return original.isCommitted();
        }

        @Override
        public void reset() {
            original.reset();
        }

        @Override
        public void setLocale(Locale loc) {
            original.setLocale(loc);
        }

        @Override
        public Locale getLocale() {
            return original.getLocale();
        }

        @Override
        public void addCookie(Cookie cookie) {
            original.addCookie(cookie);
        }

        @Override
        public boolean containsHeader(String name) {
            return original.containsHeader(name);
        }

        @Override
        public String encodeURL(String url) {
            return original.encodeURL(url);
        }

        @Override
        public String encodeRedirectURL(String url) {
            return original.encodeRedirectURL(url);
        }

        @SuppressWarnings("deprecation")
        @Override
        public String encodeUrl(String url) {
            return original.encodeUrl(url);
        }

        @SuppressWarnings("deprecation")
        @Override
        public String encodeRedirectUrl(String url) {
            return original.encodeRedirectUrl(url);
        }

        @Override
        public void sendError(int sc, String msg) throws IOException {
            original.sendError(sc, msg);
        }

        @Override
        public void sendError(int sc) throws IOException {
            original.sendError(sc);
        }

        @Override
        public void sendRedirect(String location) throws IOException {
            original.sendRedirect(location);
        }

        @Override
        public void setDateHeader(String name, long date) {
            original.setDateHeader(name, date);
        }

        @Override
        public void addDateHeader(String name, long date) {
            original.addDateHeader(name, date);
        }

        @Override
        public void setHeader(String name, String value) {
            original.setHeader(name, value);
        }

        @Override
        public void addHeader(String name, String value) {
            original.addHeader(name, value);
        }

        @Override
        public void setIntHeader(String name, int value) {
            original.setIntHeader(name, value);
        }

        @Override
        public void addIntHeader(String name, int value) {
            original.addIntHeader(name, value);
        }

        @Override
        public void setStatus(int sc) {
            original.setStatus(sc);
        }

        @SuppressWarnings("deprecation")
        @Override
        public void setStatus(int sc, String sm) {
            original.setStatus(sc, sm);
        }

        @Override
        public int getStatus() {
            return 0;
        }

        @Override
        public String getHeader(String s) {
            return "unsupported";
        }

        @Override
        public Collection<String> getHeaders(String s) {
            return Collections.emptyList();
        }

        @Override
        public Collection<String> getHeaderNames() {
            return Collections.emptyList();
        }

    }

}

深入查看请求 URL (httpServletRequest.getRequestURL()) 后,我发现过滤器工作正常。

  • java.lang.NullPointerException 表示没有返回正文(例如缓存网站 returns 304 状态并且没有正文)
  • 奇怪编码的字符串属于图像
  • 对于文本和HTML数据 返回正确编码的字符串(网站被查过,这让我很困惑,因为没有记录 HTML 代码)