【问题标题】:Modify HttpServletRequest body修改 HttpServletRequest 正文
【发布时间】:2009-03-25 11:59:38
【问题描述】:

我正在处理遗留代码,需要打补丁。

问题:一个古老的应用程序发送了错误的 HTTP POST 请求。其中一个参数不是 URL 编码的。我知道这个参数总是排在最后,而且我知道它的名字。我现在正在尝试在运行在 tomcat 中的服务器端修复它。

此参数无法通过 HttpServletRequest 的标准 getParameter 方法访问,因为它的格式不正确。方法只是返回 null。但是当我通过 ServletInputStream 手动读取整个请求体时,所有其他参数都消失了。看起来底层类无法解析 ServletInputStream 的内容,因为它已经耗尽了。

到目前为止,我已经设法制作了一个包装器,它从正文中读取所有参数并覆盖所有参数访问方法。但是如果我之前的链中的任何过滤器会尝试访问任何参数,那么一切都会中断,因为 ServletInputStream 将为空。

我能以某种方式规避这个问题吗?可能有不同的方法吗?

总而言之,如果我在过滤器中读取原始请求正文,参数将从请求中消失。如果我读取单个参数,ServletInputStream 将变为空,并且无法手动处理。而且,通过getParameter方法无法读取格式错误的参数。

