La demande de servlet HTTP perd les paramètres du corps POST après l'avoir lu une fois


86

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!


2
peut-être montrer un fragment de code de la façon dont vous le faites?
Pavel Veller

Avez-vous obtenu getInputStream () ou getReader ()? On dirait que ce sont eux qui vont interférer avec l'exécution de getParameter ()
evanwong

Réponses:


111

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 InputStreamen 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 demande InputStreamet 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 getParameterXXXméthodes car l'appel sous-jacent est getInputStream(), qui lira bien sûr la requête mise en cache InputStream.

Éditer

Pour une version plus récente de l' ServletInputStreaminterface. Vous devez fournir la mise en œuvre de quelques méthodes supplémentaires comme isReady, setReadListeneretc. Référez-vous à cette question comme indiqué dans le commentaire ci-dessous.


5
Est-ce vrai? L'appel sous-jacent est getInputStream () sur la requête d'origine , dont vous aurez déjà lu les octets. La requête sous-jacente n'a aucune connaissance de votre wrapper, alors comment saurait-elle appeler le getInputStream () du wrapper?
Frans

1
Pour être précis, getInputStreamon appelle mon wrapper car c'est l' ServletRequestinstance que je passe dans la chaîne de filtres. Si vous avez encore des doutes, lisez le code source ServletRequestWrapperet l' ServletRequestinterface.
pestrella

1
Si je pouvais faire ce +100, je le ferais. J'ai essayé de faire fonctionner cela correctement pendant 3-4 heures. Merci pour votre exemple clair et vos explications! Je suis content d'avoir trouvé ce post!
Doug

20
Des suggestions pour faire fonctionner cela avec Servlet-api 3.0+? Le ServletInputStream a maintenant abstract isReady(). isFinished()et setReadListener()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 de isFinished()et / ou isReady().
Eric B.

12
@EricB. Merci quand même. J'ai trouvé plus tard la solution pour la nouvelle interface API, juste collée ici au cas où quelqu'un serait intéressé. stackoverflow.com/questions/29208456/…
dcc

37

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

        }

    }

}

3
Cela n'a pas fonctionné pour moi. Les deux requestBodyet responseBodyétaient des chaînes vides
Abhijith Madhav

5
Mon erreur. Je faisais un chain.doFilter(request, response);au lieu d'unchain.doFilter(requestWrapper, responseWrapper);
Abhijith Madhav

5
Les ContentCaching*Wrapperclasses ont le prix élevé de la consommation du flux d'entrée, donc la "mise en cache" se fait via la méthode getContentAsByteArraymais 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-16028
Federico Piazza

Vous pouvez utiliser à AbstractRequestLoggingFilterpartir 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.
dur

1
Cela ne fonctionne pas pour moi depuis spring-web-4.3.12.RELEASE. Lorsque j'ai vérifié la source, j'ai trouvé que la variable cachedContentest utilisée pour stocker divers contenus tels que les paramètres de requête et la requête inputStream. Il est vide si vous appelez getContentAsByteArray()uniquement. Pour obtenir le corps de la requête, vous devez appeler getInputStream(). Mais encore une fois, cela rendra le inputStream indisponible pour les autres filtres et le gestionnaire.
Ivan Huang

7

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

C'est bien! J'essaie de comprendre cela depuis des jours, et cela fonctionne avec la servlet 3.1. Une question: pourquoi faites - vous decode(getPostBodyAsString(), result);en getParameterMap()? Cela crée un paramètre avec clé = corps de la demande et valeur = null, ce qui est assez étrange.
orlade

Plutôt que de parcourir toute l'analyse des chaînes, pourquoi n'appelez-vous pas super.getParameterMap()votre getParameterMap? Ce qui vous donnera une carte de <String, String[]>toute façon.
Ean V

6

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.


Je n'arrive pas à lire le InputStream et à le restaurer après, il n'y a pas de méthode get / set pour accéder directement au flux. Votre proposition semble bonne, mais je ne vois pas comment la mettre en œuvre.
amuniz

4

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


3

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.

getReader () a déjà été appelé pour cette requête

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


3

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("]"));


J'espérais utiliser votre solution pour générer un journal d'audit, mais je dois enregistrer si la demande a abouti, puis-je me connecter à la réponse http et obtenir le code dans cette classe.
jonesy

1

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 de getParameterMap(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;
}


Vous utilisez probablement Tomcat 7 ou supérieur avec Servlet 3.0? Avez-vous également le code des 3 autres méthodes?
Argent

Les 3 autres méthodes appellent simplement getParameterMap () et récupèrent la valeur nécessaire.
30

0

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 .


0

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


-1

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.


1
Il semble que le lien vers le tutoriel contient un virus maintenant.
fasth

-2

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:

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

Cela n'affecte pas du tout les lectures suivantes.


Oui. Si vous faites cela une fois, request.getReader()retournera un lecteur qui ne contient qu'une chaîne vide sur les lectures suivantes.
christophetd

1
Je travaillerais en cas d'écrasement des méthodes getReader () et getInputStream () pour utiliser ce nouveau corps comme source.
Rodrigo Borba
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.