La requête de Servlet Http perd des params du corps du POST après l'avoir lue une fois

j'essaie d'accéder à deux paramètres de requête http dans un filtre Servlet Java, rien de nouveau ici, mais j'ai été surpris de constater que les paramètres ont déjà été consommés! De ce fait, il n'est plus disponible dans la chaîne de filtrage.

il semble que cela ne se produit que lorsque les paramètres sont fournis dans un corps de requête POST (un formulaire submit, par exemple).

y a-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: Servlet Filter using request.getParameter perd les données de forme .

Merci!

60
demandé sur stites 2012-04-18 17:40:50

10 réponses

comme une mise à part, une autre façon de résoudre ce problème est de ne pas utiliser la chaîne de filtrage et de construire votre propre composant d'intercepteur, peut-être en utilisant des aspects, qui peuvent fonctionner sur le corps de requête parsé. Il sera aussi probablement plus efficace car vous ne convertissez la requête InputStream en votre propre objet model qu'une seule fois.

cependant, je pense toujours qu'il est raisonnable de vouloir lire le corps de la demande plus d'une fois en particulier que la demande se déplace à travers la chaîne de filtrage. J'utiliserais typiquement des chaînes de filtrage pour certaines opérations que je veux garder à la couche HTTP, découplées des composants de service.

comme suggéré par Will Hartung j'ai atteint cet objectif en étendant HttpServletRequestWrapper , en consommant la requête InputStream et en cachant essentiellement 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 originale avant de la passer à travers la chaîne de filtrage:

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 plusieurs fois le corps de la requête via les méthodes getParameterXXX car l'appel sous-jacent est getInputStream() , qui lira bien sûr la requête en cache InputStream .

72
répondu pestrella 2017-05-23 11:54:44

je sais que je suis en retard, mais cette question était toujours pertinente pour moi et ce post était donc l'un des meilleurs hits dans Google. Je vais de l'avant et poster ma solution dans l'espoir que quelqu'un d'autre pourrait sauver quelques heures.

dans mon cas, j'ai dû enregistrer toutes les demandes et les réponses avec leur corps. En utilisant Spring Framework la réponse est en fait très simple, il suffit d'utiliser ContentCachingRequestWrapper et Contentcachingresponseewrapper .

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

        }

    }

}
24
répondu Mikk 2016-08-25 20:33:47

la seule façon serait pour vous de consommer l'ensemble du flux d'entrée vous-même dans le filtre, de prendre ce que vous voulez de lui, puis de créer un nouveau flux D'Entrée pour le contenu que vous lisez, et de mettre ce flux D'entrée dans un ServletRequestWrapper (ou HttpServletRequestWrapper).

l'inconvénient est que vous aurez à analyser la charge utile vous-même, la norme ne rend pas cette capacité disponible pour vous.

Addenda --

Comme Je L'Ai said,vous devez regarder Httpservrequetrequestwrapper.

dans un filtre, vous continuez en appelant FilterChain.doFilter (request, response).

pour les filtres triviaux, la requête et la réponse sont les mêmes que celles transmises au filtre. Qui n'a pas à être le cas. Vous pouvez les remplacer par vos propres requêtes et/ou réponses.

HttpServletRequestWrapper est spécialement conçu pour faciliter cela. Vous transmettez à l' demande d'origine, et 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 changer le flux d'entrée de la requête originale, donc vous avez ce wrapper et retournez votre propre flux d'entrée.

le cas le plus simple est de consommer le flux d'entrée des requêtes originales dans un tampon byte, faire la magie que vous voulez dessus, puis créer un nouveau ByteArrayInputStream à partir de ce tampon. Ce est ce qui est retourné dans votre emballage, qui est passé à la chaîne de filtrage.méthode doFilter.

vous aurez besoin de sous-classe ServletInputStream et de faire un autre wrapper pour votre ByteArrayInputStream, mais ce n'est pas une grande affaire non plus.

3
répondu Will Hartung 2012-04-19 00:13:10

les réponses ci-dessus ont été très utiles, mais j'ai quand même eu des problèmes d'expérience. Sur tomcat 7 servlet 3.0, les valeurs getParamter et getparamter ont également dû être écrasées. La solution ici comprend à la fois les paramètres get-query et le post-body. Il permet d'obtenir raw-string facilement.

comme les autres solutions, il utilise Apache commons-io et Googles Guava.

dans cette solution, les méthodes getParameter* ne jettent pas L'exception mais ils utilisent super.getInputStream () (pour récupérer le corps) qui peut lancer IOException. Je l'attrape et je lance runtimeException. Il n'est pas si agréable.

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();
    }
}
3
répondu arberg 2016-04-14 11:16:50

Juste d'écraser des getInputStream() n'a pas fonctionné dans mon cas. Mon implémentation du serveur semble analyser les paramètres sans appeler cette méthode. Je n'ai pas trouvé d'autre solution, mais j'ai aussi ré-implémenté les quatre méthodes getParameter*. Voici le code de getParameterMap (Client Http Apache 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;
}
1
répondu 30thh 2014-09-25 08:54:50

moi aussi j'ai eu le même problème et je crois que le code ci-dessous est plus simple et 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 de filtre java,

            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 requêtes

1
répondu Lathy 2015-08-05 11:36:37

Avoir un coup d'oeil (ou d'utilisation) Printemps AbstractRequestLoggingFilter

0
répondu GKislin 2013-11-26 08:25:47

si vous avez le contrôle de la requête, vous pouvez définir le type de contenu à binaire/octet-stream . Cela permet d'interroger les paramètres sans consommer le flux d'entrée.

cependant, cela pourrait être spécifique à certains serveurs d'application. J'ai seulement testé tomcat, jetty semble se comporter de la même façon selon https://stackoverflow.com/a/11434646/957103 .

0
répondu Olivier.Roger 2017-05-23 12:10:07

tout d'Abord, nous ne devons pas lire les paramètres à l'intérieur du filtre. Habituellement, les en-têtes sont lus dans le filtre pour faire quelques tâches d'authentification. Cela dit, on peut lire le corps HttpRequest complètement dans le filtre ou L'intercepteur en utilisant les CharStreams:

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

Cela n'affecte pas les lectures subséquentes.

-1
répondu ashoka 2016-12-23 17:00:12

vous pouvez utiliser la chaîne de filtrage servlet, mais à la place utilisez la chaîne originale, vous pouvez créer votre propre requête yourownrequests extends HttpServletRequestWrapper.

-1
répondu Zhengwei Zhan 2017-04-27 15:18:40