当前位置: 首页 > 知识库问答 >
问题:

我们如何在不影响java中原始请求的情况下读取过滤器中的请求体?

胡彭亮
2023-03-14

(Java版本8)

我需要在过滤器中处理请求正文。使用下面的代码,我读取了正文。

    private static String convertInputStreamToString(InputStream is) throws IOException {

        ByteArrayOutputStream result = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024 * 50];
        int length;
        while ((length = is.read(buffer)) != -1) {
            result.write(buffer, 0, length);
        }

        return result.toString("UTF-8");
    }

问题是如果请求正文发布的参数的内容类型为“Application/x-www-form-urlencoded”,那么这些参数在读取正文后将不可用。如果我不读取正文,它们可以使用request.getParameter()获取。

此外,我尝试使用以下代码包装请求并提供正文,这样解决方案的其余部分(例如servlet)就可以使用它,但丢失参数的问题仍然存在。代码是从这篇文章中复制/采用的

public class RequestWrapper extends HttpServletRequestWrapper {

    private final String body;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        
        body = convertInputStreamToString(request.getInputStream());
    }

    private static String convertInputStreamToString(InputStream is) throws IOException {

        ByteArrayOutputStream result = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024 * 50];
        int length;
        while ((length = is.read(buffer)) != -1) {
            result.write(buffer, 0, length);
        }

        return result.toString("UTF-8");
    }
    
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final byte[] myBytes = body.getBytes("UTF-8");
        ServletInputStream servletInputStream = new ServletInputStream() {
            private int lastIndexRetrieved = -1;
            private ReadListener readListener = null;

            @Override
            public boolean isFinished() {
                return (lastIndexRetrieved == myBytes.length - 1);
            }

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

            @Override
            public void setReadListener(ReadListener readListener) {
                this.readListener = readListener;
                if (!isFinished()) {
                    try {
                        readListener.onDataAvailable();
                    } catch (IOException e) {
                        readListener.onError(e);
                    }
                } else {
                    try {
                        readListener.onAllDataRead();
                    } catch (IOException e) {
                        readListener.onError(e);
                    }
                }
            }

            @Override
            public int read() throws IOException {
                int i;
                if (!isFinished()) {
                    i = myBytes[lastIndexRetrieved + 1];
                    lastIndexRetrieved++;
                    if (isFinished() && (readListener != null)) {
                        try {
                            readListener.onAllDataRead();
                        } catch (IOException ex) {
                            readListener.onError(ex);
                            throw ex;
                        }
                    }
                    return i;
                } else {
                    return -1;
                }
            }
        };
        return servletInputStream;
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }
}

共有3个答案

林国安
2023-03-14

奇怪的是,HttpServletRequest内容只能读取一次。它以流的形式出现,所以一旦您读取流,它就消失了。所以您需要一些允许您多次读取的包装器。Spring实际上提供了这样的包装器。类的名称是ContentCachingRequest estWrapper。这是它的Javadoc。这是解释如何在使用Spring Boot时使用它的答案:如何在Spring过滤器中获取请求正文参数?

华谭三
2023-03-14

感谢zaerymoghaddam的帮助。

我担心我是否会隐式地影响请求对象,因此解决方案的其余部分缺少一些内容。

此外,我发现参数映射=super.get参数映射();没有从正文中提取参数(如果帖子的内容类型为“Application/x-www-form-urlencoded”)

通过对您的代码进行一点更改,我提出了以下解决方案:

public class MyRequestWrapper extends HttpServletRequestWrapper {
    private ByteArrayOutputStream cachedBytes;
    private String body;
    private Map<String, String[]> parameterMap;
    private static int bufferLength = 1024 * 50;

    public MyRequestWrapper(final HttpServletRequest request) throws IOException {
        super(request);

        cacheBodyAsString();
        
        parameterMap = new HashMap<>(super.getParameterMap());
        addParametersFromBody();
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedServletInputStream(cachedBytes.toByteArray());
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(this.getInputStream()));
    }

    public String GetRequestBodyAsString() {
        return this.body;
    }
    
    @Override
    public String getParameter(String key) {
        Map<String, String[]> parameterMap = getParameterMap();
        String[] values = parameterMap.get(key);
        return values != null && values.length > 0 ? values[0] : null;
    }

    @Override
    public String[] getParameterValues(String key) {
        Map<String, String[]> parameterMap = getParameterMap();
        return parameterMap.get(key);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return parameterMap;
    }
    
    private void cacheInputStream() throws IOException {
        cachedBytes = new ByteArrayOutputStream();

        byte[] buffer = new byte[bufferLength];
        int length;
        InputStream is = super.getInputStream();
        while ((length = is.read(buffer)) != -1) {
            cachedBytes.write(buffer, 0, length);
        }
    }
    
    private void cacheBodyAsString() throws IOException {
        if (cachedBytes == null)
            cacheInputStream();
        
        this.body = cachedBytes.toString("UTF-8");
    }
    
    private void addParametersFromBody() {
        if(this.body == null || this.body.isEmpty())
            return;
        
        String[] params = this.body.split("&");

        String[] value = new String[1];
        for (String param : params) {  
            String key = param.split("=")[0];
            value[0] = param.split("=")[1];
            parameterMap.putIfAbsent(key, value);
        }

    }
    
    class CachedServletInputStream extends ServletInputStream {
        private final ByteArrayInputStream buffer;
        
        public CachedServletInputStream(byte[] contents) {
            this.buffer = new ByteArrayInputStream(contents);
        }
        
        @Override
        public int read() {
            return buffer.read();
        }

        @Override
        public boolean isFinished() {
            return buffer.available() == 0;
        }

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

        @Override
        public void setReadListener(ReadListener listener) {
            throw new RuntimeException("Not implemented");
        }
    }
}
唐煜
2023-03-14

