一次读取后,Http Servlet请求会丢失POST正文中的参数


86

我正在尝试访问Java Servlet过滤器中的两个http请求参数,这里没有新内容,但是很惊讶地发现这些参数已经被消耗了!因此,它在过滤器链中不再可用。

似乎只有在参数进入POST请求正文(例如表单提交)时才会发生这种情况。

有没有办法读取参数而不消耗它们?

到目前为止,我只找到了以下参考:使用request.getParameter的Servlet过滤器会丢失Form数据

谢谢!


2
也许显示您如何执行的代码片段?
Pavel Veller 2012年

您是否获得了getInputStream()或getReader()?似乎它们会干扰getParameter()的执行
evanwong 2012年

Answers:


111

顺便说一句,解决此问题的另一种方法是不使用过滤器链,而是使用可以在已解析请求主体上运行的方面来构建自己的拦截器组件。由于您只需将请求InputStream转换为自己的模型对象一次,这样做也可能会更有效率。

但是,我仍然认为要多次读取请求主体是合理的,尤其是在请求通过过滤器链移动时。我通常会将过滤器链用于要保留在HTTP层的某些操作,这些操作与服务组件分离。

正如Will Hartung所建议的那样,我是通过扩展HttpServletRequestWrapper,使用请求InputStream并实质上缓存字节来实现的。

public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
  private ByteArrayOutputStream cachedBytes;

  public MultiReadHttpServletRequest(HttpServletRequest request) {
    super(request);
  }

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

      return new CachedServletInputStream();
  }

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

  private void cacheInputStream() throws IOException {
    /* Cache the inputstream in order to read it multiple times. For
     * convenience, I use apache.commons IOUtils
     */
    cachedBytes = new ByteArrayOutputStream();
    IOUtils.copy(super.getInputStream(), cachedBytes);
  }

  /* An inputstream which reads the cached request body */
  public class CachedServletInputStream extends ServletInputStream {
    private ByteArrayInputStream input;

    public CachedServletInputStream() {
      /* create a new input stream from the cached request body */
      input = new ByteArrayInputStream(cachedBytes.toByteArray());
    }

    @Override
    public int read() throws IOException {
      return input.read();
    }
  }
}

现在,通过将原始请求包装在过滤器链中之前,可以包装原始请求多次读取请求主体:

public class MyFilter implements Filter {
  @Override
  public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {

    /* wrap the request in order to read the inputstream multiple times */
    MultiReadHttpServletRequest multiReadRequest = new MultiReadHttpServletRequest((HttpServletRequest) request);

    /* here I read the inputstream and do my thing with it; when I pass the
     * wrapped request through the filter chain, the rest of the filters, and
     * request handlers may read the cached inputstream
     */
    doMyThing(multiReadRequest.getInputStream());
    //OR
    anotherUsage(multiReadRequest.getReader());
    chain.doFilter(multiReadRequest, response);
  }
}

该解决方案还将允许您通过这些getParameterXXX方法多次读取请求正文,因为基础调用是getInputStream(),当然它将读取缓存的请求InputStream

编辑

对于较新版本的ServletInputStream界面。您需要提供其他一些方法的实现,例如isReadysetReadListener等等。请参考下面的注释中提供的问题


5
真的吗?基础调用是原始请求上的getInputStream(),您已经已经读取了这些字节。基础请求不了解您的包装器,因此如何知道调用包装器的getInputStream()?
弗朗斯

1
准确地说getInputStream叫上的包装,因为这是ServletRequest我进入过滤器链的实例。如果您仍然不确定,请阅读ServletRequestWrapperServletRequest接口的源代码。
佩斯特雷拉

1
如果我可以使这个数字为+100,我会的。我一直在尝试使其正常工作3-4小时。谢谢您的清晰示例和解释!我很高兴我发现了这篇文章!
2014年

20
关于如何使它与Servlet-api 3.0+一起工作的任何建议?ServletInputStream现在具有abstract isReady()isFinished()setReadListener()处理必须实施的非阻塞IO。我在想ReadListener可以留空,但不确定如何处理isFinished()和/或isReady()
Eric B.

12
@EricB。不管怎么说,还是要谢谢你。后来我找到了较新的api接口的解决方案,只是粘贴在这里,以防有人感兴趣。stackoverflow.com/questions/29208456/…–
dcc

