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


88

Sto cercando di accedere a due parametri di richiesta http in un filtro Java Servlet, niente di nuovo qui, ma sono rimasto sorpreso di scoprire 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 entrano in un corpo di richiesta POST (un modulo di invio, ad esempio).

C'è un modo per leggere i parametri e NON consumarli?

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

Grazie!


2
forse mostrare un frammento di codice di come lo stai facendo?
Pavel Veller

Hai ottenuto getInputStream () o getReader ()? Sembra che siano quelli che interferiranno con l'esecuzione di getParameter ()
evanwong

Risposte:


115

Per inciso, un modo alternativo per risolvere questo problema è quello di non utilizzare la catena di filtri e invece di costruire il proprio componente interceptor, magari utilizzando degli aspetti, che possono operare sul corpo della richiesta analizzata. Probabilmente sarà anche più efficiente poiché stai convertendo la richiesta InputStreamnel tuo oggetto modello solo una volta.

Tuttavia, penso ancora che sia ragionevole voler leggere il corpo della richiesta più di una volta, in particolare quando la richiesta si sposta attraverso la catena del filtro. In genere utilizzo catene di filtri per determinate operazioni che desidero mantenere a livello HTTP, disaccoppiate dai componenti del servizio.

Come suggerito da Will Hartung, ho ottenuto questo risultato estendendo HttpServletRequestWrapper, consumando la richiesta InputStreamed 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ù di una volta 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 ti consentirà anche di leggere il corpo della richiesta più volte tramite i getParameterXXXmetodi perché la chiamata sottostante è getInputStream(), che ovviamente leggerà la richiesta memorizzata nella cache InputStream.

modificare

Per la versione più recente ServletInputStreamdell'interfaccia. È necessario fornire l'implementazione di altri metodi come isReady, setReadListenerecc. Fare riferimento a questa domanda come indicato nel commento di seguito.


5
È vero? La chiamata sottostante è getInputStream () sulla richiesta originale , di cui avrete già letto i byte. La richiesta sottostante non ha alcuna conoscenza del tuo wrapper, quindi come potrebbe sapere di chiamare getInputStream () del wrapper?
Frans

1
Per essere precisi getInputStreamviene chiamato il mio wrapper poiché questa è l' ServletRequestistanza che passo nella catena del filtro. Se hai ancora dei dubbi, leggi il codice sorgente ServletRequestWrappere l' ServletRequestinterfaccia.
pestrella

1
Se potessi fare questo +100, lo farei. Ho cercato di farlo funzionare bene per 3-4 ore. Grazie per il tuo chiaro esempio e spiegazione! Sono contento di aver trovato questo post!
Doug

21
Qualche suggerimento su come farlo funzionare con Servlet-api 3.0+? Il ServletInputStream ora ha abstract isReady(). isFinished()e setReadListener()per gestire l'I / O non bloccante che deve essere implementato. Sto pensando che ReadListener potrebbe essere lasciato vuoto, ma non sono sicuro di cosa fare su isFinished()e / o isReady().
Eric B.

12
@EricB. grazie comunque. In seguito ho trovato la soluzione per la nuova interfaccia api, incollata qui nel caso qualcuno fosse interessato. stackoverflow.com/questions/29208456/…
dcc

37

So di essere in ritardo, ma questa domanda era ancora rilevante per me e questo post SO è stato uno dei migliori successi di Google. Vado avanti e pubblicherò la mia soluzione nella speranza che qualcun altro possa risparmiare un paio d'ore.

Nel mio caso avevo bisogno di 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

        }

    }

}

3
Questo non ha funzionato per me. Entrambi requestBodyed responseBodyerano stringhe vuote
Abhijith Madhav

5
Errore mio. Stavo facendo un chain.doFilter(request, response);invece di unchain.doFilter(requestWrapper, responseWrapper);
Abhijith Madhav

