Запит Http Servlet втрачає параметри з тіла POST після його прочитання один раз


86

Я намагаюся отримати доступ до двох параметрів запиту http у фільтрі Java Servlet, тут нічого нового, але з подивом виявив, що параметри вже використані! Через це він більше не доступний у ланцюзі фільтрів.

Здається, це відбувається лише тоді, коли параметри надходять у тіло запиту POST (наприклад, подання форми).

Чи є спосіб прочитати параметри і НЕ споживати їх?

Поки що я знайшов лише це посилання: фільтр сервлетів за допомогою request.getParameter втрачає дані форми .

Дякую!


2
може показати фрагмент коду, як ви це робите?
Павел Веллер,

Ви отримали getInputStream () або getReader ()? Схоже, саме вони заважають виконувати getParameter ()
evanwong

Відповіді:


111

Окрім того, альтернативний спосіб вирішення цієї проблеми - не використовувати ланцюжок фільтрів, а замість цього створити власний компонент-перехоплювач, можливо, використовуючи аспекти, які можуть працювати на аналізованому тілі запиту. Це, швидше за все, буде більш ефективним, оскільки ви лише один раз перетворюєте запит InputStreamу власний об’єкт моделі.

Однак я все ще вважаю, що доцільно хотіти прочитати тіло запиту більше одного разу, особливо коли запит рухається через ланцюжок фільтрів. Я зазвичай використовую ланцюжки фільтрів для певних операцій, які я хочу зберегти на рівні HTTP, відокремленому від службових компонентів.

Як запропонував Уілл Хартунг, я досяг цього, розширивши HttpServletRequestWrapper, споживаючи запит InputStreamі по суті кешуючи байти.

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

Тепер тіло запиту можна прочитати більше одного разу, обернувши вихідний запит перед передачею через ланцюжок фільтру:

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

Це рішення також дозволить вам прочитати тіло запиту кілька разів за допомогою getParameterXXXметодів, оскільки базовий виклик є getInputStream(), що, звичайно, прочитає кешований запит InputStream.

Редагувати

Для нової версії ServletInputStreamінтерфейсу. Ви повинні забезпечити виконання ще кілька методів , як isReady, і setReadListenerт.д. Див цей питання , як це передбачено в коментарі нижче.


5
Це правда? Основний виклик - getInputStream () за вихідним запитом , з якого ви вже прочитали байти. Базовий запит не знає вашої обгортки, то як би він знав, як викликати обгортку getInputStream ()?
Франс

1
Якщо бути точним, getInputStreamце викликається на моїй обгортці, оскільки це ServletRequestекземпляр, який я передаю в ланцюжок фільтрів. Якщо ви все ще сумніваєтесь, прочитайте вихідний код ServletRequestWrapperта ServletRequestінтерфейс.
pestrella

1
Якби я міг зробити це +100, я б. Я намагаюся, щоб це працювало правильно протягом 3-4 годин. Дякую за ваш наочний приклад та пояснення! Я радий, що знайшов цю публікацію!
Даг

20
Будь-які пропозиції, як змусити цю роботу працювати з Servlet-api 3.0+? Тепер ServletInputStream має абстракцію isReady(). isFinished()і setReadListener()мати справу з неблокуючим ВВ, який необхідно впровадити. Я думаю, що ReadListener можна залишити порожнім, але я не знаю, що робити з цим isFinished()та / або isReady().
Ерік Б.

12
@EricB. все одно, дякую. Пізніше я знайшов рішення для нового інтерфейсу api, просто вставив його сюди на випадок, якщо комусь буде цікаво. stackoverflow.com/questions/29208456/…
dcc

37

Я знаю, що запізнився, але це питання все ще було для мене актуальним, і ця публікація SO була одним із найпопулярніших хітів у Google. Я продовжую і публікую своє рішення в надії, що хтось інший може заощадити пару годин.

У моєму випадку мені потрібно було реєструвати всі запити та відповіді з їхніми органами. Використовуючи Spring Framework, відповідь насправді досить проста, просто використовуйте ContentCachingRequestWrapper та 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
Це не спрацювало для мене. Обидва requestBodyі responseBodyбули порожніми струнами
Абхіджіт Мадхав

5
Моя помилка. Я робив chain.doFilter(request, response);замість цьогоchain.doFilter(requestWrapper, responseWrapper);
Абхіджіт Мадхав

