Http Servlet isteği, bir kez okuduktan sonra POST gövdesindeki parametreleri kaybediyor


88

Bir Java Servlet filtresinde iki http istek parametresine erişmeye çalışıyorum, burada yeni bir şey yok, ancak parametrelerin zaten tüketilmiş olduğunu görünce şaşırdım! Bu nedenle artık filtre zincirinde bulunmamaktadır.

Görünüşe göre bu sadece parametreler bir POST istek gövdesinde (örneğin bir form gönderme) geldiğinde meydana gelir.

Parametreleri okumanın ve tüketmemenin bir yolu var mı?

Şimdiye kadar sadece bu referansı buldum: request.getParameter kullanan Servlet Filter Form verilerini kaybediyor .

Teşekkürler!


2
belki bunu nasıl yaptığınıza dair bir kod parçası gösterebilir misiniz?
Pavel Veller

GetInputStream () veya getReader () aldınız mı?
GetParameter

Yanıtlar:


115

Bir kenara, bu sorunu çözmenin alternatif bir yolu, filtre zincirini kullanmamak ve bunun yerine, belki de ayrıştırılmış istek gövdesinde çalışabilen yönleri kullanarak kendi durdurucu bileşeninizi oluşturmaktır. İsteği yalnızca bir InputStreamkez kendi model nesnenize dönüştürdüğünüz için muhtemelen daha verimli olacaktır .

Bununla birlikte, özellikle istek filtre zinciri boyunca ilerlerken, istek gövdesini birden çok kez okumak istemenin makul olduğunu düşünüyorum. HTTP katmanında tutmak istediğim, hizmet bileşenlerinden ayrılmış olarak belirli işlemler için genellikle filtre zincirleri kullanırdım.

Will Hartung tarafından önerildiği gibi, bunu genişleterek HttpServletRequestWrapper, isteği tüketerek InputStreamve esas olarak baytları önbelleğe alarak başardım .

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

Artık istek gövdesi, orijinal isteği filtre zincirinden geçirmeden önce sararak birden çok kez okunabilir:

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

Bu çözüm aynı zamanda getParameterXXXyöntemlerle istek gövdesini birden çok kez okumanıza da olanak tanır, çünkü temeldeki çağrıdır getInputStream()ve tabii ki önbelleğe alınmış isteği okuyacaktır InputStream.

Düzenle

ServletInputStreamArayüzün daha yeni sürümü için . Sen gibi birkaç yöntemlerin uygulanmasını sağlamak için gereken isReady, setReadListenervb bu başvurun soruyu aşağıda yorum belirtildiği şekilde.


5
Bu doğru mu? Temel çağrı, baytlarını zaten okumuş olacağınız orijinal istek üzerine getInputStream () 'dir . Temel istek, sarmalayıcınız hakkında bilgi sahibi değildir, bu nedenle sarmalayıcının getInputStream () işlevini çağıracağını nasıl bilebilir?
Frans

1
Daha net olmak gerekirse getInputStreamçağrılan zaman bu yana sargı ServletRequestI filtre zincirine geçmek örneği. Hala şüpheniz varsa, kaynak kodunu ServletRequestWrapperve ServletRequestarayüzü okuyun.
pestrella

1
Bunu +100 yapabilseydim, yapardım. 3-4 saattir bunu doğru şekilde çalıştırmaya çalışıyorum. Net örnek ve açıklamanız için teşekkür ederiz! Bu gönderiyi bulduğuma sevindim!
Doug

21
Bunun Servlet-api 3.0+ ile nasıl çalışacağına dair herhangi bir öneriniz var mı? ServletInputStream artık soyuta sahip isReady(). isFinished()ve setReadListener()uygulanması gereken bloke edici olmayan IO ile başa çıkmak için. ReadListener'ın boş bırakılabileceğini düşünüyorum, ancak isFinished()ve / veya hakkında ne yapacağımı bilmiyorum isReady().
Eric B.

12
@EricB. yine de teşekkürler. Daha sonra yeni api arayüzünün çözümünü buldum, birinin ilgilenmesi durumunda buraya yapıştırdım. stackoverflow.com/questions/29208456/…
dcc

37