5
Le ContentCaching*Wrapperclassi hanno il prezzo elevato di consumare il flusso di input, quindi la "memorizzazione nella cache" viene eseguita tramite il metodo, getContentAsByteArrayma questa classe non memorizza nella cache il flusso di input che potrebbe essere necessario per altri filtri nella catena di filtri (che è il mio caso d'uso). Imho, questo è un comportamento non previsto di una classe di caching dei contenuti, quindi ho aumentato questo miglioramento nel team primaverile jira.spring.io/browse/SPR-16028
Federico Piazza

Puoi usare AbstractRequestLoggingFilterda Spring, dove la maggior parte del lavoro è già stata eseguita da Spring e devi solo sovrascrivere 1 o 2 metodi semplici.
duro

1
Questo non funziona per me a partire da spring-web-4.3.12.RELEASE. Come ho controllato la sorgente ho scoperto che la variabile cachedContentviene utilizzata per memorizzare vari contenuti come parametri di richiesta e richiesta inputStream. È vuoto se chiami getContentAsByteArray()esclusivamente. Per ottenere il corpo della richiesta è necessario chiamare getInputStream(). Ma ancora una volta, questo renderà inputStream non disponibile per altri filtri e per il gestore.
Ivan Huang

7

Le risposte di cui sopra sono state molto utili, ma hanno avuto ancora 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. Consente di ottenere facilmente corde grezze.

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

In questa soluzione i metodi getParameter * non lanciano IOException ma usano super.getInputStream () (per ottenere il corpo) che può lanciare 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<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();
    }
}

È fantastico! Ho cercato di capirlo per giorni e funziona con servlet 3.1. Una domanda: perché lo fai decode(getPostBodyAsString(), result);in getParameterMap()? Ciò crea un parametro con key = request body e value = null, il che è piuttosto strano.
orlade

Piuttosto che analizzare tutta la stringa, perché non chiami il super.getParameterMap()tuo getParameterMap? Che ti darà <String, String[]>comunque una mappa .
Ean V

6

L'unico modo sarebbe quello di consumare l'intero flusso di input da solo nel filtro, prendere quello che vuoi da esso, quindi creare un nuovo InputStream per il contenuto che leggi e inserire quell'InputStream in un ServletRequestWrapper (o HttpServletRequestWrapper).

Lo svantaggio è che dovrai analizzare tu stesso il carico utile, lo standard non ti rende disponibile questa funzionalità.

Addenda -

Come ho detto, devi guardare HttpServletRequestWrapper.

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

Per filtri banali, la richiesta e la risposta sono le stesse di quelle passate al filtro. Non deve essere così. Puoi sostituirli con le tue richieste e / o risposte.

HttpServletRequestWrapper è progettato specificamente per facilitare questa operazione. Si passa la richiesta originale e quindi si possono intercettare tutte le chiamate. Crei la tua sottoclasse e sostituisci il metodo getInputStream con uno tuo. Non puoi modificare il flusso di input della richiesta originale, quindi hai questo wrapper e restituisci il tuo flusso di input.

Il caso più semplice è consumare il flusso di input delle richieste originali in un buffer di byte, eseguire la magia che desideri 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.

Avrai bisogno di sottoclassare ServletInputStream e creare un altro wrapper per il tuo ByteArrayInputStream, ma neanche questo è un grosso problema.


Non riesco a leggere InputStream e ripristinarlo dopo, non ci sono metodi get / set per l'accesso diretto al flusso. La tua proposta sembra buona, ma non vedo come attuarla.
amuniz

5

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 classe java del filtro,

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


3

Quindi questa è fondamentalmente la risposta di Lathy MA aggiornata per i requisiti più recenti per ServletInputStream.

Vale a dire (per ServletInputStream), è necessario implementare:

public abstract boolean isFinished();

public abstract boolean isReady();

public abstract void setReadListener(ReadListener var1);

Questo è l'oggetto modificato di Lathy

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

e da qualche parte (??) ho trovato questo (che è una classe di prima classe che si occupa dei metodi "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;
        }
    }
};

Alla fine, stavo solo cercando di registrare le richieste. E i pezzi sopra frankensteined insieme mi hanno aiutato a creare il sotto.

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

Per favore, prendi questo codice con le pinze.

Il "test" PIÙ importante è se un POST funziona con un payload. Questo è ciò che esporrà problemi di "doppia lettura".