5
У ContentCaching*Wrapperкласах мають дорогу ціну споживаючи вхідний потік , так що «кешування» здійснюються з допомогою методу , getContentAsByteArrayале цей клас кешувати вхідний потік , який може бути необхідний іншими фільтрами в ланцюжку фільтрів (це мій випадок використання). Імхо, це не очікувана поведінка класу кешування вмісту, тому я підняв це вдосконалення у весняній команді jira.spring.io/browse/SPR-16028
Federico Piazza

Ви можете використовувати AbstractRequestLoggingFilterз Spring, де більша частина роботи вже виконана Spring, і вам потрібно лише замінити 1 або 2 простих методів.
суворий

1
На сьогодні це у мене не працює spring-web-4.3.12.RELEASE. Перевіривши джерело, я виявив, що змінна cachedContentвикористовується для зберігання різного вмісту, такого як параметри запиту та запит inputStream. Якщо ви телефонуєте getContentAsByteArray()виключно , порожньо . Для отримання органу запиту вам потрібно зателефонувати getInputStream(). Але знову ж таки, це зробить inputStream недоступним для інших фільтрів та обробника.
Іван Хуанг

7

Наведені вище відповіді були дуже корисними, але, як я відчував, все-таки мали проблеми. На сервлеті tomcat 7 3.0 3.0 getParamter та getParamterValues ​​також повинні були бути перезаписані. Рішення тут включає як параметри get-query, так і post-body. Це дозволяє легко отримувати сирі струни.

Як і інші рішення, він використовує Apache commons-io та Googles Guava.

У цьому рішенні методи getParameter * не кидають IOException, але вони використовують super.getInputStream () (для отримання тіла), який може кинути IOException. Я ловлю це і кидаю runtimeException. Це не так приємно.

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.1. Одне питання: чому ви робите decode(getPostBodyAsString(), result);в getParameterMap()? Це створює параметр з ключем = тіло запиту та значенням = нуль, що досить дивно.
орладе

Замість того, щоб пройти весь синтаксичний аналіз рядків, чому ви не закликаєте super.getParameterMap()свій getParameterMap? Що дасть вам карту в <String, String[]>будь-якому випадку.
Ean V

6

Єдиним способом було б, щоб ви спожили весь вхідний потік самостійно у фільтрі, взяли те, що хочете від нього, а потім створили новий InputStream для прочитаного вмісту і помістили цей InputStream у ServletRequestWrapper (або HttpServletRequestWrapper).

Недоліком є ​​те, що вам доведеться самостійно розбирати корисне навантаження, стандарт не робить цю можливість доступною для вас.

Додатки -

Як я вже говорив, вам потрібно переглянути HttpServletRequestWrapper.

У фільтрі ви продовжуєте, викликаючи FilterChain.doFilter (запит, відповідь).

Для тривіальних фільтрів запит і відповідь такі ж, як і ті, що передаються у фільтр. Це не повинно бути так. Ви можете замінити їх власними запитами та / або відповідями.

HttpServletRequestWrapper спеціально розроблений для полегшення цього. Ви передаєте йому оригінальний запит, а потім можете перехопити всі дзвінки. Ви створюєте власний підклас цього і замінюєте метод getInputStream на власний. Ви не можете змінити вхідний потік вихідного запиту, тому замість цього ви маєте цю обгортку і повертаєте власний вхідний потік.

Найпростіший випадок - споживати вхідний потік вихідних запитів до байтового буфера, робити все, що завгодно, на ньому, а потім створити новий ByteArrayInputStream з цього буфера. Це те, що повертається у вашу обгортку, яка передається методу FilterChain.doFilter.

Вам потрібно буде підклас ServletInputStream і зробити ще одну обгортку для вашого ByteArrayInputStream, але це теж не велика проблема.


Мені не вдається прочитати InputStream і відновити його після того, як немає методів get / set для прямого доступу до потоку. Ваша пропозиція здається хорошою, але я не бачу, як її реалізувати.
amuniz

4

У мене теж була та сама проблема, і я вважаю, що наведений нижче код простіший і працює для мене,

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

у класі Java фільтра,

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

Будь ласка, повідомте мене, якщо у вас є запитання


3

Отже, це в основному відповідь Леті, АЛЕ оновлена ​​для нових вимог до ServletInputStream.

А саме (для ServletInputStream) потрібно реалізувати:

public abstract boolean isFinished();

public abstract boolean isReady();

