La richiesta servlet Http perde i parametri dal corpo POST dopo averlo letto una volta

Sto provando ad accedere a due parametri di richiesta http in un filtro Java Servlet, niente di nuovo qui, ma sono rimasto sorpreso nel constatare che i parametri sono già stati consumati! Per questo motivo, non è più disponibile nella catena di filtri.

Sembra che ciò si verifichi solo quando i parametri arrivano in un corpo di richiesta POST (un modulo di invio, ad esempio).

C’è un modo per leggere i parametri e NON li consumano?

Finora ho trovato solo questo riferimento: il filtro servlet che utilizza request.getParameter perde i dati del modulo .

Grazie!

Per inciso, un modo alternativo per risolvere questo problema è non usare la catena di filtri e invece build il proprio componente interceptor, magari usando aspetti, che possono operare sul corpo della richiesta analizzata. Sarà anche probabilmente più efficiente in quanto si converte la richiesta InputStream nel proprio object modello una sola volta.

Tuttavia, continuo a pensare che sia ragionevole voler leggere il corpo della richiesta più di una volta in particolare mentre la richiesta passa attraverso la catena di filtri. Generalmente utilizzo le catene di filtri per determinate operazioni che desidero mantenere sul livello HTTP, disaccoppiate dai componenti del servizio.

Come suggerito da Will Hartung, ho raggiunto questo objective estendendo HttpServletRequestWrapper , consumando la richiesta InputStream e essenzialmente memorizzando nella cache i byte.

 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(); } } } 

Ora il corpo della richiesta può essere letto più volte avvolgendo la richiesta originale prima di passarla attraverso la catena del filtro:

 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); } } 

Questa soluzione consente inoltre di leggere il corpo della richiesta più volte tramite i metodi getInputStream() poiché la chiamata sottostante è getInputStream() , che ovviamente leggerà la richiesta InputStream memorizzata nella cache.

So che sono in ritardo, ma questa domanda era ancora pertinente per me e questo post SO era uno dei migliori successi di Google. Sto andando avanti e posta la mia soluzione nella speranza che qualcun altro possa risparmiare un paio d’ore.

Nel mio caso dovevo registrare tutte le richieste e le risposte con i loro corpi. Utilizzando Spring Framework la risposta è in realtà abbastanza semplice, basta usare 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 } } } 

L’unico modo per te è di consumare l’intero stream di input nel filtro, prendere ciò che desideri da esso, quindi creare un nuovo InputStream per il contenuto che leggi e inserire InputStream in un ServletRequestWrapper (o HttpServletRequestWrapper).

Il rovescio della medaglia è che dovrete analizzare da soli il payload, lo standard non rende disponibile quella funzionalità.

Addenda –

Come ho detto, devi dare un’occhiata a HttpServletRequestWrapper.

In un filtro, si continua chiamando FilterChain.doFilter (richiesta, risposta).

Per i filtri banali, la richiesta e la risposta sono identiche a quelle passate al filtro. Questo non deve essere il caso. Puoi sostituire quelli con le tue richieste e / o risposte.

HttpServletRequestWrapper è progettato specificamente per facilitare questo. Passa la richiesta originale e quindi puoi intercettare tutte le chiamate. Crei la tua sottoclass e sostituisci il metodo getInputStream con uno tuo. Non è ansible modificare il stream di input della richiesta originale, quindi si dispone di questo wrapper e si restituisce il proprio stream di input.

Il caso più semplice è quello di consumare le richieste originali di un stream di input in un buffer di byte, fare qualsiasi magia tu voglia su di esso, quindi creare un nuovo ByteArrayInputStream da quel buffer. Questo è ciò che viene restituito nel tuo wrapper, che viene passato al metodo FilterChain.doFilter.

Dovrai creare una sottoclass di ServletInputStream e creare un altro wrapper per ByteArrayInputStream, ma non è un grosso problema.

Le risposte di cui sopra sono state molto utili, ma hanno comunque avuto alcuni problemi nella mia esperienza. Su tomcat 7 servlet 3.0, anche getParamter e getParamterValues ​​dovevano essere sovrascritti. La soluzione qui include sia i parametri get-query che il post-body. Permette di ottenere facilmente stringhe semplici.

Come le altre soluzioni, utilizza Apache commons-io e Googles Guava.

In questa soluzione i metodi getParameter * non generano IOException ma usano super.getInputStream () (per ottenere il corpo) che può generare IOException. Lo prendo e lancio runtimeException. Non è così carino

 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 parameterMap; public MultiReadHttpServletRequest(HttpServletRequest request) { super(request); } public static void toMap(Iterable inputParams, Map 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 parameterMap = getParameterMap(); String[] values = parameterMap.get(key); return values != null && values.length > 0 ? values[0] : null; } @Override public String[] getParameterValues(String key) { Map parameterMap = getParameterMap(); return parameterMap.get(key); } @Override public Map getParameterMap() { if (parameterMap == null) { Map result = new LinkedHashMap(); decode(getQueryString(), result); decode(getPostBodyAsString(), result); parameterMap = Collections.unmodifiableMap(result); } return parameterMap; } private void decode(String queryString, Map result) { if (queryString != null) toMap(decodeParams(queryString), result); } private Iterable decodeParams(String body) { Iterable 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 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 getInputStream() sovrascrittura di getInputStream() non ha funzionato nel mio caso. L’implementazione del mio server sembra analizzare i parametri senza chiamare questo metodo. Non ho trovato nessun altro modo, ma re-implementare anche tutti e quattro i metodi getParameter *. Ecco il codice di getParameterMap (utilizzato il client Http Apache e Google Guava):

 @Override public Map getParameterMap() { Iterable 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 postParams = URLEncodedUtils.parse(IOUtils.toString(getReader()), NullUtils.UTF8); params = Iterables.concat(params, postParams); } } } catch (IOException e) { throw new IllegalStateException(e); } Map result = toMap(params); return result; } public static Map toMap(Iterable body) { Map 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; } 

Anch’io ho avuto lo stesso problema e credo che il codice sottostante sia più semplice e funzioni per me,

 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())); } } 

nella class java filter,

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

Per favore fatemi sapere se avete domande

Dai un’occhiata (o usa) Spring AbstractRequestLoggingFilter

Se hai il controllo sulla richiesta, puoi impostare il tipo di contenuto su binario / ottetto-stream . Ciò consente di interrogare i parametri senza consumare il stream di input.

Tuttavia, questo potrebbe essere specifico per alcuni server delle applicazioni. Ho provato solo tomcat, il molo sembra comportarsi allo stesso modo in base a https://stackoverflow.com/a/11434646/957103 .

Prima di tutto non dovremmo leggere i parametri all’interno del filtro. Solitamente le intestazioni vengono lette nel filtro per eseguire alcune attività di autenticazione. Detto questo si può leggere il corpo HttpRequest completamente nel Filtro o Interceptor usando i CharStreams:

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

Questo non influisce affatto sulle letture successive.

puoi usare la catena di filtri del servlet, ma invece di usare quella originale, puoi creare la tua richiesta, le tue richieste vengono estese su HttpServletRequestWrapper.