Java Http Servlet请求在读取一次后从POST正文中丢失参数

Java Http Servlet请求在读取一次后从POST正文中丢失参数,java,servlets,parameters,httprequest,servlet-filters,Java,Servlets,Parameters,Httprequest,Servlet Filters,我试图在JavaServlet过滤器中访问两个http请求参数,这里没有什么新内容,但惊讶地发现这些参数已经被使用了!因此,它在过滤器链中不再可用 似乎只有当参数出现在POST请求主体(例如表单提交)中时,才会发生这种情况 有没有办法读取参数而不使用它们 到目前为止,我只找到了以下引用: 谢谢 唯一的方法是您自己在过滤器中使用整个输入流,从中获取您想要的内容,然后为您读取的内容创建一个新的InputStream,并将该InputStream放入ServletRequestWrapper或Http

我试图在JavaServlet过滤器中访问两个http请求参数,这里没有什么新内容,但惊讶地发现这些参数已经被使用了!因此,它在过滤器链中不再可用

似乎只有当参数出现在POST请求主体(例如表单提交)中时,才会发生这种情况

有没有办法读取参数而不使用它们

到目前为止,我只找到了以下引用:


谢谢

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

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

附录-

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

在筛选器中,通过调用FilterChain.doFilterrequest、response继续

对于普通过滤器,请求和响应与传递给过滤器的请求和响应相同。事实并非如此。您可以用自己的请求和/或响应替换这些请求和/或响应

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

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


您需要将ServletInputStream子类化,并为ByteArrayInputStream制作另一个包装器,但这也不是什么大问题。

另一种解决此问题的方法是不使用过滤器链,而是构建自己的拦截器组件,可能使用方面,它可以在解析的请求体上操作。它也可能更有效,因为您只需将请求InputStream转换为您自己的模型对象一次

但是,我仍然认为有理由多次读取请求正文,特别是当请求通过过滤器链时。我通常会对某些操作使用过滤器链,这些操作我希望保留在HTTP层,与服务组件分离

正如所建议的,我通过扩展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接口。您需要提供更多方法的实现,如isReady、setReadListener等。请参阅下面的评论。

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

如果您可以控制请求,您可以将内容类型设置为二进制/八位字节流。这允许在不使用输入流的情况下查询参数

但是,这可能特定于某些应用程序服务器。我只测试了tomcat,jetty的行为似乎与之相同。

在我的情况下,仅仅覆盖getInputStream不起作用。我的服务器实现似乎不调用此方法就解析参数。我没有找到任何其他方法,但也重新实现了所有四个getParameter*方法。下面是使用的getParameterMap Apache Http客户端和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;
}

我也有同样的问题,我相信下面的代码更简单,它对我很有用

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()));
}
}
在filter java类中

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

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

以上答案非常有用,但在我的经验中仍然存在一些问题。在Tomcat7Servlet3.0上,getParameter和getParameterValues也必须被覆盖。这里的解决方案包括get查询参数和post正文。它允许轻松获取原始字符串

与其他解决方案一样,它使用ApacheCommonsIO和GooglesGuava

在这个解决方案中,getParameter*方法不抛出IOException,但它们使用super.getInputStream来获取可能抛出IOException的主体。我捕获它并抛出runtimeException。不太好


我知道我迟到了,但这个问题对我来说仍然很重要,所以这篇文章是谷歌的热门文章之一。我将继续发布我的解决方案,希望其他人可以节省几个小时

在我的情况下,我需要用他们的身体记录所有请求和响应。使用Spring框架,答案其实很简单,只需使用和


首先,我们不应该读取过滤器中的参数。通常在筛选器中读取头以执行少量身份验证任务。已经说过,可以使用CharStreams在过滤器或拦截器中完全读取HttpRequest主体:

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

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

因此这基本上是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;
    }
}
如果只想测试,可以用普通ole对象替换MyCustomObject

这个答案是从几个不同的软件帖子和例子中得出的。但是,把它全部整合起来需要一段时间,所以我希望它能对未来的读者有所帮助

请先把拉西的答案投给我。没有它我不可能走这么远

下面是我在解决这个问题时遇到的一个/一些例外情况

已为此请求调用getReader

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

2021年1月

我已经了解了上面的代码不适用于

x-www-form-urlencoded

考虑下面的例子:

   @CrossOrigin
    @ResponseBody
    @PostMapping(path = "/mypath", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE})
    public ResponseEntity myMethodName(@RequestParam Map<String, String> parameters
    ) {
        /* DO YOU GET ANY PARAMETERS HERE?  Or are they empty because of logging/auditing filter ?*/
        return new ResponseEntity(HttpStatus.OK);

    }