pseudo codice di esempio

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

Puoi sostituire "MyCustomObject" con il semplice "Object" se vuoi solo testare.

Questa risposta è basata su diversi post ed esempi SOF ... ma ci è voluto un po 'per metterla insieme, quindi spero che aiuti un futuro lettore.

Per favore vota la risposta di Lathy prima della mia. Non sarei potuto arrivare così lontano senza di essa.

Di seguito è riportata una / alcune delle eccezioni che ho riscontrato durante l'elaborazione.

getReader () è già stato chiamato per questa richiesta

Sembra che alcuni dei luoghi da cui ho "preso in prestito" siano qui:

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 ha il supporto integrato per questo con 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;
}

Sfortunatamente non sarai ancora in grado di leggere il payload direttamente dalla richiesta, ma il parametro String message includerà il payload in modo da poterlo prelevare da lì come segue:

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


Speravo di utilizzare la tua soluzione per generare un registro di controllo, ma ho bisogno di registrare se la richiesta ha avuto successo, posso collegarmi alla risposta http e ottenere il codice all'interno di questa classe.
jonesy

1

La sola 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 altro modo, ma reimplementare anche tutti e quattro i metodi getParameter *. Ecco il codice di getParameterMap(Apache Http Client e Google Guava library utilizzati):

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


Probabilmente stai usando Tomcat 7 o superiore con Servlet 3.0? Hai il codice anche per gli altri 3 metodi?
Argento

Altri 3 metodi chiamano semplicemente getParameterMap () e recuperano il valore necessario.
30

0

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

Tuttavia, questo potrebbe essere specifico per alcuni server delle applicazioni. Ho testato solo tomcat, jetty sembra comportarsi allo stesso modo secondo https://stackoverflow.com/a/11434646/957103 .


0

Il metodo getContentAsByteArray () della classe Spring ContentCachingRequestWrapper legge il corpo più volte, ma i metodi getInputStream () e getReader () della stessa classe non leggono il corpo più volte:

"Questa classe memorizza nella cache il corpo della richiesta consumando InputStream. Se leggiamo InputStream in uno dei filtri, gli altri filtri successivi nella catena di filtri non possono più leggerlo. A causa di questa limitazione, questa classe non è adatta a tutti situazioni. "

Nel mio caso la soluzione più generale che ha risolto questo problema è stata l'aggiunta delle seguenti tre classi al mio progetto Spring boot (e le dipendenze richieste al file 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);
    }
}

Ho anche aggiunto le seguenti dipendenze a 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 codice sorgente completo e tuturiale si trova qui: https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times


0

Ho trovato una buona soluzione per qualsiasi formato di corpo della richiesta. Ho provato application/x-www-form-urlencodeded application/jsonentrambi hanno funzionato molto bene. Il problema di ContentCachingRequestWrapperquesto è progettato solo per il x-www-form-urlencodedcorpo della richiesta, ma non funziona con, ad esempio, json. Ho trovato una soluzione per json link . Aveva problemi che non supportava x-www-form-urlencoded. Ho unito entrambi nel mio codice:

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) {
            body = IOUtils.toByteArray(super.getInputStream()); // second option for other body formats
        }
    }

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

    }

}

-1

puoi usare la catena di filtri servlet, ma invece usare quella originale, puoi creare la tua richiesta yourownrequests estende HttpServletRequestWrapper.


1
Sembra che il collegamento al tutorial contenga un virus ora.
Fasth

-2

Prima di tutto non dovremmo leggere i parametri all'interno del filtro. Di solito le intestazioni vengono lette nel filtro per eseguire poche attività di autenticazione. Detto questo, è possibile leggere completamente il corpo HttpRequest nel Filter o Interceptor utilizzando CharStreams:

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

Ciò non influisce affatto sulle letture successive.


si lo fa. Se lo fai una volta, request.getReader()restituirà un lettore che contiene solo una stringa vuota nelle letture successive.
christophetd

1
Lavorerei in caso di sovrascrittura dei metodi getReader () e getInputStream () per utilizzare questo nuovo corpo come sorgente.
Rodrigo Borba
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.