Estou tentando acessar dois parâmetros de solicitação http em um filtro Java Servlet, nada de novo aqui, mas fiquei surpreso ao descobrir que os parâmetros já foram consumidos! Por causa disso, ele não está mais disponível na cadeia de filtros.
Parece que isso só ocorre quando os parâmetros vêm em um corpo de solicitação POST (um envio de formulário, por exemplo).
Existe uma maneira de ler os parâmetros e NÃO consumi-los?
Até agora, encontrei apenas esta referência: Filtro de servlet usando request.getParameter perde dados do formulário .
Obrigado!
Respostas:
Como um aparte, uma maneira alternativa de resolver este problema é não usar a cadeia de filtros e, em vez disso, construir seu próprio componente interceptor, talvez usando aspectos, que podem operar no corpo da solicitação analisado. Provavelmente, também será mais eficiente, pois você só está convertendo a solicitação
InputStream
em seu próprio objeto de modelo uma vez.No entanto, ainda acho que é razoável querer ler o corpo da solicitação mais de uma vez, especialmente à medida que a solicitação passa pela cadeia de filtros. Normalmente, eu usaria cadeias de filtros para certas operações que desejo manter na camada HTTP, desacopladas dos componentes de serviço.
Conforme sugerido por Will Hartung , consegui isso estendendo
HttpServletRequestWrapper
, consumindo a solicitaçãoInputStream
e essencialmente armazenando os bytes em cache.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(); } } }
Agora, o corpo da solicitação pode ser lido mais de uma vez envolvendo a solicitação original antes de passá-la pela cadeia de filtros:
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); } }
Essa solução também permitirá que você leia o corpo da solicitação várias vezes por meio dos
getParameterXXX
métodos, pois a chamada subjacente égetInputStream()
, que, é claro, lerá a solicitação em cacheInputStream
.Editar
Para uma versão mais recente da
ServletInputStream
interface. Você precisa fornecer a implementação de mais alguns métodosisReady
, como ,setReadListener
etc. Consulte esta questão conforme fornecido no comentário abaixo.fonte
getInputStream
é chamado no meu invólucro, pois é aServletRequest
instância que passo para a cadeia de filtros. Se você ainda estiver em dúvida, leia o código-fonte doServletRequestWrapper
e aServletRequest
interface.isReady()
.isFinished()
esetReadListener()
para lidar com IO sem bloqueio que deve ser implementado. Estou pensando que o ReadListener pode ser deixado em branco, mas não tenho certeza do que fazerisFinished()
e / ouisReady()
.Eu sei que estou atrasado, mas esta questão ainda era relevante para mim e este post do SO foi um dos maiores sucessos no Google. Vou postar minha solução na esperança de que outra pessoa economize algumas horas.
No meu caso, precisei registrar todas as solicitações e respostas com seus corpos. Usando Spring Framework, a resposta é bastante simples, basta usar ContentCachingRequestWrapper e ContentCachingResponseWrapper .
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 } } }
fonte
requestBody
eresponseBody
eram strings vaziaschain.doFilter(request, response);
vez de umchain.doFilter(requestWrapper, responseWrapper);
ContentCaching*Wrapper
classes têm o preço caro de consumir o fluxo de entrada, então o "armazenamento em cache" é feito por meio do método,getContentAsByteArray
mas essa classe não está armazenando em cache o fluxo de entrada que pode ser necessário para outros filtros na cadeia de filtros (que é meu caso de uso). Imho, este é um comportamento não esperado de uma classe de cache de conteúdo, portanto levantei essa melhoria no time de primavera jira.spring.io/browse/SPR-16028AbstractRequestLoggingFilter
partir do Spring, onde a maior parte do trabalho já é feito pelo Spring e você só precisa substituir 1 ou 2 métodos simples.spring-web-4.3.12.RELEASE
. Ao verificar a fonte, descobri que a variávelcachedContent
é usada para armazenar vários conteúdos, como parâmetros de solicitação e inputStream de solicitação. Está vazio se você ligargetContentAsByteArray()
apenas. Para obter o corpo da solicitação, você deve ligargetInputStream()
. Mas, novamente, isso tornará o inputStream indisponível para outros filtros e o manipulador.As respostas acima foram muito úteis, mas ainda apresentavam alguns problemas em minha experiência. No servlet 3.0 do tomcat 7, getParamter e getParamterValues também tiveram que ser substituídos. A solução aqui inclui os parâmetros get-query e o post-body. Ele permite obter cordas brutas facilmente.
Como as outras soluções, ele usa Apache commons-io e Googles Guava.
Nesta solução, os métodos getParameter * não lançam IOException, mas usam super.getInputStream () (para obter o corpo), que pode lançar IOException. Eu pego e lanço runtimeException. Não é tão bom.
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(); } }
fonte
decode(getPostBodyAsString(), result);
emgetParameterMap()
? Isso cria um parâmetro com key = request body e value = null, o que é muito estranho.super.getParameterMap()
seugetParameterMap
? O que lhe dará um mapa de<String, String[]>
qualquer maneira.A única maneira seria você consumir todo o fluxo de entrada sozinho no filtro, pegar o que quiser dele e, em seguida, criar um novo InputStream para o conteúdo lido e colocar esse InputStream em um ServletRequestWrapper (ou HttpServletRequestWrapper).
A desvantagem é que você mesmo terá que analisar a carga útil, o padrão não disponibiliza esse recurso para você.
Addenda -
Como eu disse, você precisa olhar para HttpServletRequestWrapper.
Em um filtro, você continua chamando FilterChain.doFilter (solicitação, resposta).
Para filtros triviais, a solicitação e a resposta são iguais às passadas para o filtro. Isso não precisa ser o caso. Você pode substituí-los por suas próprias solicitações e / ou respostas.
HttpServletRequestWrapper é projetado especificamente para facilitar isso. Você passa a solicitação original e pode interceptar todas as chamadas. Você cria sua própria subclasse disso e substitui o método getInputStream por um seu. Você não pode alterar o fluxo de entrada da solicitação original, então, em vez disso, você tem este wrapper e retorna seu próprio fluxo de entrada.
O caso mais simples é consumir o fluxo de entrada das solicitações originais em um buffer de bytes, fazer o que quiser e criar um novo ByteArrayInputStream a partir desse buffer. Isso é o que é retornado em seu wrapper, que é passado para o método FilterChain.doFilter.
Você precisará criar uma subclasse de ServletInputStream e fazer outro wrapper para seu ByteArrayInputStream, mas isso também não é grande coisa.
fonte
Eu também tive o mesmo problema e acredito que o código abaixo é mais simples e está funcionando para mim,
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())); } }
na classe de filtro java,
HttpServletRequest properRequest = ((HttpServletRequest) req); MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(properRequest); req = wrappedRequest; inputJson = IOUtils.toString(req.getReader()); System.out.println("body"+inputJson);
Por favor, deixe-me saber se você tiver alguma dúvida
fonte
Portanto, esta é basicamente a resposta de Lathy, MAS atualizada para requisitos mais recentes para ServletInputStream.
A saber (para ServletInputStream), é necessário implementar:
public abstract boolean isFinished(); public abstract boolean isReady(); public abstract void setReadListener(ReadListener var1);
Este é o objeto editado de 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())); } }
e em algum lugar (??) eu encontrei isso (que é uma classe de primeira classe que lida com os métodos "extras".
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; } } };
No final das contas, eu estava apenas tentando registrar as solicitações. E as peças combinadas de frankensteined acima me ajudaram a criar o abaixo.
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; } }
Por favor, aceite este código com cautela.
O "teste" MAIS importante é se um POST funciona com uma carga útil. Isso é o que irá expor os problemas de "leitura dupla".
código de pseudo exemplo
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; } }
Você pode substituir "MyCustomObject" por "Object" simples se quiser apenas testar.
Esta resposta vem de vários posts e exemplos diferentes de SOF ... mas demorou um pouco para reunir tudo, então espero que ajude um futuro leitor.
Por favor, vote a favor da resposta de Lathy antes da minha. Eu não poderia ter chegado tão longe sem ele.
Abaixo está uma / algumas das exceções que recebi enquanto trabalhava nisso.
Parece que alguns dos lugares de onde "peguei emprestado" estão aqui:
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
fonte
O Spring tem suporte integrado para isso com
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; }
Infelizmente, você ainda não conseguirá ler a carga útil diretamente da solicitação, mas o parâmetro String message incluirá a carga útil para que você possa pegá-la de lá da seguinte maneira:
String body = message.substring(message.indexOf("{"), message.lastIndexOf("]"));
fonte
Apenas substituir de
getInputStream()
não funcionou no meu caso. Minha implementação de servidor parece analisar parâmetros sem chamar esse método. Eu não encontrei outra maneira, mas reimplemente os quatro métodos getParameter * também. Aqui está o código degetParameterMap
(Apache Http Client e biblioteca Google Guava usados):@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; }
fonte
Se você tiver controle sobre a solicitação, poderá definir o tipo de conteúdo como binário / fluxo de octeto . Isso permite consultar os parâmetros sem consumir o fluxo de entrada.
No entanto, isso pode ser específico para alguns servidores de aplicativos. Eu só testei o tomcat, o jetty parece se comportar da mesma maneira de acordo com https://stackoverflow.com/a/11434646/957103 .
fonte
O método getContentAsByteArray () da classe Spring ContentCachingRequestWrapper lê o corpo várias vezes, mas os métodos getInputStream () e getReader () da mesma classe não lêem o corpo várias vezes:
"Esta classe armazena em cache o corpo da solicitação consumindo o InputStream. Se lermos o InputStream em um dos filtros, outros filtros subsequentes na cadeia de filtros não poderão mais lê-lo. Devido a essa limitação, esta classe não é adequada em todos situações. "
No meu caso, a solução mais geral que resolveu esse problema foi adicionar as três classes a seguir ao meu projeto de inicialização Spring (e as dependências necessárias ao arquivo 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); } }
Também adicionei as seguintes dependências ao 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>
Um código-fonte tutural e completo está localizado aqui: https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times
fonte
você pode usar a cadeia de filtro de servlet, mas, em vez de usar o original, pode criar sua própria solicitação yourownrequests estende HttpServletRequestWrapper.
fonte
Em primeiro lugar, não devemos ler os parâmetros do filtro. Normalmente, os cabeçalhos são lidos no filtro para fazer algumas tarefas de autenticação. Dito isto, pode-se ler o corpo HttpRequest completamente no Filtro ou Interceptor usando CharStreams:
Isso não afeta as leituras subsequentes.
fonte
request.getReader()
retornará um leitor que contém apenas uma string vazia nas leituras subsequentes.