Geç kaldığımı biliyorum, ancak bu soru hala benim için geçerliydi ve bu SO gönderisi Google'daki en çok beğenilenlerden biriydi. Devam ediyorum ve başka birinin birkaç saat kazanması umuduyla çözümümü yayınlıyorum.

Benim durumumda, tüm istekleri ve yanıtları bedenleriyle birlikte kaydetmem gerekiyordu. Spring Framework kullanarak cevap aslında oldukça basittir, sadece ContentCachingRequestWrapper ve ContentCachingResponseWrapper'ı kullanın .

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
Bu benim için işe yaramadı. Hem requestBodyve responseBodywere boş dizeler
abhijith Madhav

5
Benim hatam. chain.doFilter(request, response);chain.doFilter(requestWrapper, responseWrapper);
Yerine

5
ContentCaching*WrapperSınıflar "önbelleğe alma" yöntemiyle yapılır böylece giriş akışı tüketen pahalı fiyat var getContentAsByteArrayama bu sınıf (benim kullanım durumu olan) filtre zincirindeki diğer filtreler tarafından ihtiyaç duyulabilecek giriş akışı önbelleğe değildir. Imho, bu içerik önbelleğe alma sınıfının beklenmeyen bir davranışıdır, bu nedenle bu gelişmeyi bahar ekibi jira.spring.io/browse/SPR-16028
Federico Piazza

Sen kullanabilirsiniz AbstractRequestLoggingFilterişin çoğunu zaten Bahar tarafından yapılan ve sadece 1 veya 2 basit yöntemleri geçersiz kılmak gerekir edilir Bahar, gelen.
sert

1
Bu benim için geçerli değil spring-web-4.3.12.RELEASE. Kaynağı kontrol ederken, değişkenin cachedContentistek parametreleri ve request inputStream gibi çeşitli içerikleri saklamak için kullanıldığını gördüm. Yalnızca ararsanız boş getContentAsByteArray(). Talep gövdesini almak için aramanız gerekir getInputStream(). Ancak yine, bu, inputStream'i diğer filtreler ve işleyici için kullanılamaz hale getirecektir.
Ivan Huang

7

Yukarıdaki cevaplar çok yardımcı oldu, ancak yine de deneyimlerimde bazı sorunlar yaşadım. Tomcat 7 sunucu uygulaması 3.0'da getParamter ve getParamterValues ​​öğelerinin de üzerine yazılması gerekiyordu. Buradaki çözüm hem get-query parametrelerini hem de post-body'i içerir. Ham ipi kolayca elde etmeyi sağlar.

Diğer çözümler gibi Apache commons-io ve Googles Guava'yı kullanır.

Bu çözümde getParameter * yöntemleri IOException oluşturmaz, ancak IOException oluşturabilecek super.getInputStream () (gövdeyi almak için) kullanırlar. Yakaladım ve runtimeException atıyorum. O kadar hoş değil.

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

Bu harika! Bunu günlerdir anlamaya çalışıyorum ve bu servlet 3.1 ile çalışıyor. Bir soru: Neden yapıyorsun decode(getPostBodyAsString(), result);içinde getParameterMap()? Bu, oldukça garip olan key = istek gövdesi ve değer = null ile bir parametre oluşturur.
orlade

Aksine dize ayrıştırma tüm geçmekte daha, neden aramıyorsun super.getParameterMap()Gözlerinde farklı getParameterMap? Size yine de bir harita verecek <String, String[]>.
Ean V

6

Tek yol, tüm giriş akışını filtrede kendiniz tüketmeniz, ondan istediğinizi almanız ve ardından okuduğunuz içerik için yeni bir InputStream oluşturmanız ve bu InputStream'i bir ServletRequestWrapper'a (veya HttpServletRequestWrapper) yerleştirmenizdir.

Olumsuz tarafı, yükü kendiniz ayrıştırmanız gerekmesidir, standart bu özelliği sizin için kullanılabilir kılmaz.

Addenda -

Dediğim gibi, HttpServletRequestWrapper'a bakmanız gerekiyor.

Bir filtrede, FilterChain.doFilter'ı (istek, yanıt) çağırarak devam edersiniz.