下面是另一个与JSON等一起工作的示例。这是工厂可以输出的另一个具体示例。我把它放在这里是为了让我2021年1月的附录是一致的。我不知道下面的代码是否与我的原始答案完全一致:

import org.springframework.http.MediaType;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * Makes a "copy" of the HttpRequest so the body can be accessed more than 1 time.
 * See : https://stackoverflow.com/questions/44182370/why-do-we-wrap-httpservletrequest-the-api-provides-an-httpservletrequestwrappe/44187955#44187955
 * DOES NOT WORK WITH APPLICATION_FORM_URLENCODED_VALUE
 */
public final class PersistedBodyRequestWrapper extends HttpServletRequestWrapper {

    public static final String ERROR_MSG_CONTENT_TYPE_NOT_SUPPORTED = "ContentType not supported. (ContentType=\"%1$s\")";

    public static final String ERROR_MSG_PERSISTED_BODY_REQUEST_WRAPPER_CONSTRUCTOR_FAILED = "PersistedBodyRequestWrapper constructor FAILED";

    private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(PersistedBodyRequestWrapper.class);

    private String persistedBody;

    private final Map<String, String[]> parameterMap;

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

        String contentType = request.getContentType();
        /* Allow everything EXCEPT APPLICATION_FORM_URLENCODED_VALUE */
        if (null != contentType && contentType.equalsIgnoreCase(MediaType.APPLICATION_FORM_URLENCODED_VALUE)) {
            IllegalArgumentException ioex = new IllegalArgumentException(String.format(ERROR_MSG_CONTENT_TYPE_NOT_SUPPORTED, MediaType.APPLICATION_FORM_URLENCODED_VALUE));
            LOGGER.error(ERROR_MSG_PERSISTED_BODY_REQUEST_WRAPPER_CONSTRUCTOR_FAILED, ioex);
            throw ioex;
        }

        parameterMap = request.getParameterMap();
        this.persistedBody = "";
        BufferedReader bufferedReader = request.getReader();
        String line;
        while ((line = bufferedReader.readLine()) != null) {
            this.persistedBody += line;
        }
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        CustomServletInputStream csis = new CustomServletInputStream(this.persistedBody.getBytes(StandardCharsets.UTF_8));
        return csis;
    }

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

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

Spring通过AbstractRequestLoggingFilter对此提供了内置支持:

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


String body=message.substringmessage.indexOf{,message.lastIndexOf];

Spring类ContentCachingRequestWrapper的方法getContentAsByteArray多次读取正文,但同一类的方法getInputStream和getReader不会多次读取正文:

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

在我的例子中,解决这个问题的更通用的解决方案是将以下三个类添加到我的Spring boot项目中,并将所需的依赖项添加到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));
    }
}
CachedBodyServletiInputStream.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>
教程和完整的源代码位于此处:

我找到了适用于任何格式请求正文的好解决方案。 我测试了application/x-www-form-urlencoded和application/json,两者都运行得很好。ContentCachingRequestWrapper的问题只针对x-www-form-urlencoded请求体设计,但不适用于json。我找到了json的解决方案。它遇到的问题是不支持x-www-form-urlencoded。 我在代码中加入了这两者:

import org.apache.commons.io.IOUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;

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

public class MyContentCachingRequestWrapper extends ContentCachingRequestWrapper {

    private byte[] body;

    public MyContentCachingRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        super.getParameterMap(); // init cache in ContentCachingRequestWrapper
        body = super.getContentAsByteArray(); // first option for application/x-www-form-urlencoded
        if (body.length == 0) {
          try {
            body = IOUtils.toByteArray(super.getInputStream()); // second option for other body formats
          } catch (IOException ex) {
            body = new byte[0];
          }
        }
    }

    public byte[] getBody() {
        return body;
    }

    @Override
    public ServletInputStream getInputStream() {
        return new RequestCachingInputStream(body);
    }

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

    private static class RequestCachingInputStream extends ServletInputStream {

        private final ByteArrayInputStream inputStream;

        public RequestCachingInputStream(byte[] bytes) {
            inputStream = new ByteArrayInputStream(bytes);
        }

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

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

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

        @Override
        public void setReadListener(ReadListener readlistener) {
        }

    }

}

可能会显示一段代码片段说明您是如何操作的?您是否获得getInputStream或getReader?似乎它们会干扰GetParameter的执行。我无法读取InputStream并将其还原,因为没有直接访问流的get/set方法。您的建议似乎不错,但我看不到如何导入是这样吗?底层调用是原始请求上的getInputStream,您已经读取了其中的字节。底层请求不知道您的包装器,所以它怎么知道调用包装器的getInputStream?确切地说,getInputStream是在我的包装器上调用的,因为这是我创建的ServletRequest实例进入过滤链。如果您仍然有疑问,请阅读ServletRequestWrapper和ServletRequest接口的源代码。如果我可以使其+100,我会的。我已经尝试使其正常工作了3-4个小时。感谢您提供清晰的示例和解释!我很高兴找到这篇文章!有任何建议如何使其与Ser一起工作吗vlet api 3.0+?ServletInputStream现在有抽象isReady.isFinished和setReadListener来处理必须实现的非阻塞IO。我认为ReadListener可以留空,但不确定