37

我知道我来晚了,但是这个问题对我来说仍然很重要,因此,这篇SO帖子是Google的热门歌曲之一。我正在继续发布解决方案,希望其他人可以节省几个小时。

就我而言,我需要记录所有请求和响应及其内容。使用Spring Framework,答案实​​际上非常简单,只需使用ContentCachingRequestWrapperContentCachingResponseWrapper即可

import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class LoggingFilter implements Filter {

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

    }

    @Override
    public void destroy() {

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper((HttpServletRequest) request);
        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) response);

        try {
            chain.doFilter(requestWrapper, responseWrapper);
        } finally {

            String requestBody = new String(requestWrapper.getContentAsByteArray());
            String responseBody = new String(responseWrapper.getContentAsByteArray());
            // Do not forget this line after reading response content or actual response will be empty!
            responseWrapper.copyBodyToResponse();

            // Write request and response body, headers, timestamps etc. to log files

        }

    }

}

3
这对我不起作用。两者requestBodyresponseBody都是空字符串
Abhijith Madhav

5
我的错。我chain.doFilter(request, response);不是在做chain.doFilter(requestWrapper, responseWrapper);
Abhijith Madhav

5
这些ContentCaching*Wrapper类具有消耗输入流的昂贵价格,因此“缓存”是通过方法完成的,getContentAsByteArray但是此类并未缓存输入流,这可能是过滤器链中其他过滤器可能需要的(这是我的用例)。恕我直言,这是内容缓存类的意外行为,因此我在春季团队jira.spring.io/browse/SPR-16028
Federico

您可以AbstractRequestLoggingFilter在Spring中使用它,其中大部分工作已由Spring完成,您只需要重写1或2个简单方法即可。
苛刻的

1
截至目前,这对我不起作用spring-web-4.3.12.RELEASE。当我检查源代码时,我发现该变量cachedContent用于存储各种内容,例如请求参数和请求inputStream。如果您getContentAsByteArray()只打一个电话,那是空的。要获取请求正文,您必须致电getInputStream()。但是同样,这将使inputStream对其他过滤器和处理程序不可用。
伊万·黄

7

以上答案非常有帮助,但根据我的经验仍然存在一些问题。在tomcat 7 Servlet 3.0上,getParamter和getParamterValues也必须被覆盖。这里的解决方案包括get-query参数和post-body。它允许轻松获取原始字符串。

像其他解决方案一样,它使用Apache commons-io和Googles Guava。

在此解决方案中,getParameter *方法不引发IOException,但它们使用super.getInputStream()(获取主体),而该方法可能引发IOException。我抓住了它并抛出了runtimeException。不太好

import com.google.common.collect.Iterables;
import com.google.common.collect.ObjectArrays;