public abstract void setReadListener(ReadListener var1);

Це відредагований об’єкт Леті

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

і десь (??) я знайшов це (це першокласний клас, який має справу з "зайвими" методами.

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

Зрештою, я просто намагався реєструвати запити. А вищезазначені фрагменти франкенштейну допомогли мені створити нижченаведені.

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

Будь ласка, візьміть цей код з достатньою кількістю солі.

НАЙГОЛОВНІШИЙ "тест" - це якщо POST працює з корисним навантаженням. Саме це розкриє проблеми "подвійного читання".

псевдо приклад коду

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

Ви можете замінити "MyCustomObject" на звичайний ole "Object", якщо ви просто хочете перевірити.

Ця відповідь відверта від кількох різних публікацій SOF та прикладів .. але це зайняло деякий час, щоб зібрати це все разом, тому я сподіваюся, що це допоможе майбутньому читачеві.

Будь ласка, проголосуйте відповідь Леті перед моєю. Я не міг би зайти так далеко без нього.

Нижче наведено один / деякі винятки, які я отримав під час розробки цього.

getReader () вже було викликано для цього запиту

Схоже, деякі місця, у яких я "позичав", є тут:

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 має вбудовану підтримку для цього за допомогою 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;
}

На жаль, ви все одно не зможете прочитати корисне навантаження безпосередньо із запиту, але параметр повідомлення String включатиме корисне навантаження, тому ви зможете взяти його звідти наступним чином:

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


Я сподівався використовувати ваше рішення для створення журналу аудиту, але мені потрібно записати, чи був запит успішним, чи можу я підключитись до відповіді http і отримати код у цьому класі.
jonesy

1

Просто перезапис getInputStream()в моєму випадку не спрацював. Моя реалізація сервера, здається, аналізує параметри, не викликаючи цей метод. Я не знайшов іншого шляху, але також повторно впровадив усі чотири методи getParameter *. Ось код getParameterMap(використовується клієнт Apache Http та бібліотека 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
На жаль, у Jetty є ця проблема, grepcode.com/file/repo1.maven.org/maven2/org.eclipse.jetty/…
mikeapr4,

Напевно, ви використовуєте Tomcat 7 або новішу версію з Servlet 3.0? Чи є у вас код для інших 3 методів?
Silver

Інші 3 методи просто викликають getParameterMap () і отримують необхідне значення.
год.

0

Якщо ви маєте контроль над запитом, ви можете встановити тип вмісту в бінарний / октет-потік . Це дозволяє запитувати параметри, не витрачаючи вхідний потік.

Однак це може бути специфічним для деяких серверів додатків. Я протестував лише tomcat, пристань, схоже, поводиться однаково згідно з https://stackoverflow.com/a/11434646/957103 .


0

Метод getContentAsByteArray () класу Spring ContentCachingRequestWrapper читає тіло кілька разів, але методи getInputStream () та getReader () того самого класу не читають тіло кілька разів:

"Цей клас кешує тіло запиту, використовуючи InputStream. Якщо ми читаємо InputStream в одному з фільтрів, то інші наступні фільтри в ланцюжку фільтрів більше не можуть його прочитати. Через це обмеження цей клас не підходить для всіх ситуацій ".

У моєму випадку більш загальним рішенням, яке вирішило цю проблему, було додавання наступних трьох класів до мого проекту завантаження Spring (і необхідних залежностей до файлу 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);
    }
}

Я також додав наступні залежності до 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>

Навчальний і повний вихідний код знаходиться тут: https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times


-1

Ви можете використовувати ланцюжок фільтрів сервлетів, але замість цього використовувати оригінальний, ви можете створити власний запит yourownrequests розширює HttpServletRequestWrapper.


1
Здається, посилання на підручник зараз містить вірус.
fasth

-2

Перш за все, ми не повинні читати параметри у фільтрі. Зазвичай заголовки читаються у фільтрі, щоб виконати кілька завдань автентифікації. Сказавши, що можна повністю прочитати тіло HttpRequest у фільтрі або перехоплювачі за допомогою CharStreams:

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

Це взагалі не впливає на подальші читання.


так. Якщо ви зробите це один раз, request.getReader()поверне читач, який містить лише порожній рядок для наступних читань.
christophetd

1
Я б працював у разі перезапису методів getReader () та getInputStream (), щоб використовувати це нове тіло як джерело.
Родріго Борба
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.