o关于“完成”和/或“已准备好”怎么办。无论如何,谢谢你。我后来找到了新api接口的解决方案,只是粘贴在这里以防有人感兴趣。Jetty有这个问题,很不幸,您可能正在使用Tomcat7或更高版本的Servlet3.0?你也有其他3个方法的代码吗?其他3个方法只需调用getParameterMap并获取所需的值。所以我也遇到了paramMap的一些问题。见我的答案和2021年1月的附录。这个问题的另一个答案中有一条神奇的线……这可能是不必手动保留参数映射的触发点。代码的神奇部分super.getParameterMap;//ContentCachingRequestWrapper中的初始化缓存。这是一个至关重要的调用,因此@RequestParam映射参数被填充到REST控制器上。现在,指向教程的链接似乎包含病毒。这对我不起作用。requestBody和responseBody都是空字符串,这是我的错误。我在做一个链式回答;不是chain.doFilterrequestWrapper,而是responseWrapper;ContentCaching*包装类消耗输入流的代价很高,因此缓存是通过getContentAsByteArray方法完成的,但该类不缓存输入流,这可能是我的用例过滤器链中的其他过滤器所需要的。恕我直言,这不是内容缓存类的预期行为,因此我在spring团队中提出了这一改进,您可以使用spring中的AbstractRequestLoggingFilter,其中大部分工作已经由spring完成,您只需覆盖1或2个简单方法。这在spring-web-4.3.12.RELEASE中对我不起作用。在检查源代码时,我发现变量cachedContent用于存储各种内容,例如请求参数和请求inputStream。如果单独调用getContentAsByteArray,则它是空的。要获取请求正文,必须调用getInputStream。但同样,这将使其他过滤器和处理程序无法使用inputStream。这太棒了!几天来我一直在努力解决这个问题,这在Servlet3.1中是有效的。一个问题:为什么要解码GetPostBodyAsString,result;在getParameterMap中?这会创建一个key=request body,value=null的参数,这很奇怪。与其进行所有的字符串解析,为什么不在getParameterMap中调用super.getParameterMap?这会给你一张地图,所以我也碰到了一些关于paramMap的问题。见我的答案和2021年1月的附录。这个问题的另一个答案中有一条神奇的线……这可能是不必手动保留参数映射的触发点。代码的神奇部分super.getParameterMap;//ContentCachingRequestWrapper中的初始化缓存。这是一个重要的调用,因此@RequestParam映射参数将填充到它所执行的REST控制器上。如果执行此操作一次,request.getReader将在后续读取中返回一个仅包含空字符串的读取器。如果覆盖getReader和getInputStream方法,我将使用此新正文作为源。我希望使用您的解决方案生成审核日志,但我需要记录请求是否成功,我可以挂接http响应并获取此类中的代码吗;我更新了我的答案…但是你的答案我投了更高的票。。这有助于我的代码。并且在代码旁边有一个位置良好的注释。谢谢super.getParameterMap;//初始化缓存ContentCachingRequestWrapper@granadaCoder你应该点击trought to super method,你会看到正在做什么,哇!!!!!
@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;
}
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));
    }
}
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();
    }
}
@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);
    }
}
<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>
import org.apache.commons.io.IOUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;

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

public class MyContentCachingRequestWrapper extends ContentCachingRequestWrapper {

    private byte[] body;

    public MyContentCachingRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        super.getParameterMap(); // init cache in ContentCachingRequestWrapper
        body = super.getContentAsByteArray(); // first option for application/x-www-form-urlencoded
        if (body.length == 0) {
          try {
            body = IOUtils.toByteArray(super.getInputStream()); // second option for other body formats
          } catch (IOException ex) {
            body = new byte[0];
          }
        }
    }

    public byte[] getBody() {
        return body;
    }

    @Override
    public ServletInputStream getInputStream() {
        return new RequestCachingInputStream(body);
    }

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

    private static class RequestCachingInputStream extends ServletInputStream {

        private final ByteArrayInputStream inputStream;

        public RequestCachingInputStream(byte[] bytes) {
            inputStream = new ByteArrayInputStream(bytes);
        }

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

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

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

        @Override
        public void setReadListener(ReadListener readlistener) {
        }

    }

}