import org.apache.commons.io.IOUtils;
import org.apache.http.NameValuePair;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.ContentType;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/**
 * Purpose of this class is to make getParameter() return post data AND also be able to get entire
 * body-string. In native implementation any of those two works, but not both together.
 */
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
    public static final String UTF8 = "UTF-8";
    public static final Charset UTF8_CHARSET = Charset.forName(UTF8);
    private ByteArrayOutputStream cachedBytes;
    private Map<String, String[]> parameterMap;

    public MultiReadHttpServletRequest(HttpServletRequest request) {
        super(request);
    }

    public static void toMap(Iterable<NameValuePair> inputParams, Map<String, String[]> toMap) {
        for (NameValuePair e : inputParams) {
            String key = e.getName();
            String value = e.getValue();
            if (toMap.containsKey(key)) {
                String[] newValue = ObjectArrays.concat(toMap.get(key), value);
                toMap.remove(key);
                toMap.put(key, newValue);
            } else {
                toMap.put(key, new String[]{value});
            }
        }
    }

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

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

    private void cacheInputStream() throws IOException {
    /* Cache the inputStream in order to read it multiple times. For
     * convenience, I use apache.commons IOUtils
     */
        cachedBytes = new ByteArrayOutputStream();
        IOUtils.copy(super.getInputStream(), cachedBytes);
    }

    @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() {
        if (parameterMap == null) {
            Map<String, String[]> result = new LinkedHashMap<String, String[]>();
            decode(getQueryString(), result);
            decode(getPostBodyAsString(), result);
            parameterMap = Collections.unmodifiableMap(result);
        }
        return parameterMap;
    }

    private void decode(String queryString, Map<String, String[]> result) {
        if (queryString != null) toMap(decodeParams(queryString), result);
    }

    private Iterable<NameValuePair> decodeParams(String body) {
        Iterable<NameValuePair> params = URLEncodedUtils.parse(body, UTF8_CHARSET);
        try {
            String cts = getContentType();
            if (cts != null) {
                ContentType ct = ContentType.parse(cts);
                if (ct.getMimeType().equals(ContentType.APPLICATION_FORM_URLENCODED.getMimeType())) {
                    List<NameValuePair> postParams = URLEncodedUtils.parse(IOUtils.toString(getReader()), UTF8_CHARSET);
                    params = Iterables.concat(params, postParams);
                }
            }
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
        return params;
    }

    public String getPostBodyAsString() {
        try {
            if (cachedBytes == null) cacheInputStream();
            return cachedBytes.toString(UTF8);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /* An inputStream which reads the cached request body */
    public class CachedServletInputStream extends ServletInputStream {
        private ByteArrayInputStream input;

        public CachedServletInputStream() {
            /* create a new input stream from the cached request body */
            input = new ByteArrayInputStream(cachedBytes.toByteArray());
        }

        @Override
        public int read() throws IOException {
            return input.read();
        }
    }

    @Override
    public String toString() {
        String query = dk.bnr.util.StringUtil.nullToEmpty(getQueryString());
        StringBuilder sb = new StringBuilder();
        sb.append("URL='").append(getRequestURI()).append(query.isEmpty() ? "" : "?" + query).append("', body='");
        sb.append(getPostBodyAsString());
        sb.append("'");
        return sb.toString();
    }
}

这很棒!我已经尝试了好几天了,并且可以在Servlet 3.1中使用。疑问一:为什么你做decode(getPostBodyAsString(), result);getParameterMap()?这会创建一个参数,其中键=请求正文,值= null,这很奇怪。
Orlade

为何不进行调用super.getParameterMap(),而不是进行所有的字符串解析getParameterMap<String, String[]>无论如何都会给你一张地图。
Ean V

6

唯一的方法是让您自己在过滤器中使用整个输入流,从中获取所需的内容,然后为读取的内容创建一个新的InputStream,并将该InputStream放入ServletRequestWrapper(或HttpServletRequestWrapper)中。

缺点是您必须自己解析有效负载,标准并不能使您使用该功能。

附录-

如我所说,您需要查看HttpServletRequestWrapper。

在过滤器中,您可以继续通过调用FilterChain.doFilter(request,response)。

对于琐碎的过滤器,请求和响应与传递到过滤器的请求和响应相同。事实并非如此。您可以将它们替换为自己的请求和/或响应。

HttpServletRequestWrapper是专门为实现此目的而设计的。您将原始请求传递给它,然后就可以拦截所有呼叫。您可以创建自己的子类,然后用自己的一个替换getInputStream方法。您不能更改原始请求的输入流,因此可以使用此包装器并返回自己的输入流。

最简单的情况是将原始请求输入流消耗到字节缓冲区中,对其执行任何操作,然后从该缓冲区中创建一个新的ByteArrayInputStream。这就是包装器中返回的内容,该包装器将传递给FilterChain.doFilter方法。

您需要继承ServletInputStream的子类,并为ByteArrayInputStream制作另一个包装,但这也不是什么大不了的事情。


我无法读取InputStream并还原之后,没有获取/设置方法来直接访问该流。您的建议看起来不错,但我看不出如何实施。
amuniz 2012年

4

我也遇到了同样的问题,我相信下面的代码会更简单,并且对我有用。

public class MultiReadHttpServletRequest extends  HttpServletRequestWrapper {

 private String _body;

public MultiReadHttpServletRequest(HttpServletRequest request) throws IOException {
   super(request);
   _body = "";
   BufferedReader bufferedReader = request.getReader();           
   String line;
   while ((line = bufferedReader.readLine()) != null){
       _body += line;
   }
}

@Override
public ServletInputStream getInputStream() throws IOException {
   final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(_body.getBytes());
   return new ServletInputStream() {
       public int read() throws IOException {
           return byteArrayInputStream.read();
       }
   };
}

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

在过滤器Java类中,

HttpServletRequest properRequest = ((HttpServletRequest) req);
MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(properRequest);
req = wrappedRequest;
inputJson = IOUtils.toString(req.getReader());
System.out.println("body"+inputJson);

如果您有任何疑问,请告诉我


3

因此,这基本上是Lathy的答案,但已针对ServletInputStream的较新要求进行了更新。

即(对于ServletInputStream),必须实现:

public abstract boolean isFinished();

public abstract boolean isReady();

public abstract void setReadListener(ReadListener var1);

这是Lathy编辑的对象

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

public class RequestWrapper extends HttpServletRequestWrapper {

    private String _body;

    public RequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        _body = "";
        BufferedReader bufferedReader = request.getReader();
        String line;
        while ((line = bufferedReader.readLine()) != null){
            _body += line;
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {

        CustomServletInputStream kid = new CustomServletInputStream(_body.getBytes());
        return kid;
    }

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

在某个地方(??),我发现了这一点(这是处理“额外”方法的一流类。

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

public class CustomServletInputStream extends ServletInputStream {

    private byte[] myBytes;

    private int lastIndexRetrieved = -1;
    private ReadListener readListener = null;

    public CustomServletInputStream(String s) {
        try {
            this.myBytes = s.getBytes("UTF-8");
        } catch (UnsupportedEncodingException ex) {
            throw new IllegalStateException("JVM did not support UTF-8", ex);
        }
    }

    public CustomServletInputStream(byte[] inputBytes) {
        this.myBytes = inputBytes;
    }

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

    @Override
    public boolean isReady() {
        // This implementation will never block
        // We also never need to call the readListener from this method, as this method will never return false
        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;
        }
    }
};

最终,我只是试图记录请求。上面的弗兰肯斯坦作品一起帮助我创造了下面的作品。

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;

//one or the other based on spring version
//import org.springframework.boot.autoconfigure.web.ErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;

import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.filter.OncePerRequestFilter;


/**
 * A filter which logs web requests that lead to an error in the system.
 */
@Component
public class LogRequestFilter extends OncePerRequestFilter implements Ordered {

    // I tried apache.commons and slf4g loggers.  (one or the other in these next 2 lines of declaration */
    //private final static org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory.getLog(LogRequestFilter.class);
    private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(LogRequestFilter.class);

    // put filter at the end of all other filters to make sure we are processing after all others
    private int order = Ordered.LOWEST_PRECEDENCE - 8;
    private ErrorAttributes errorAttributes;

    @Override
    public int getOrder() {
        return order;
    }

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

        String temp = ""; /* for a breakpoint, remove for production/real code */

        /* change to true for easy way to comment out this code, remove this if-check for production/real code */
        if (false) {
            filterChain.doFilter(request, response);
            return;
        }

        /* make a "copy" to avoid issues with body-can-only-read-once issues */
        RequestWrapper reqWrapper = new RequestWrapper(request);

        int status = HttpStatus.INTERNAL_SERVER_ERROR.value();
        // pass through filter chain to do the actual request handling
        filterChain.doFilter(reqWrapper, response);
        status = response.getStatus();

        try {
            Map<String, Object> traceMap = getTrace(reqWrapper, status);
            // body can only be read after the actual request handling was done!
            this.getBodyFromTheRequestCopy(reqWrapper, traceMap);

            /* now do something with all the pieces of information gatherered */
            this.logTrace(reqWrapper, traceMap);
        } catch (Exception ex) {
            logger.error("LogRequestFilter FAILED: " + ex.getMessage(), ex);
        }
    }

    private void getBodyFromTheRequestCopy(RequestWrapper rw, Map<String, Object> trace) {
        try {
            if (rw != null) {
                byte[] buf = IOUtils.toByteArray(rw.getInputStream());
                //byte[] buf = rw.getInputStream();
                if (buf.length > 0) {
                    String payloadSlimmed;
                    try {
                        String payload = new String(buf, 0, buf.length, rw.getCharacterEncoding());
                        payloadSlimmed = payload.trim().replaceAll(" +", " ");
                    } catch (UnsupportedEncodingException ex) {
                        payloadSlimmed = "[unknown]";
                    }

                    trace.put("body", payloadSlimmed);
                }
            }
        } catch (IOException ioex) {
            trace.put("body", "EXCEPTION: " + ioex.getMessage());
        }
    }

    private void logTrace(HttpServletRequest request, Map<String, Object> trace) {
        Object method = trace.get("method");
        Object path = trace.get("path");
        Object statusCode = trace.get("statusCode");

        logger.info(String.format("%s %s produced an status code '%s'. Trace: '%s'", method, path, statusCode,
                trace));
    }

    protected Map<String, Object> getTrace(HttpServletRequest request, int status) {
        Throwable exception = (Throwable) request.getAttribute("javax.servlet.error.exception");

        Principal principal = request.getUserPrincipal();

        Map<String, Object> trace = new LinkedHashMap<String, Object>();
        trace.put("method", request.getMethod());
        trace.put("path", request.getRequestURI());
        if (null != principal) {
            trace.put("principal", principal.getName());
        }
        trace.put("query", request.getQueryString());
        trace.put("statusCode", status);

        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = (String) headerNames.nextElement();
            String value = request.getHeader(key);
            trace.put("header:" + key, value);
        }

        if (exception != null && this.errorAttributes != null) {
            trace.put("error", this.errorAttributes
                    .getErrorAttributes((WebRequest) new ServletRequestAttributes(request), true));
        }

        return trace;
    }
}

请将此代码加一粒盐。

最重要的“测试”是POST是否可与有效负载配合使用。这就是暴露“双重读取”问题的原因。

伪示例代码

import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("myroute")
public class MyController {
    @RequestMapping(method = RequestMethod.POST, produces = "application/json")
    @ResponseBody
    public String getSomethingExample(@RequestBody MyCustomObject input) {

        String returnValue = "";

        return returnValue;
    }
}

如果只想测试,则可以用普通的“对象”替换“ MyCustomObject”。

这个答案是从几个不同的SOF帖子和示例中得出的。

请在我之前赞成Lathy的回答。没有它,我不可能走得那么远。

以下是我在解决此问题时遇到的一些异常。

此请求已调用getReader()

看来我“借来”的一些地方在这里:

http://slackspace.de/articles/log-request-body-with-spring-boot/

https://github.com/c0nscience/spring-web-logging/blob/master/src/main/java/org/zalando/springframework/web/logging/LoggingFilter.java

https://howtodoinjava.com/servlets/httpservletrequestwrapper-example-read-request-body/

https://www.oodlestechnologies.com/blogs/How-to-create-duplicate-object-of-httpServletRequest-object

https://github.com/c0nscience/spring-web-logging/blob/master/src/main/java/org/zalando/springframework/web/logging/LoggingFilter.java


3

Spring内置了对此的支持AbstractRequestLoggingFilter

@Bean
public Filter loggingFilter(){
    final AbstractRequestLoggingFilter filter = new AbstractRequestLoggingFilter() {
        @Override
        protected void beforeRequest(final HttpServletRequest request, final String message) {

        }

        @Override
        protected void afterRequest(final HttpServletRequest request, final String message) {

        }
    };

    filter.setIncludePayload(true);
    filter.setIncludeQueryString(false);
    filter.setMaxPayloadLength(1000000);

    return filter;
}

不幸的是,您仍然无法直接从请求中读取有效负载,但是String消息参数将包含有效负载,因此您可以从此处获取它,如下所示:

String body = message.substring(message.indexOf("{"), message.lastIndexOf("]"));


我希望使用您的解决方案来生成审核日志,但是我需要记录请求是否成功,是否可以插入http响应并在此类中获取代码。
jonesy

1

getInputStream()我而言,仅覆盖无效。我的服务器实现似乎在不调用此方法的情况下解析参数。我没有找到其他方法,但是还重新实现了所有四个getParameter *方法。以下是getParameterMap(使用的Apache Http Client和Google Guava库)的代码:

@Override
public Map<String, String[]> getParameterMap() {
    Iterable<NameValuePair> params = URLEncodedUtils.parse(getQueryString(), NullUtils.UTF8);

    try {
        String cts = getContentType();
        if (cts != null) {
            ContentType ct = ContentType.parse(cts);
            if (ct.getMimeType().equals(ContentType.APPLICATION_FORM_URLENCODED.getMimeType())) {
                List<NameValuePair> postParams = URLEncodedUtils.parse(IOUtils.toString(getReader()), NullUtils.UTF8);
                params = Iterables.concat(params, postParams);
            }
        }
    } catch (IOException e) {
        throw new IllegalStateException(e);
    }
    Map<String, String[]> result = toMap(params);
    return result;
}

public static Map<String, String[]> toMap(Iterable<NameValuePair> body) {
    Map<String, String[]> result = new LinkedHashMap<>();
    for (NameValuePair e : body) {
        String key = e.getName();
        String value = e.getValue();
        if (result.containsKey(key)) {
            String[] newValue = ObjectArrays.concat(result.get(key), value);
            result.remove(key);
            result.put(key, newValue);
        } else {
            result.put(key, new String[] {value});
        }
    }
    return result;
}

1
不幸的是,码头有这个问题,grepcode.com
file/

您可能正在使用Tomcat 7或更高版本的Servlet 3.0?您是否还有其他3种方法的代码?

其他3种方法仅调用getParameterMap()并获取所需的值。
15th


0

Spring类ContentCachingRequestWrapper的方法getContentAsByteArray()多次读取主体,但同一类的方法getInputStream()和getReader()不会多次读取主体:

“此类通过使用InputStream来缓存请求正文。如果我们在其中一个过滤器中读取InputStream,则过滤器链中的其他后续过滤器将无法再读取它。由于此限制,该类不适用于所有情况。”

在我的情况下,解决此问题的更通用的解决方案是在我的Spring启动项目中添加以下三个类(以及对pom文件的必需依赖项):

CachedBodyHttpServletRequest.java:

public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {

    private byte[] cachedBody;

    public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
        super(request);
        InputStream requestInputStream = request.getInputStream();
        this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        return new CachedBodyServletInputStream(this.cachedBody);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        // Create a reader from cachedContent
        // and return it
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
        return new BufferedReader(new InputStreamReader(byteArrayInputStream));
    }
}

CachedBodyServletInputStream.java:

public class CachedBodyServletInputStream extends ServletInputStream {

    private InputStream cachedBodyInputStream;

    public CachedBodyServletInputStream(byte[] cachedBody) {
        this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
    }

    @Override
    public boolean isFinished() {
        try {
            return cachedBodyInputStream.available() == 0;
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return false;
    }

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

    @Override
    public void setReadListener(ReadListener readListener) {
        throw new UnsupportedOperationException();
    }

    @Override
    public int read() throws IOException {
        return cachedBodyInputStream.read();
    }
}

ContentCachingFilter.java:

@Order(value = Ordered.HIGHEST_PRECEDENCE)
@Component
@WebFilter(filterName = "ContentCachingFilter", urlPatterns = "/*")
public class ContentCachingFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        System.out.println("IN  ContentCachingFilter ");
        CachedBodyHttpServletRequest cachedBodyHttpServletRequest = new CachedBodyHttpServletRequest(httpServletRequest);
        filterChain.doFilter(cachedBodyHttpServletRequest, httpServletResponse);
    }
}

我还向pom添加了以下依赖项:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.0</version>
</dependency>

完整的源代码位于此处:https ://www.baeldung.com/spring-reading-httpservletrequest-multiple-times


-1

您可以使用servlet筛选器链,但是可以使用原始的筛选器链,可以创建自己的请求yourownrequests扩展HttpServletRequestWrapper。


1
似乎指向教程的链接现在包含病毒。
fasth 2016年

-2

首先,我们不应该在过滤器中读取参数。通常,在过滤器中读取标头可以执行一些身份验证任务。前面已经说过,可以使用CharStreams在Filter或Interceptor中完全读取HttpRequest主体:

String body = com.google.common.io.CharStreams.toString(request.getReader());

这根本不影响后续的读取。


是的,它确实。如果执行一次,request.getReader()将返回一个在后续读取中仅包含空字符串的读取器。
christophetd

1
如果覆盖getReader()和getInputStream()方法以使用此新主体作为源,我将进行工作。
罗德里戈·博尔巴
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.