Http Servlet request lose params from POST body after read it once

Próbuję uzyskać dostęp do dwóch parametrów żądania http w filtrze Java Servlet, nic nowego tutaj, ale byłem zaskoczony, że parametry zostały już zużyte! Z tego powodu nie jest już dostępny w łańcuchu filtrów.

Wydaje się, że dzieje się tak tylko wtedy, gdy parametry są dostarczane w ciele żądania POST (na przykład formularz submit).

Czy jest sposób, aby odczytać parametry i ich nie konsumować?

Do tej pory znalazłem tylko to odniesienie: Servlet Filter using request.getParameter traci dane formularza .

Dzięki!

Author: stites, 2012-04-18

10 answers

Na marginesie, alternatywnym sposobem rozwiązania tego problemu jest nieużywanie łańcucha filtrów i zamiast tego zbudowanie własnego komponentu interceptor, być może przy użyciu aspektów, które mogą działać na przetwarzanym ciele żądania. Prawdopodobnie będzie to również bardziej efektywne, ponieważ tylko raz konwertujesz żądanie InputStream do własnego obiektu modelu.

Jednak nadal uważam, że rozsądne jest, aby chcieć przeczytać treść żądania więcej niż raz, szczególnie gdy żądanie przechodzi przez łańcuch filtrów. Ja bym zazwyczaj używaj łańcuchów filtrów do pewnych operacji, które chcę zachować na warstwie HTTP, oddzielonych od komponentów usługi.

Zgodnie z sugestią Will Hartung osiągnąłem to poprzez rozszerzenie HttpServletRequestWrapper, pochłonięcie żądania InputStream i zasadniczo buforowanie bajtów.

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

Teraz treść żądania może być odczytana więcej niż raz, zawijając pierwotne żądanie przed przepuszczeniem go przez łańcuch filtrów:

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

To rozwiązanie pozwoli również na odczytanie żądania body wiele razy za pomocą metod getParameterXXX, ponieważ wywołanie bazowe to getInputStream(), które oczywiście odczyta buforowane żądanie InputStream.

 72
Author: pestrella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:44

Wiem, że się spóźniłem, ale to pytanie nadal było dla mnie istotne i ten post był jednym z najlepszych hitów w Google. Idę dalej i zamieszczam moje rozwiązanie w nadziei, że ktoś inny zaoszczędzi kilka godzin.

W moim przypadku musiałem zapisać wszystkie prośby i Odpowiedzi ich ciałami. Używając Spring Framework odpowiedź jest dość prosta, wystarczy użyć ContentCachingRequestWrapper i 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

        }

    }

}
 24
Author: Mikk,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-25 20:33:47

Jedynym sposobem byłoby użycie całego strumienia wejściowego w filtrze, pobranie z niego tego, co chcesz, a następnie utworzenie nowego strumienia wejściowego dla przeczytanej treści i umieszczenie go w Serwletrequestwrapper (lub HttpServletRequestWrapper).

Minusem jest to, że będziesz musiał sam przeanalizować ładunek, standard nie udostępnia Ci tej możliwości.

Addenda --

Jak już mówiłem, musisz spojrzeć na HttpServletRequestWrapper.

W filtrze kontynuujesz, wywołując FilterChain.doFilter (Prośba, odpowiedź).

Dla filtrów trywialnych, żądanie i odpowiedź są takie same jak te przekazywane do filtra. Nie musi tak być. Możesz je zastąpić własnymi prośbami i / lub odpowiedziami.

HttpServletRequestWrapper został specjalnie zaprojektowany, aby to ułatwić. Przekazujesz oryginalną prośbę, a potem możesz przechwycić wszystkie połączenia. Tworzysz własną podklasę tego i zastąp metodę getInputStream jedną z twoich własnych. Nie możesz zmienić strumienia wejściowego oryginalnego żądania, więc zamiast tego masz ten wrapper i zwracasz własny strumień wejściowy.

Najprostszym przypadkiem jest użycie oryginalnego strumienia wejściowego żądań do bufora bajtowego, wykonanie na nim dowolnej magii, a następnie utworzenie nowego strumienia bajtowego ByteArrayInputStream z tego bufora. To jest to, co jest zwracane w Twoim opakowaniu, które jest przekazywane do łańcucha filtrów.doFilter metoda.

Będziesz musiał podklasować ServletInputStream i zrobić kolejny wrapper dla swojego ByteArrayInputStream, ale to też nie jest wielka sprawa.

 3
Author: Will Hartung,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-19 00:13:10

Powyższe odpowiedzi były bardzo pomocne, ale nadal miałem pewne problemy z moim doświadczeniem. W tomcat 7 servlet 3.0 wartości getParamter i getparamter również musiały zostać nadpisane. Rozwiązanie zawiera zarówno parametry get-query, jak i post-body. Pozwala na uzyskanie raw-string łatwo.

Podobnie jak inne rozwiązania używa Apache commons-io i Googles Guava.

W tym rozwiązaniu metody getParameter* nie rzucają IOException, ale używają super.getInputStream () (aby uzyskać ciała), które mogą powodować IOException. Łapię go i rzucam runtimeException. To nie jest takie miłe.

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
Author: arberg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-14 11:16:50

Samo nadpisanie getInputStream() nie zadziałało w moim przypadku. Moja implementacja serwera zdaje się parsować parametry bez wywoływania tej metody. Nie znalazłem innego sposobu, ale ponownie zaimplementowałem wszystkie cztery metody getParameter*. Oto kod getParameterMap (używany Klient Http Apache i biblioteka Google Guava):

@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
Author: 30thh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-25 08:54:50

Ja też miałem ten sam problem i uważam, że poniższy kod jest prostszy i działa dla mnie,

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

W klasie filter java,

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

Proszę dać mi znać, jeśli masz jakieś pytania

 1
Author: Lathy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-05 11:36:37

Spójrz (lub użyj) Spring AbstractRequestLoggingFilter

 0
Author: GKislin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-11-26 08:25:47

Jeśli masz kontrolę nad żądaniem, możesz ustawić typ zawartości na binary/octet-stream. Pozwala to na wyszukiwanie parametrów bez konsumowania strumienia wejściowego.

Może to być jednak specyficzne dla niektórych serwerów aplikacji. Testowałem tylko Tomcata, wygląda na to, że Jetty zachowuje się tak samo zgodnie z https://stackoverflow.com/a/11434646/957103 .

 0
Author: Olivier.Roger,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:07

Przede wszystkim nie powinniśmy odczytywać parametrów wewnątrz filtra. Zazwyczaj nagłówki są odczytywane w filtrze, aby wykonać kilka zadań uwierzytelniania. Mówiąc, że można odczytać ciało HttpRequest całkowicie w filtrze lub Intercept za pomocą CharStreams:

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

Nie ma to wpływu na kolejne odczyty.

 -1
Author: ashoka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-23 17:00:12

Możesz użyć servlet filter chain, ale zamiast tego użyj oryginalnego, możesz utworzyć własne żądanie yourownrequests rozszerza HttpServletRequestWrapper.

 -1
Author: Zhengwei Zhan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-27 15:18:40