Önemsiz filtreler için istek ve yanıt, filtreye iletilenlerle aynıdır. Durum böyle olmak zorunda değil. Bunları kendi istekleriniz ve / veya yanıtlarınızla değiştirebilirsiniz.

HttpServletRequestWrapper bunu kolaylaştırmak için özel olarak tasarlanmıştır. Orijinal isteği iletirsiniz ve ardından tüm aramaları kesebilirsiniz. Bunun kendi alt sınıfını oluşturursunuz ve getInputStream yöntemini kendi alt sınıfınızdan biriyle değiştirirsiniz. Orijinal isteğin girdi akışını değiştiremezsiniz, bunun yerine bu sarmalayıcıya sahip olursunuz ve kendi girdi akışınızı döndürürsünüz.

En basit durum, orijinal istek giriş akışını bir bayt arabelleğinde kullanmak, üzerinde istediğiniz sihri yapmak ve ardından bu arabellekten yeni bir ByteArrayInputStream oluşturmaktır. Bu, sarmalayıcınızda döndürülen ve FilterChain.doFilter yöntemine iletilen şeydir.

ServletInputStream'i alt sınıflara ayırmanız ve ByteArrayInputStream'iniz için başka bir sarmalayıcı yapmanız gerekir, ancak bu da önemli bir şey değil.


InputStream'i okuyup sonra geri yükleyemiyorum, akışa doğrudan erişim için get / set yöntemi yok. Öneriniz iyi görünüyor, ancak nasıl uygulanacağını bilmiyorum.
amuniz

5

Ben de aynı sorunu yaşadım ve aşağıdaki kodun daha basit olduğuna ve benim için işe yaradığına inanıyorum.

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

filtre java sınıfında,

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

Herhangi bir sorunuz varsa lütfen bana bildirin


3

Bu temelde Lathy'nin cevabıdır ANCAK ServletInputStream için daha yeni gereksinimler için güncellendi.

Yani (ServletInputStream için), aşağıdakileri uygulamak gerekir:

public abstract boolean isFinished();

public abstract boolean isReady();

public abstract void setReadListener(ReadListener var1);

Bu, Lathy'nin düzenlenmiş nesnesi

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

ve bir yerde (??) Bunu buldum ("ekstra" yöntemlerle ilgilenen birinci sınıf bir sınıftır.

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

Sonunda, istekleri kaydetmeye çalışıyordum. Ve yukarıdaki frankensteined birlikte parçalar, aşağıdakileri oluşturmama yardımcı oldu.

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

Lütfen bu kodu bir miktar tuzla alın.

EN önemli "test", bir POST'un bir yük ile çalışıp çalışmadığıdır. "Çift okuma" sorunlarını ortaya çıkaracak şey budur.

sözde örnek kod

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

Yalnızca test etmek istiyorsanız, "MyCustomObject" öğesini düz "Nesne" ile değiştirebilirsiniz.

Bu cevap, birkaç farklı SOF gönderisinden ve örneğinden türetilmiştir .. ama hepsini bir araya getirmek biraz zaman aldı, bu yüzden umarım gelecekteki bir okuyucuya yardımcı olur.

Lütfen benimkinden önce Lathy'nin cevabını destekleyin. Onsuz bu kadar ileri gidemezdim.

Aşağıda, bunu çalışırken aldığım istisnalardan biri / birkaçı var.

getReader () bu istek için zaten çağrıldı

Görünüşe göre "ödünç aldığım" yerlerden bazıları burada:

http://slackspace.de/articles/log-request-body-with-spring-boot/

https://github.com/c0nscience/spring-web-logging/blob/master/src/main/java/org/zalando/springframework/web/logging/LoggingFilter.java

https://howtodoinjava.com/servlets/httpservletrequestwrapper-example-read-request-body/

https://www.oodlestechnologies.com/blogs/How-to-create-duplicate-object-of-httpServletRequest-object

https://github.com/c0nscience/spring-web-logging/blob/master/src/main/java/org/zalando/springframework/web/logging/LoggingFilter.java


3

Spring, aşağıdakilerle bunun için yerleşik desteğe sahiptir 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;
}