【问题讨论】:

    标签: java http request


    【解决方案1】:

    我找到的解决方案:

    仅仅重新定义参数访问方法是不够的。必须做几件事。

    1. 需要一个过滤器来包装请求。
    2. 需要自定义 HttpRequestWrapper 并覆盖所有参数访问方法。请求正文应在构造函数中解析并存储为字段。
    3. getInputStreamgetReader 方法也应该重新定义。它们的返回值取决于存储的请求正文。
    4. 需要扩展 ServletInputStream 的自定义类,因为这是抽象的。

    这 4 个组合将允许您使用 getParameter 而不会干扰 getInputStreamgetReader 方法。

    请注意,手动请求参数解析可能会因多部分请求而变得复杂。但这是另一个话题。

    为了澄清,我重新定义了参数访问方法,因为我的请求已损坏,如问题中所述。你可能不需要那个。

    【讨论】:

    • 您能否提供有关手动请求参数解析的更多详细信息?仅仅覆盖 getInputStream 和 getReader 还不够吗?
    • 好的,我查过了。我不得不多次解析参数,因为其中一个已损坏,如问题中所述。如果他们在您的请求中没有问题,则无需这样做。
    • 你有示例代码吗?必须覆盖 getParameter() 和朋友让我担心,因为我宁愿把它留给 servlet 容器。在我的情况下,所有参数都是可读的,但我需要在我的 servlet 调用 getParameter() 之前读取并缓存输入流。而 HttpServletRequestWrapper 的 getParameter() 只是调用原始请求的 getParameter() ,它只知道原始请求的输入流(那时是空的)。
    • @clorz - 你能分享示例代码吗?我真的需要这个。
    【解决方案2】:

    为什么不安装重写请求的 servlet 过滤器,而不是覆盖方法?

    Jason Hunter 有一个不错的article on filters

    【讨论】:

    • 我还不确定,如何读取http post数据并更新post数据并将其设置回请求,如果我读取request.getReader(),那么我又无法读取它,我们该怎么办这个?
    • @PAA:我建议你问一个包含所有细节的新问题,而不是在这个超过 10 年的不完全相关的问题上添加 cmets。
    【解决方案3】:

    我做了一个更完整的包装器,在 Content-Type 是 application/x-www-form-urlencoded 并且您已经调用了 getParameterXXX 方法之一的情况下,您仍然可以访问内容:

    import java.io.BufferedReader;
    import java.io.ByteArrayInputStream;
    import java.io.IOException;  
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.UnsupportedEncodingException;
    import java.net.URLDecoder;
    import java.security.Principal;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.LinkedList;
    import java.util.Locale;
    import java.util.Map;
    import java.util.StringTokenizer;
    
    import javax.servlet.RequestDispatcher;
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    /**
     * This class implements the Wrapper or Decorator pattern.<br/> 
     * Methods default to calling through to the wrapped request object, 
     * except the ones that read the request's content (parameters, stream or reader).
     * <p>
     * This class provides a buffered content reading that allows the methods
     * {@link #getReader()}, {@link #getInputStream()} and any of the getParameterXXX to be     called
     * safely and repeatedly with the same results.
     * <p>
     * This class is intended to wrap relatively small HttpServletRequest instances.
     * 
     * @author pgurov
     */
    public class HttpServletRequestWrapper implements HttpServletRequest {
    
    private class ServletInputStreamWrapper extends ServletInputStream {
    
        private byte[] data;
        private int idx = 0;
        ServletInputStreamWrapper(byte[] data) {
            if(data == null)
                data = new byte[0];
            this.data = data;
        }
        @Override
        public int read() throws IOException {
            if(idx == data.length)
                return -1;
            return data[idx++];
        }
    
    }
    
    private HttpServletRequest req;
    private byte[] contentData;
    private HashMap<String, String[]> parameters;
    
    public HttpServletRequestWrapper() {
        //a trick for Groovy
        throw new IllegalArgumentException("Please use HttpServletRequestWrapper(HttpServletRequest request) constructor!");
    }
    
    private HttpServletRequestWrapper(HttpServletRequest request, byte[] contentData, HashMap<String, String[]> parameters) {
        req = request;
        this.contentData = contentData;
        this.parameters = parameters;
    }
    
    public HttpServletRequestWrapper(HttpServletRequest request) {
        if(request == null)
            throw new IllegalArgumentException("The HttpServletRequest is null!");
        req = request;
    }
    
    /**
     * Returns the wrapped HttpServletRequest.
     * Using the getParameterXXX(), getInputStream() or getReader() methods may interfere
     * with this class operation.
     * 
     * @return 
     *      The wrapped HttpServletRequest.
     */
    public HttpServletRequest getRequest() {
        try {
            parseRequest();
        } catch (IOException e) {
            throw new IllegalStateException("Cannot parse the request!", e);
        }
        return new HttpServletRequestWrapper(req, contentData, parameters);
    }
    
    /**
     * This method is safe to use multiple times.
     * Changing the returned array will not interfere with this class operation.
     * 
     * @return 
     *      The cloned content data.
     */
    public byte[] getContentData() {
        return contentData.clone();
    }
    
    /**
     * This method is safe to use multiple times.
     * Changing the returned map or the array of any of the map's values will not
     * interfere with this class operation.
     * 
     * @return
     *      The clonned parameters map.
     */
    public HashMap<String, String[]> getParameters() {
        HashMap<String, String[]> map = new HashMap<String, String[]>(parameters.size() * 2);
        for(String key : parameters.keySet()) {
            map.put(key, parameters.get(key).clone());
        }
        return map;
    }
    
    private void parseRequest() throws IOException {
        if(contentData != null)
            return; //already parsed
    
        byte[] data = new byte[req.getContentLength()];
        int len = 0, totalLen = 0;
        InputStream is = req.getInputStream();
        while(totalLen < data.length) {
            totalLen += (len = is.read(data, totalLen, data.length - totalLen));
            if(len < 1)
                throw new IOException("Cannot read more than " + totalLen + (totalLen == 1 ? " byte!" : " bytes!"));
        }
        contentData = data;
        String enc = req.getCharacterEncoding();
        if(enc == null)
            enc = "UTF-8";
        String s = new String(data, enc), name, value;
        StringTokenizer st = new StringTokenizer(s, "&");
        int i;
        HashMap<String, LinkedList<String>> mapA = new HashMap<String, LinkedList<String>>(data.length * 2);
        LinkedList<String> list;
        boolean decode = req.getContentType() != null && req.getContentType().equals("application/x-www-form-urlencoded");
        while(st.hasMoreTokens()) {
            s = st.nextToken();
            i = s.indexOf("=");
            if(i > 0 && s.length() > i + 1) {
                name = s.substring(0, i);
                value = s.substring(i+1);
                if(decode) {
                    try {
                        name = URLDecoder.decode(name, "UTF-8");
                    } catch(Exception e) {}
                    try {
                        value = URLDecoder.decode(value, "UTF-8");
                    } catch(Exception e) {}
                }
                list = mapA.get(name);
                if(list == null) {
                    list = new LinkedList<String>();
                    mapA.put(name, list);
                }
                list.add(value);
            }
        }
        HashMap<String, String[]> map = new HashMap<String, String[]>(mapA.size() * 2);
        for(String key : mapA.keySet()) {
            list = mapA.get(key);
            map.put(key, list.toArray(new String[list.size()]));
        }
        parameters = map;
    }
    
    /**
     * This method is safe to call multiple times.
     * Calling it will not interfere with getParameterXXX() or getReader().
     * Every time a new ServletInputStream is returned that reads data from the begining.
     * 
     * @return
     *      A new ServletInputStream.
     */
    public ServletInputStream getInputStream() throws IOException {
        parseRequest();
    
        return new ServletInputStreamWrapper(contentData);
    }
    
    /**
     * This method is safe to call multiple times.
     * Calling it will not interfere with getParameterXXX() or getInputStream().
     * Every time a new BufferedReader is returned that reads data from the begining.
     * 
     * @return
     *      A new BufferedReader with the wrapped request's character encoding (or UTF-8 if null).
     */
    public BufferedReader getReader() throws IOException {
        parseRequest();
    
        String enc = req.getCharacterEncoding();
        if(enc == null)
            enc = "UTF-8";
        return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(contentData), enc));
    }
    
    /**
     * This method is safe to execute multiple times.
     * 
     * @see javax.servlet.ServletRequest#getParameter(java.lang.String)
     */
    public String getParameter(String name) {
        try {
            parseRequest();
        } catch (IOException e) {
            throw new IllegalStateException("Cannot parse the request!", e);
        }
        String[] values = parameters.get(name);
        if(values == null || values.length == 0)
            return null;
        return values[0];
    }
    
    /**
     * This method is safe.
     * 
     * @see {@link #getParameters()}
     * @see javax.servlet.ServletRequest#getParameterMap()
     */
    @SuppressWarnings("unchecked")
    public Map getParameterMap() {
        try {
            parseRequest();
        } catch (IOException e) {
            throw new IllegalStateException("Cannot parse the request!", e);
        }
        return getParameters();
    }
    
    /**
     * This method is safe to execute multiple times.
     * 
     * @see javax.servlet.ServletRequest#getParameterNames()
     */
    @SuppressWarnings("unchecked")
    public Enumeration getParameterNames() {
        try {
            parseRequest();
        } catch (IOException e) {
            throw new IllegalStateException("Cannot parse the request!", e);
        }
        return new Enumeration<String>() {
            private String[] arr = getParameters().keySet().toArray(new String[0]); 
            private int idx = 0;
    
            public boolean hasMoreElements() {
                return idx < arr.length;
            }
    
            public String nextElement() {
                return arr[idx++];
            }
    
        };
    }
    
    /**
     * This method is safe to execute multiple times.
     * Changing the returned array will not interfere with this class operation.
     * 
     * @see javax.servlet.ServletRequest#getParameterValues(java.lang.String)
     */
    public String[] getParameterValues(String name) {
        try {
            parseRequest();
        } catch (IOException e) {
            throw new IllegalStateException("Cannot parse the request!", e);
        }
        String[] arr = parameters.get(name);
        if(arr == null)
            return null;
        return arr.clone();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getAuthType()
     */
    public String getAuthType() {
        return req.getAuthType();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getContextPath()
     */
    public String getContextPath() {
        return req.getContextPath();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getCookies()
     */
    public Cookie[] getCookies() {
        return req.getCookies();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getDateHeader(java.lang.String)
     */
    public long getDateHeader(String name) {
        return req.getDateHeader(name);
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getHeader(java.lang.String)
     */
    public String getHeader(String name) {
        return req.getHeader(name);
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getHeaderNames()
     */
    @SuppressWarnings("unchecked")
    public Enumeration getHeaderNames() {
        return req.getHeaderNames();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getHeaders(java.lang.String)
     */
    @SuppressWarnings("unchecked")
    public Enumeration getHeaders(String name) {
        return req.getHeaders(name);
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getIntHeader(java.lang.String)
     */
    public int getIntHeader(String name) {
        return req.getIntHeader(name);
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getMethod()
     */
    public String getMethod() {
        return req.getMethod();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getPathInfo()
     */
    public String getPathInfo() {
        return req.getPathInfo();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getPathTranslated()
     */
    public String getPathTranslated() {
        return req.getPathTranslated();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getQueryString()
     */
    public String getQueryString() {
        return req.getQueryString();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getRemoteUser()
     */
    public String getRemoteUser() {
        return req.getRemoteUser();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getRequestURI()
     */
    public String getRequestURI() {
        return req.getRequestURI();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getRequestURL()
     */
    public StringBuffer getRequestURL() {
        return req.getRequestURL();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getRequestedSessionId()
     */
    public String getRequestedSessionId() {
        return req.getRequestedSessionId();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getServletPath()
     */
    public String getServletPath() {
        return req.getServletPath();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getSession()
     */
    public HttpSession getSession() {
        return req.getSession();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getSession(boolean)
     */
    public HttpSession getSession(boolean create) {
        return req.getSession(create);
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#getUserPrincipal()
     */
    public Principal getUserPrincipal() {
        return req.getUserPrincipal();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromCookie()
     */
    public boolean isRequestedSessionIdFromCookie() {
        return req.isRequestedSessionIdFromCookie();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromURL()
     */
    public boolean isRequestedSessionIdFromURL() {
        return req.isRequestedSessionIdFromURL();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdFromUrl()
     */
    @SuppressWarnings("deprecation")
    public boolean isRequestedSessionIdFromUrl() {
        return req.isRequestedSessionIdFromUrl();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#isRequestedSessionIdValid()
     */
    public boolean isRequestedSessionIdValid() {
        return req.isRequestedSessionIdValid();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.http.HttpServletRequest#isUserInRole(java.lang.String)
     */
    public boolean isUserInRole(String role) {
        return req.isUserInRole(role);
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getAttribute(java.lang.String)
     */
    public Object getAttribute(String name) {
        return req.getAttribute(name);
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getAttributeNames()
     */
    @SuppressWarnings("unchecked")
    public Enumeration getAttributeNames() {
        return req.getAttributeNames();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getCharacterEncoding()
     */
    public String getCharacterEncoding() {
        return req.getCharacterEncoding();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getContentLength()
     */
    public int getContentLength() {
        return req.getContentLength();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getContentType()
     */
    public String getContentType() {
        return req.getContentType();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getLocalAddr()
     */
    public String getLocalAddr() {
        return req.getLocalAddr();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getLocalName()
     */
    public String getLocalName() {
        return req.getLocalName();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getLocalPort()
     */
    public int getLocalPort() {
        return req.getLocalPort();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getLocale()
     */
    public Locale getLocale() {
        return req.getLocale();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getLocales()
     */
    @SuppressWarnings("unchecked")
    public Enumeration getLocales() {
        return req.getLocales();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getProtocol()
     */
    public String getProtocol() {
        return req.getProtocol();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getRealPath(java.lang.String)
     */
    @SuppressWarnings("deprecation")
    public String getRealPath(String path) {
        return req.getRealPath(path);
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getRemoteAddr()
     */
    public String getRemoteAddr() {
        return req.getRemoteAddr();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getRemoteHost()
     */
    public String getRemoteHost() {
        return req.getRemoteHost();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getRemotePort()
     */
    public int getRemotePort() {
        return req.getRemotePort();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getRequestDispatcher(java.lang.String)
     */
    public RequestDispatcher getRequestDispatcher(String path) {
        return req.getRequestDispatcher(path);
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getScheme()
     */
    public String getScheme() {
        return req.getScheme();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getServerName()
     */
    public String getServerName() {
        return req.getServerName();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#getServerPort()
     */
    public int getServerPort() {
        return req.getServerPort();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#isSecure()
     */
    public boolean isSecure() {
        return req.isSecure();
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#removeAttribute(java.lang.String)
     */
    public void removeAttribute(String name) {
        req.removeAttribute(name);
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#setAttribute(java.lang.String, java.lang.Object)
     */
    public void setAttribute(String name, Object value) {
        req.setAttribute(name, value);
    }
    
    /* (non-Javadoc)
     * @see javax.servlet.ServletRequest#setCharacterEncoding(java.lang.String)
     */
    public void setCharacterEncoding(String env)
            throws UnsupportedEncodingException {
        req.setCharacterEncoding(env);
    }
    
    }
    

    【讨论】:

    • 为什么你需要重写所有委托给 req 对象的方法,而基类已经这样做了?
    • 我意识到您实现了 HttpServletRequest 而不是扩展 HttpServletRequestWrapper 因此您需要实现所有必需的方法。但是为什么不从 HttpServletRequestWrapper 扩展,它无论如何都会将调用委托给原始请求对象?
    • getParameterValues、getParameter 方法依赖于原始流,因此单独的包装器不会这样做。但是我放弃使用包装器以避免实现流的所有方法。事实上,如果没有额外的独立配置,undertow 会拒绝这个。
    【解决方案4】:

    我想将此作为评论发布,但我没有足够的代表。您的解决方案是不够的,因为 ServletInputStreamWrapper 将返回负整数。例如,模拟一个输入编码为 UTF-16 的请求,无论是大端还是小端。输入可能以指示字节顺序的字节顺序标记开始,并且在测试我的语句时请构造模拟请求内容以执行此操作。 http://en.wikipedia.org/wiki/Byte_order_mark#UTF-16 这些 BOM 中的任何一个都包含一个 0xFF 字节。由于 java 没有无符号字节,所以这个 0xFF 作为 -1 返回。要解决这个问题,只需像这样更改读取功能

        public int read() throws IOException {
            if (index == data.length) {
                return -1;
            }
            return data[index++] & 0xff;
        }
    

    我有点喜欢您的解决方案,因为它适用于 Spring。起初,我试图通过扩展 HttpServletRequestWrapper 来消除您编写的一些委托代码。然而,Spring 做了一些有趣的事情:当它遇到 ServletRequestWrapper 类型的请求时,它会解开它,调用 getRequest()。问题是我的 getRequest() 方法(从您的代码中复制)返回一个从 HttpServletRequestWrapper 扩展的新类...冲洗并无限重复。所以很遗憾地说,不使用接口就赢了!

    【讨论】:

    • 我还发现了另一个问题,req.getContentType().equals("application/x-www-form-urlencoded") 应该是 req.getContentType().startsWith("application/x- www-form-urlencoded") 事实证明,Firefox 可以发送 "application/x-www-form-urlencoded; charset=UTF-8" 而 Chrome 和 IE 只发送 "application/x-www-form-urlencoded"
    【解决方案5】:

    您可以编写自己的 Servlet 过滤器,并希望确保它首先出现在链中。然后将 ServletRequest 对象包装在可以在需要时处理重写的东西中。查看http://java.sun.com/products/servlet/Filters.html的编程自定义请求和响应部分

    ------更新------

    我一定错过了什么。你说你可以自己读请求体,读参数。难道你不能确保你的过滤器是第一个,包装 ServletRequest 对象,读取,处理和存储参数,将你的请求对象传递到链上并提供你存储的参数而不是原始参数吗?

    【讨论】:

    • 我可以读取除损坏的参数之外的所有参数。我只能通过 ServletInputStream 读取损坏的参数。当我使用输入流时,所有其他参数都会消失。如果我从输入流中解析所有参数,有时 servlet 会在链的后面中断。很复杂=)
    猜你喜欢
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-17
    • 2011-12-27
    • 2014-03-29
    • 1970-01-01
    • 2019-07-31
    相关资源
    最近更新 更多