J'essaie d'accéder à deux paramètres de requête http dans un filtre Java Servlet, rien de nouveau ici, mais j'ai été surpris de constater que les paramètres ont déjà été consommés! Pour cette raison, il n'est plus disponible dans la chaîne de filtres.
Il semble que cela ne se produise que lorsque les paramètres arrivent dans un corps de requête POST (une soumission de formulaire, par exemple).
Existe-t-il un moyen de lire les paramètres et de NE PAS les consommer?
Jusqu'à présent, je n'ai trouvé que cette référence: le filtre de servlet utilisant request.getParameter perd les données du formulaire .
Merci!
Réponses:
En passant, une autre façon de résoudre ce problème est de ne pas utiliser la chaîne de filtres et de créer à la place votre propre composant d'intercepteur, peut-être en utilisant des aspects, qui peuvent fonctionner sur le corps de la requête analysé. Cela sera également probablement plus efficace car vous ne convertissez qu'une seule fois la demande
InputStream
en votre propre objet modèle.Cependant, je pense toujours qu'il est raisonnable de vouloir lire le corps de la requête plus d'une fois, d'autant plus que la requête se déplace dans la chaîne de filtres. J'utiliserais généralement des chaînes de filtres pour certaines opérations que je souhaite conserver au niveau de la couche HTTP, découplées des composants de service.
Comme suggéré par Will Hartung, j'ai réalisé cela en étendant
HttpServletRequestWrapper
, en consommant la demandeInputStream
et en mettant essentiellement en cache les octets.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(); } } }
Maintenant, le corps de la requête peut être lu plus d'une fois en enveloppant la requête d'origine avant de la passer à travers la chaîne de filtres:
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); } }
Cette solution vous permettra également de lire le corps de la requête plusieurs fois via les
getParameterXXX
méthodes car l'appel sous-jacent estgetInputStream()
, qui lira bien sûr la requête mise en cacheInputStream
.Éditer
Pour une version plus récente de l'
ServletInputStream
interface. Vous devez fournir la mise en œuvre de quelques méthodes supplémentaires commeisReady
,setReadListener
etc. Référez-vous à cette question comme indiqué dans le commentaire ci-dessous.la source
getInputStream
on appelle mon wrapper car c'est l'ServletRequest
instance que je passe dans la chaîne de filtres. Si vous avez encore des doutes, lisez le code sourceServletRequestWrapper
et l'ServletRequest
interface.isReady()
.isFinished()
etsetReadListener()
pour traiter les E / S non bloquantes qui doivent être implémentées. Je pense que le ReadListener pourrait être laissé vide, mais je ne sais pas quoi faire à propos deisFinished()
et / ouisReady()
.Je sais que je suis en retard, mais cette question était toujours d'actualité pour moi et ce message SO était l'un des meilleurs succès de Google. Je vais de l'avant et publie ma solution dans l'espoir que quelqu'un d'autre puisse gagner quelques heures.
Dans mon cas, je devais enregistrer toutes les demandes et réponses avec leurs corps. En utilisant Spring Framework, la réponse est en fait assez simple, utilisez simplement ContentCachingRequestWrapper et 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 } } }
la source
requestBody
etresponseBody
étaient des chaînes videschain.doFilter(request, response);
au lieu d'unchain.doFilter(requestWrapper, responseWrapper);
ContentCaching*Wrapper
classes ont le prix élevé de la consommation du flux d'entrée, donc la "mise en cache" se fait via la méthodegetContentAsByteArray
mais cette classe ne met pas en cache le flux d'entrée qui pourrait être nécessaire par d'autres filtres dans la chaîne de filtres (ce qui est mon cas d'utilisation). A mon humble avis, ce n'est pas un comportement attendu d'une classe de mise en cache de contenu, j'ai donc soulevé cette amélioration dans l'équipe de printemps jira.spring.io/browse/SPR-16028AbstractRequestLoggingFilter
partir de Spring, où la plupart du travail est déjà effectué par Spring et il vous suffit de remplacer 1 ou 2 méthodes simples.spring-web-4.3.12.RELEASE
. Lorsque j'ai vérifié la source, j'ai trouvé que la variablecachedContent
est utilisée pour stocker divers contenus tels que les paramètres de requête et la requête inputStream. Il est vide si vous appelezgetContentAsByteArray()
uniquement. Pour obtenir le corps de la requête, vous devez appelergetInputStream()
. Mais encore une fois, cela rendra le inputStream indisponible pour les autres filtres et le gestionnaire.Les réponses ci-dessus ont été très utiles, mais ont encore eu quelques problèmes dans mon expérience. Sur tomcat 7 servlet 3.0, les valeurs getParamter et getParamterValues ont également dû être écrasées. La solution ici comprend à la fois les paramètres get-query et le post-body. Cela permet d'obtenir facilement des chaînes brutes.
Comme les autres solutions, il utilise Apache commons-io et Googles Guava.
Dans cette solution, les méthodes getParameter * ne lèvent pas IOException mais elles utilisent super.getInputStream () (pour obtenir le corps) qui peut lever IOException. Je l'attrape et lance runtimeException. Ce n'est pas si gentil.
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(); } }
la source
decode(getPostBodyAsString(), result);
engetParameterMap()
? Cela crée un paramètre avec clé = corps de la demande et valeur = null, ce qui est assez étrange.super.getParameterMap()
votregetParameterMap
? Ce qui vous donnera une carte de<String, String[]>
toute façon.La seule façon serait pour vous de consommer vous-même tout le flux d'entrée dans le filtre, d'en tirer ce que vous voulez, puis de créer un nouveau InputStream pour le contenu que vous lisez, et de placer ce InputStream dans un ServletRequestWrapper (ou HttpServletRequestWrapper).
L'inconvénient est que vous devrez analyser vous-même la charge utile, la norme ne met pas cette capacité à votre disposition.
Addenda -
Comme je l'ai dit, vous devez regarder HttpServletRequestWrapper.
Dans un filtre, vous continuez en appelant FilterChain.doFilter (requête, réponse).
Pour les filtres triviaux, la demande et la réponse sont les mêmes que celles transmises au filtre. Cela ne doit pas être le cas. Vous pouvez les remplacer par vos propres demandes et / ou réponses.
HttpServletRequestWrapper est spécialement conçu pour faciliter cela. Vous lui transmettez la demande d'origine, puis vous pouvez intercepter tous les appels. Vous créez votre propre sous-classe de ceci et remplacez la méthode getInputStream par l'une des vôtres. Vous ne pouvez pas modifier le flux d'entrée de la demande d'origine, vous avez donc à la place ce wrapper et renvoyez votre propre flux d'entrée.
Le cas le plus simple est de consommer le flux d'entrée des requêtes d'origine dans un tampon d'octets, de faire la magie que vous voulez dessus, puis de créer un nouveau ByteArrayInputStream à partir de ce tampon. C'est ce qui est renvoyé dans votre wrapper, qui est passé à la méthode FilterChain.doFilter.
Vous devrez sous-classer ServletInputStream et créer un autre wrapper pour votre ByteArrayInputStream, mais ce n'est pas non plus un gros problème.
la source
J'ai aussi eu le même problème et je pense que le code ci-dessous est plus simple et qu'il fonctionne pour moi,
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())); } }
dans la classe java filter,
HttpServletRequest properRequest = ((HttpServletRequest) req); MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(properRequest); req = wrappedRequest; inputJson = IOUtils.toString(req.getReader()); System.out.println("body"+inputJson);
S'il vous plaît laissez-moi savoir si vous avez des questions
la source
C'est donc essentiellement la réponse de Lathy MAIS mise à jour pour les nouvelles exigences de ServletInputStream.
A savoir (pour ServletInputStream), il faut implémenter:
public abstract boolean isFinished(); public abstract boolean isReady(); public abstract void setReadListener(ReadListener var1);
Ceci est l'objet de Lathy édité
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())); } }
et quelque part (??) j'ai trouvé ceci (qui est une classe de première classe qui traite des méthodes "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 fin de compte, j'essayais simplement de consigner les demandes. Et les pièces ensemble frankensteined ci-dessus m'ont aidé à créer le ci-dessous.
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; } }
Veuillez prendre ce code avec un grain de sel.
Le «test» le plus important est de savoir si un POST fonctionne avec une charge utile. C'est ce qui exposera les problèmes de «double lecture».
pseudo exemple de code
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; } }
Vous pouvez remplacer "MyCustomObject" par "Object" si vous voulez juste tester.
Cette réponse est tirée de plusieurs articles et exemples SOF différents .. mais il a fallu un certain temps pour tout rassembler donc j'espère que cela aidera un futur lecteur.
Veuillez voter pour la réponse de Lathy avant la mienne. Je n'aurais pas pu aller aussi loin sans lui.
Vous trouverez ci-dessous une / certaines des exceptions que j'ai obtenues en travaillant sur ce problème.
Il semble que certains des endroits où j'ai "emprunté" se trouvent ici:
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
la source
Spring a un support intégré pour cela avec 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; }
Malheureusement, vous ne pourrez toujours pas lire la charge utile directement à partir de la demande, mais le paramètre de message String inclura la charge utile afin que vous puissiez la récupérer à partir de là comme suit:
String body = message.substring(message.indexOf("{"), message.lastIndexOf("]"));
la source
L'écrasement de
getInputStream()
n'a pas fonctionné dans mon cas. L'implémentation de mon serveur semble analyser les paramètres sans appeler cette méthode. Je n'ai pas trouvé d'autre moyen, mais ré-implémentez également les quatre méthodes getParameter *. Voici le code degetParameterMap
(Apache Http Client et bibliothèque Google Guava utilisés):@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; }
la source
Si vous avez le contrôle sur la demande, vous pouvez définir le type de contenu sur binaire / octet-stream . Cela permet d'interroger des paramètres sans consommer le flux d'entrée.
Cependant, cela peut être spécifique à certains serveurs d'applications. Je n'ai testé que tomcat, jetty semble se comporter de la même manière selon https://stackoverflow.com/a/11434646/957103 .
la source
La méthode getContentAsByteArray () de la classe Spring ContentCachingRequestWrapper lit le corps plusieurs fois, mais les méthodes getInputStream () et getReader () de la même classe ne lisent pas le corps plusieurs fois:
"Cette classe met en cache le corps de la requête en consommant le InputStream. Si nous lisons le InputStream dans l'un des filtres, les autres filtres suivants de la chaîne de filtres ne pourront plus le lire. En raison de cette limitation, cette classe ne convient pas à tous situations. "
Dans mon cas, une solution plus générale qui a résolu ce problème était d'ajouter les trois classes suivantes à mon projet de démarrage Spring (et les dépendances requises au fichier 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); } }
J'ai également ajouté les dépendances suivantes à 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 code source tuturiel et complet se trouve ici: https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times
la source
vous pouvez utiliser la chaîne de filtres de servlet, mais à la place, utilisez la chaîne d'origine, vous pouvez créer votre propre requête yourownrequests étend HttpServletRequestWrapper.
la source
Tout d'abord, nous ne devons pas lire les paramètres dans le filtre. Habituellement, les en-têtes sont lus dans le filtre pour effectuer quelques tâches d'authentification. Cela dit, on peut lire complètement le corps HttpRequest dans le filtre ou l'intercepteur en utilisant les CharStreams:
Cela n'affecte pas du tout les lectures suivantes.
la source
request.getReader()
retournera un lecteur qui ne contient qu'une chaîne vide sur les lectures suivantes.