我尝试运行您提到的您正在使用的代码,我认为已接受的答案可能无法解决您的问题,因为它很旧。似乎您还需要覆盖getParametergetParameterMapgetParameterValures方法。我试图根据同一帖子中的这个答案来做到这一点,似乎它是有效的。这是代码:

public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
    private ByteArrayOutputStream cachedBytes;
    private String body;
    private Map<String, String[]> parameterMap;

    public MultiReadHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        parameterMap = super.getParameterMap();
        cacheBodyAsString();
        System.out.println("The Body read into a String is: " + body);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (cachedBytes == null)
            cacheInputStream();

        return new CachedServletInputStream(cachedBytes.toByteArray());
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    @Override
    public String getParameter(String key) {
        Map<String, String[]> parameterMap = getParameterMap();
        String[] values = parameterMap.get(key);
        return values != null && values.length > 0 ? values[0] : null;
    }

    @Override
    public String[] getParameterValues(String key) {
        Map<String, String[]> parameterMap = getParameterMap();
        return parameterMap.get(key);
    }

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

    private void cacheInputStream() throws IOException {
        // Cache the inputstream in order to read it multiple times
        cachedBytes = new ByteArrayOutputStream();

        byte[] buffer = new byte[1024 * 50];
        int length;
        InputStream is = super.getInputStream();
        while ((length = is.read(buffer)) != -1) {
            cachedBytes.write(buffer, 0, length);
        }
    }

    private void cacheBodyAsString() throws IOException {

        ByteArrayOutputStream result = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024 * 50];
        int length;
        InputStream is = getInputStream();
        while ((length = is.read(buffer)) != -1) {
            result.write(buffer, 0, length);
        }

        body = result.toString("UTF-8");
    }
}

public class CachedServletInputStream extends ServletInputStream {

    private final ByteArrayInputStream buffer;

    public CachedServletInputStream(byte[] contents) {
        this.buffer = new ByteArrayInputStream(contents);
    }

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

    @Override
    public boolean isFinished() {
        return buffer.available() == 0;
    }

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

    @Override
    public void setReadListener(ReadListener listener) {
        throw new RuntimeException("Not implemented");
    }
}

这只是一个示例实现。我强烈建议遵循上面提到的答案中指定的步骤,因为它看起来比较新,而且还可以确保从正文和查询字符串中读取参数。我的代码只是一个示例草图,看看它是否按预期工作。

 类似资料:
  • 我正在基于Tomcat servlet和NIO创建服务。输入时有大的XML请求(约100 MB),通过HTML POST方法发送。我只想流前8千磅,然后立即发送响应到客户端。 当我尝试发送小请求(内容中只有几行)时,套接字工作正常。 2016-02-01 10:44:52 Http11NioProtocol[DEBUG]套接字:[org.apache.tomcat.util.net.NioEndp

  • 我如何解析JSON请求之类的东西? 示例代码: 带有_名称的位置_为无

  • 我正在使用名为“更新人员”的Jmete对HTTP补丁方法请求进行性能测试。这里的情况是更新人员依赖于另一个名为“创建人员”的请求。创建人员将返回一个“人员ID”作为响应,我将使用该ID发送更新请求。所以我不能仅使用更新人员请求进行性能测试。这是我的Jmete测试计划布局: 测试仪测试计划 当我运行测试计划时,两个请求的性能都比单独测试Create Person慢得多。我的问题是: 测试两个超文本传

  • 问题内容: 我有一个实现接口的类型,该接口在其方法中检查传入的HTTP请求,采取某些措施,然后将请求转发到反向代理处理程序()。 只要我仅检查基本的请求属性(例如URL或标头),此方法就可以正常工作。 当我想检查传入的POST请求的主体(例如,通过调用然后使用属性)时,将请求传递到反向代理后,就会遇到错误: 我认为发生这种情况是因为查看HTTP请求的正文会导致流被耗尽,这意味着代理处理程序无法再次

  • 我想通过REST API(最好使用Python或Java)发出请求。然而,我想过滤掉结果,因为我不想从响应中得到所有信息。例如,我只需要文本。作为初学者,我很难取得成功。到目前为止,我只在Python上用库“requests”完成了一个请求。有人能帮我吗?