Maalesef, yükü doğrudan istek dışında okuyamazsınız, ancak Dize mesaj parametresi, yükü içerecek ve böylece buradan aşağıdaki şekilde yakalayabilirsiniz:

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


Çözümünüzü bir denetim günlüğü oluşturmak için kullanmayı umuyordum, ancak isteğin başarılı olup olmadığını kaydetmem gerekiyor, http yanıtına bağlanabilir ve bu sınıf içindeki kodu alabilir miyim?
jonesy

1

Sadece üzerine yazmak getInputStream()benim durumumda işe yaramadı. Sunucu uygulamam bu yöntemi çağırmadan parametreleri ayrıştırıyor gibi görünüyor. Başka bir yol bulamadım, ancak dört getParameter * yöntemini de yeniden uyguladım. İşte getParameterMap(kullanılan Apache Http İstemcisi ve Google Guava kitaplığı) kodu :

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


Muhtemelen Tomcat 7 veya Servlet 3.0 ile üstünü kullanıyorsunuz? Diğer 3 yöntem için de kodunuz var mı?
Silver

Diğer 3 yöntem sadece getParameterMap () öğesini çağırır ve gerekli değeri getirir.
30

0

İstek üzerinde kontrole sahipseniz, içerik türünü ikili / sekizli akış olarak ayarlayabilirsiniz. . Bu, girdi akışını tüketmeden parametreleri sorgulamaya izin verir.

Ancak bu, bazı uygulama sunucularına özgü olabilir. Sadece tomcat'i test ettim, jetty https://stackoverflow.com/a/11434646/957103'e göre aynı şekilde davranıyor gibi görünüyor .


0

Spring sınıfının ContentCachingRequestWrapper'ın getContentAsByteArray () yöntemi gövdeyi birden çok kez okur, ancak aynı sınıfın getInputStream () ve getReader () yöntemleri gövdeyi birden çok kez okumaz:

"Bu sınıf, InputStream'i tüketerek istek gövdesini önbelleğe alır. InputStream'i filtrelerden birinde okursak, filtre zincirindeki diğer sonraki filtreler artık onu okuyamaz. Bu sınırlamadan dolayı, bu sınıf hiçbir şekilde uygun değildir. durumlar. "

Benim durumumda, bu sorunu çözen daha genel bir çözüm, Spring boot projeme aşağıdaki üç sınıfı eklemekti (ve pom dosyasına gerekli bağımlılıklar):

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

Ayrıca pom'a aşağıdaki bağımlılıkları ekledim:

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

Tuturi ve tam kaynak kodu burada bulunur: https://www.baeldung.com/spring-reading-httpservletrequest-multiple-times


0

Herhangi bir istek gövdesi biçimi için iyi bir çözüm buldum. Test ettim application/x-www-form-urlencodedve application/jsonikisi de çok iyi çalıştı. Bunun problemi ContentCachingRequestWrappersadece x-www-form-urlencodedistek gövdesi için tasarlanmıştır , ancak örneğin json ile çalışmaz. JSON bağlantısı için çözüm buldum . Desteklemediği sorunları vardı x-www-form-urlencoded. Her ikisine de kodumda katıldım:

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

servlet filtre zincirini kullanabilirsiniz, ancak bunun yerine orijinali kullanabilirsiniz, kendi isteğinizi oluşturabilirsiniz, kendi istekleriniz HttpServletRequestWrapper'ı genişletir.


1
Öğreticinin bağlantısı şimdi bir virüs içeriyor gibi görünüyor.
fasth

-2

Öncelikle filtre içindeki parametreleri okumamalıyız. Birkaç kimlik doğrulama görevi gerçekleştirmek için genellikle başlıklar filtrede okunur. CharStreams kullanılarak HttpRequest gövdesinin tamamen Filter veya Interceptor'da okunabileceğini söyleyerek:

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

Bu, sonraki okumaları hiç etkilemez.


Evet öyle. Bunu bir kez yaparsanız request.getReader(), sonraki okumalarda yalnızca boş bir dize içeren bir okuyucu döndürür.
christophetd

1
Bu yeni gövdeyi kaynak olarak kullanmak için getReader () ve getInputStream () yöntemlerinin üzerine yazılması durumunda çalışırdım.
Rodrigo Borba
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.