Estoy tratando de acceder a dos parámetros de solicitud http en un filtro de servlet de Java, nada nuevo aquí, ¡pero me sorprendió descubrir que los parámetros ya se han consumido! Debido a esto, ya no está disponible en la cadena de filtros.
Parece que esto solo ocurre cuando los parámetros vienen en un cuerpo de solicitud POST (un formulario de envío, por ejemplo).
¿Hay alguna forma de leer los parámetros y NO consumirlos?
Hasta ahora, solo he encontrado esta referencia: el filtro de servlet usando request.getParameter pierde datos de formulario .
¡Gracias!
Respuestas:
Además, una forma alternativa de resolver este problema es no usar la cadena de filtros y, en su lugar, construir su propio componente interceptor, quizás usando aspectos, que pueden operar en el cuerpo de la solicitud analizada. También es probable que sea más eficiente ya que solo está convirtiendo la solicitud
InputStream
en su propio objeto modelo una vez.Sin embargo, sigo pensando que es razonable querer leer el cuerpo de la solicitud más de una vez, especialmente cuando la solicitud se mueve a través de la cadena de filtros. Por lo general, usaría cadenas de filtros para ciertas operaciones que quiero mantener en la capa HTTP, desacopladas de los componentes del servicio.
Como sugirió Will Hartung, logré esto extendiendo
HttpServletRequestWrapper
, consumiendo la solicitudInputStream
y esencialmente almacenando en caché los bytes.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(); } } }
Ahora, el cuerpo de la solicitud se puede leer más de una vez envolviendo la solicitud original antes de pasarla por la cadena 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); } }
Esta solución también le permitirá leer el cuerpo de la solicitud varias veces a través de los
getParameterXXX
métodos porque la llamada subyacente esgetInputStream()
, que por supuesto leerá la solicitud en cachéInputStream
.Editar
Para una versión más reciente de la
ServletInputStream
interfaz. Debe proporcionar la implementación de algunos métodos más comoisReady
,setReadListener
etc. Consulte esta pregunta como se indica en el comentario a continuación.fuente
getInputStream
se llama a mi contenedor, ya que esta es laServletRequest
instancia que paso a la cadena de filtros. Si aún tiene dudas, lea el código fuenteServletRequestWrapper
y laServletRequest
interfaz.isReady()
.isFinished()
ysetReadListener()
para hacer frente a las E / S sin bloqueo que deben implementarse. Creo que ReadListener podría dejarse en blanco, pero no estoy seguro de qué hacer al respectoisFinished()
y / oisReady()
.Sé que llego tarde, pero esta pregunta seguía siendo relevante para mí y esta publicación SO fue uno de los principales éxitos en Google. Voy a publicar mi solución con la esperanza de que alguien más pueda ahorrar un par de horas.
En mi caso, necesitaba registrar todas las solicitudes y respuestas con sus cuerpos. Usando Spring Framework, la respuesta es bastante simple, solo use ContentCachingRequestWrapper y 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 } } }
fuente
requestBody
yresponseBody
eran cadenas vacíaschain.doFilter(request, response);
lugar de unchain.doFilter(requestWrapper, responseWrapper);
ContentCaching*Wrapper
clases tienen el costoso precio de consumir el flujo de entrada, por lo que el "almacenamiento en caché" se realiza a través del método,getContentAsByteArray
pero esta clase no almacena en caché el flujo de entrada que podrían necesitar otros filtros en la cadena de filtros (que es mi caso de uso). En mi humilde opinión, este es un comportamiento no esperado de una clase de almacenamiento en caché de contenido, por lo tanto, planteé esta mejora en el equipo de primavera jira.spring.io/browse/SPR-16028AbstractRequestLoggingFilter
desde Spring, donde Spring ya realiza la mayor parte del trabajo y solo necesita anular 1 o 2 métodos simples.spring-web-4.3.12.RELEASE
. Cuando verifiqué la fuente, encontré que la variablecachedContent
se usa para almacenar varios contenidos, como los parámetros de solicitud y la solicitud inputStream. Está vacío si llamagetContentAsByteArray()
únicamente. Para obtener el cuerpo de la solicitud hay que llamargetInputStream()
. Pero nuevamente, esto hará que inputStream no esté disponible para otros filtros y el controlador.Las respuestas anteriores fueron muy útiles, pero aún tuve algunos problemas en mi experiencia. En el servlet 3.0 de Tomcat 7, getParamter y getParamterValues también tuvieron que sobrescribirse. La solución aquí incluye tanto parámetros get-query como post-body. Permite obtener cuerdas sin procesar fácilmente.
Como las otras soluciones, usa Apache commons-io y Googles Guava.
En esta solución, los métodos getParameter * no lanzan IOException pero usan super.getInputStream () (para obtener el cuerpo) que puede lanzar IOException. Lo atrapo y lanzo runtimeException. No es tan agradable.
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(); } }
fuente
decode(getPostBodyAsString(), result);
engetParameterMap()
? Eso crea un parámetro con key = request body y value = null, lo cual es bastante extraño.super.getParameterMap()
a sugetParameterMap
? Lo que te dará un mapa de<String, String[]>
todos modos.La única forma sería que usted mismo consumiera todo el flujo de entrada en el filtro, tome lo que quiera de él y luego cree un nuevo InputStream para el contenido que lea y coloque ese InputStream en un ServletRequestWrapper (o HttpServletRequestWrapper).
La desventaja es que tendrá que analizar la carga útil usted mismo, el estándar no pone esa capacidad a su disposición.
Adenda --
Como dije, debe mirar HttpServletRequestWrapper.
En un filtro, continúa llamando a FilterChain.doFilter (solicitud, respuesta).
Para los filtros triviales, la solicitud y la respuesta son las mismas que las que se pasaron al filtro. Ese no tiene por qué ser el caso. Puede reemplazarlos con sus propias solicitudes y / o respuestas.
HttpServletRequestWrapper está diseñado específicamente para facilitar esto. Le pasa la solicitud original y luego puede interceptar todas las llamadas. Usted crea su propia subclase de esto y reemplaza el método getInputStream con uno propio. No puede cambiar el flujo de entrada de la solicitud original, por lo que tiene este contenedor y devuelve su propio flujo de entrada.
El caso más simple es consumir el flujo de entrada de las solicitudes originales en un búfer de bytes, hacer la magia que desee en él y luego crear un nuevo ByteArrayInputStream a partir de ese búfer. Esto es lo que se devuelve en su contenedor, que se pasa al método FilterChain.doFilter.
Deberá crear una subclase de ServletInputStream y hacer otro contenedor para su ByteArrayInputStream, pero eso tampoco es gran cosa.
fuente
Yo también tuve el mismo problema y creo que el código a continuación es más simple y funciona para mí.
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())); } }
en la clase filter java,
HttpServletRequest properRequest = ((HttpServletRequest) req); MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(properRequest); req = wrappedRequest; inputJson = IOUtils.toString(req.getReader()); System.out.println("body"+inputJson);
Por favor avíseme si tiene alguna duda
fuente
Entonces, esta es básicamente la respuesta de Lathy PERO actualizada para los requisitos más nuevos para ServletInputStream.
Es decir (para ServletInputStream), uno tiene que implementar:
public abstract boolean isFinished(); public abstract boolean isReady(); public abstract void setReadListener(ReadListener var1);
Este es el objeto de Lathy editado
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())); } }
y en alguna parte (??) encontré esto (que es una clase de primera clase que se ocupa de los métodos "extra".
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; } } };
En última instancia, solo estaba tratando de registrar las solicitudes. Y las piezas anteriores unidas con frankenstein me ayudaron a crear lo siguiente.
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, tome este código con cautela.
La "prueba" MÁS importante es si un POST funciona con una carga útil. Esto es lo que expondrá los problemas de "lectura doble".
pseudo código de ejemplo
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; } }
Puede reemplazar "MyCustomObject" con "Object" simple y simple si solo desea probar.
Esta respuesta está extraída de varias publicaciones y ejemplos diferentes de SOF ... pero tomó un tiempo reunirlo todo, así que espero que ayude a un futuro lector.
Por favor, vote la respuesta de Lathy antes que la mía. No podría haber llegado tan lejos sin él.
A continuación se muestra una / algunas de las excepciones que obtuve mientras resolvía esto.
Parece que algunos de los lugares que "tomé prestados" están aquí:
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
fuente
Spring tiene soporte incorporado para esto con un
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; }
Desafortunadamente, aún no podrá leer la carga útil directamente de la solicitud, pero el parámetro de mensaje de cadena incluirá la carga útil para que pueda tomarla desde allí de la siguiente manera:
String body = message.substring(message.indexOf("{"), message.lastIndexOf("]"));
fuente
Simplemente sobrescribir
getInputStream()
no funcionó en mi caso. La implementación de mi servidor parece analizar los parámetros sin llamar a este método. No encontré ninguna otra forma, pero también volví a implementar los cuatro métodos getParameter *. Aquí está el código degetParameterMap
(Apache Http Client y la biblioteca Google Guava utilizada):@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; }
fuente
Si tiene control sobre la solicitud, puede establecer el tipo de contenido en binary / octet-stream . Esto permite consultar los parámetros sin consumir el flujo de entrada.
Sin embargo, esto puede ser específico de algunos servidores de aplicaciones. Solo probé tomcat, jetty parece comportarse de la misma manera de acuerdo con https://stackoverflow.com/a/11434646/957103 .
fuente
El método getContentAsByteArray () de la clase Spring ContentCachingRequestWrapper lee el cuerpo varias veces, pero los métodos getInputStream () y getReader () de la misma clase no leen el cuerpo varias veces:
"Esta clase almacena en caché el cuerpo de la solicitud consumiendo InputStream. Si leemos InputStream en uno de los filtros, otros filtros posteriores en la cadena de filtros ya no pueden leerlo. Debido a esta limitación, esta clase no es adecuada en todos situaciones ".
En mi caso, la solución más general que resolvió este problema fue agregar las siguientes tres clases a mi proyecto de arranque de Spring (y las dependencias requeridas al archivo 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); } }
También agregué las siguientes dependencias a 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>
Un tutorial y el código fuente completo se encuentra aquí: https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times
fuente
puede usar la cadena de filtros de servlets, pero en su lugar use la original, puede crear su propia solicitud yourownrequests extiende HttpServletRequestWrapper.
fuente
En primer lugar, no debemos leer parámetros dentro del filtro. Por lo general, los encabezados se leen en el filtro para realizar algunas tareas de autenticación. Habiendo dicho eso, uno puede leer el cuerpo de HttpRequest completamente en el Filtro o Interceptor usando CharStreams:
Esto no afecta en absoluto las lecturas posteriores.
fuente
request.getReader()
devolverá un lector que solo contiene una cadena vacía en las lecturas posteriores.