110 votes

La requête Http Servlet perd les paramètres du corps POST après l'avoir lu une fois.

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 ! De ce fait, il n'est plus disponible dans la chaîne de filtres.

Il semble que cela ne se produise que lorsque les paramètres se trouvent dans le corps d'une requête POST (un formulaire à soumettre, 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 : Filtre Servlet utilisant request.getParameter perd les données du formulaire .

Merci !

7voto

Will Hartung Points 57465

La seule solution serait que vous consommiez vous-même l'intégralité du flux d'entrée dans le filtre, que vous preniez ce que vous voulez, puis que vous créiez un nouvel InputStream pour le contenu que vous lisez, et que vous mettiez cet InputStream dans un ServletRequestWrapper (ou HttpServletRequestWrapper).

L'inconvénient est que vous devrez analyser vous-même la charge utile, la norme ne vous offrant pas cette possibilité.

Addenda --

Comme je l'ai dit, vous devez vous pencher sur HttpServletRequestWrapper.

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 passées au filtre. Ce n'est pas forcément le cas. Vous pouvez les remplacer par vos propres demandes et/ou réponses.

HttpServletRequestWrapper est spécifiquement conçu pour faciliter cela. Vous lui passez la requête originale, et vous pouvez ensuite intercepter tous les appels. Vous créez votre propre sous-classe et remplacez la méthode getInputStream par la vôtre. Vous ne pouvez pas modifier le flux d'entrée de la requête originale, donc à la place, vous avez ce wrapper et retournez votre propre flux d'entrée.

Le cas le plus simple est de transformer le flux d'entrée des requêtes originales en un tampon d'octets, d'effectuer toutes les opérations magiques que vous souhaitez, puis de créer un nouveau flux d'entrée ByteArrayInputStream à partir de ce tampon. C'est ce qui est renvoyé dans votre wrapper, qui est transmis à la méthode FilterChain.doFilter.

Vous devrez sous-classer ServletInputStream et créer un autre wrapper pour votre ByteArrayInputStream, mais ce n'est pas un problème non plus.

7voto

Andrew Sneck Points 21

J'ai trouvé une bonne solution pour tout format de corps de requête. J'ai testé pour application/x-www-form-urlencoded y application/json Les deux ont très bien fonctionné. Problème de ContentCachingRequestWrapper qui est conçu uniquement pour x-www-form-urlencoded corps de la demande, mais ne fonctionne pas avec, par exemple, json. J'ai trouvé une solution pour json lien . Il avait des problèmes qu'il ne supportait pas x-www-form-urlencoded . J'ai joint les deux dans mon code :

import org.apache.commons.io.IOUtils;
import org.springframework.web.util.ContentCachingRequestWrapper;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class MyContentCachingRequestWrapper extends ContentCachingRequestWrapper {

    private byte[] body;

    public MyContentCachingRequestWrapper(HttpServletRequest request) throws IOException {
        super(request);
        super.getParameterMap(); // init cache in ContentCachingRequestWrapper
        body = super.getContentAsByteArray(); // first option for application/x-www-form-urlencoded
        if (body.length == 0) {
          try {
            body = IOUtils.toByteArray(super.getInputStream()); // second option for other body formats
          } catch (IOException ex) {
            body = new byte[0];
          }
        }
    }

    public byte[] getBody() {
        return body;
    }

    @Override
    public ServletInputStream getInputStream() {
        return new RequestCachingInputStream(body);
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding()));
    }

    private static class RequestCachingInputStream extends ServletInputStream {

        private final ByteArrayInputStream inputStream;

        public RequestCachingInputStream(byte[] bytes) {
            inputStream = new ByteArrayInputStream(bytes);
        }

        @Override
        public int read() throws IOException {
            return inputStream.read();
        }

        @Override
        public boolean isFinished() {
            return inputStream.available() == 0;
        }

        @Override
        public boolean isReady() {
            return true;
        }

        @Override
        public void setReadListener(ReadListener readlistener) {
        }

    }

}

6voto

Lathy Points 869

J'ai aussi 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 java du filtre,

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

N'hésitez pas à me contacter si vous avez des questions.

3voto

Markoorn Points 1163

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 les données utiles directement à partir de la requête, mais le paramètre String message inclura les données utiles et vous pourrez les récupérer comme suit :

String body = message.substring(message.indexOf("{"), message.lastIndexOf("]"));

1voto

30thh Points 1662

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 solution que de réimplémenter les quatre méthodes getParameter*. Voici le code de getParameterMap (Client Apache Http 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;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X