Http सर्वलेट अनुरोध एक बार पढ़ने के बाद POST बॉडी से परम को खो देता है


86

मैं जावा सर्वलेट फ़िल्टर में दो http अनुरोध मापदंडों तक पहुँचने की कोशिश कर रहा हूँ, यहाँ कुछ भी नया नहीं है, लेकिन यह जानकर आश्चर्य हुआ कि पैरामीटर पहले ही भस्म हो चुके हैं! इस वजह से, यह फ़िल्टर श्रृंखला में अब उपलब्ध नहीं है।

ऐसा लगता है कि यह केवल तब होता है जब पैरामीटर POST अनुरोध निकाय में आते हैं (उदाहरण के लिए एक फ़ॉर्म सबमिट करें)।

क्या मापदंडों को पढ़ने और उनका उपभोग नहीं करने का कोई तरीका है?

अब तक मुझे केवल यह संदर्भ मिला है: Request.getParameter का उपयोग करके सर्वलेट फ़िल्टर फॉर्म डेटा खो देता है

धन्यवाद!


2
शायद आप यह कैसे कर रहे हैं का एक कोड टुकड़ा दिखा?
पावेल वेलर

क्या आपको getInputStream () या getReader () मिला है? लगता है कि वे लोग हैं जो getParameter () के निष्पादन में हस्तक्षेप करेंगे
evanwong

जवाबों:


111

एक तरफ के रूप में, इस समस्या को हल करने का एक वैकल्पिक तरीका फ़िल्टर श्रृंखला का उपयोग नहीं करना है और इसके बजाय अपने स्वयं के इंटरसेप्टर घटक का निर्माण करना है, शायद पहलुओं का उपयोग करना, जो पार्स किए गए अनुरोध पर काम कर सकता है। यह भी अधिक कुशल होने की संभावना है क्योंकि आप केवल InputStreamएक बार अपने स्वयं के मॉडल ऑब्जेक्ट में अनुरोध परिवर्तित कर रहे हैं ।

हालाँकि, मुझे अभी भी लगता है कि यह अनुरोध शरीर को एक से अधिक बार पढ़ने के लिए उचित है क्योंकि विशेष रूप से अनुरोध फ़िल्टर श्रृंखला के माध्यम से चलता है। मैं आमतौर पर कुछ निश्चित ऑपरेशनों के लिए फ़िल्टर श्रृंखलाओं का उपयोग करता हूं, जिन्हें मैं एचटीटीपी परत पर रखना चाहता हूं, सेवा घटकों से हटा दिया गया है।

जैसा कि विल हार्टुंग ने सुझाव दिया था कि मैंने इसे प्राप्त करके 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पर कॉल किया जाता है क्योंकि यह वह उदाहरण है जिसे मैं फ़िल्टर श्रृंखला में पास करता हूं। यदि आप अभी भी संदेह में हैं, तो इंटरफ़ेस और उसके लिए स्रोत कोड पढ़ें । ServletRequestServletRequestWrapperServletRequest
प्रेस्टीला

1
अगर मैं यह +100 बना सका, तो मैं करूंगा। मैं 3-4 घंटे के लिए सही काम करने के लिए इसे पाने की कोशिश कर रहा हूं। आपके स्पष्ट उदाहरण और स्पष्टीकरण के लिए धन्यवाद! मुझे खुशी है कि मुझे यह पोस्ट मिली!
डग

20
किसी भी सुझाव कैसे सर्वलेट-एपीआई 3.0+ के साथ यह काम करने के लिए? ServletInputStream में अब सार है isReady()isFinished()और setReadListener()गैर-अवरुद्ध IO से निपटने के लिए जिसे लागू किया जाना चाहिए। मुझे लगता है कि ReadListener को खाली छोड़ दिया जा सकता है, लेकिन निश्चित नहीं है कि क्या करना है isFinished()और / या के बारे में क्या करना है isReady()
एरिक बी।

12
@EricB। फिर भी धन्यवाद। मुझे बाद में नए एपीआई इंटरफ़ेस के लिए समाधान मिला, बस किसी को दिलचस्पी होने की स्थिति में यहां चिपकाया गया। stackoverflow.com/questions/29208456/…
dcc

37

मुझे पता है कि मुझे देर हो गई है, लेकिन यह प्रश्न मेरे लिए अभी भी प्रासंगिक था और यह एसओ पोस्ट Google में शीर्ष हिट में से एक था। मैं आगे जा रहा हूं और इस उम्मीद में अपना समाधान पोस्ट कर रहा हूं कि कोई और कुछ घंटे बचा सकता है।

मेरे मामले में मुझे उनके शरीर के साथ सभी अनुरोधों और प्रतिक्रियाओं को लॉग इन करने की आवश्यकता थी। स्प्रिंग फ्रेमवर्क का उपयोग करना वास्तव में उत्तर काफी सरल है, बस 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लेकिन इस वर्ग इनपुट धारा फिल्टर श्रृंखला में अन्य फिल्टर द्वारा की आवश्यकता हो सकती है, जो (जो अपने उपयोग मामला है) ग्रहण कर रही है। IMHO, यह एक सामग्री कैशिंग वर्ग के एक उम्मीद नहीं व्यवहार है, इसलिए मैं वसंत टीम में इस सुधार उठाया jira.spring.io/browse/SPR-16028
फेडरिको पियाजा

आप AbstractRequestLoggingFilterस्प्रिंग से उपयोग कर सकते हैं , जहां अधिकांश काम पहले से ही स्प्रिंग द्वारा किया जाता है और आपको केवल 1 या 2 सरल तरीकों को ओवरराइड करने की आवश्यकता होती है।
कठोर

1
यह मेरे लिए काम नहीं करता है spring-web-4.3.12.RELEASE। जैसा कि मैंने स्रोत की जाँच की, मैंने पाया कि चर cachedContentका उपयोग विभिन्न सामग्रियों जैसे अनुरोध पैरामीटर और इनपुट इनपुट स्ट्रीम को संग्रहीत करने के लिए किया जाता है। यदि आप getContentAsByteArray()पूरी तरह से कॉल करते हैं तो यह खाली है । अनुरोध निकाय प्राप्त करने के लिए आपको कॉल करना होगा getInputStream()। लेकिन फिर से, यह अन्य फिल्टर और हैंडलर के लिए इनपुटस्ट्रीम अनुपलब्ध करेगा।
इवान हुआंग

7

उपरोक्त उत्तर बहुत मददगार थे, लेकिन फिर भी मेरे अनुभव में कुछ समस्याएं थीं। टॉमकैट 7 सर्वलेट 3.0 पर, गेटपरमटर और गेटपरम्टरवैल्यूज़ को भी अधिलेखित करना पड़ा। यहां समाधान में गेट-क्वेरी पैरामीटर और पोस्ट-बॉडी दोनों शामिल हैं। यह आसानी से कच्चे-स्ट्रिंग प्राप्त करने की अनुमति देता है।

अन्य समाधानों की तरह यह Apache commons-io और Googles Guava का उपयोग करता है।

इस समाधान में getParameter * के तरीके IOException को नहीं फेंकते हैं, लेकिन वे सुपर .getInputStream () (बॉडी को पाने के लिए) का उपयोग करते हैं जो IOException को फेंक सकते हैं। मैं इसे पकड़ता हूं और रनटाइम एक्ससेप्शन फेंकता हूं। यह इतना अच्छा नहीं है।

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()? यह कुंजी = अनुरोध निकाय और मान = अशक्त के साथ एक पैरामीटर बनाता है, जो बहुत अजीब है।
orlade

सभी स्ट्रिंग पार्सिंग के माध्यम से जाने के बजाय, आप super.getParameterMap()अपने फोन में कॉल क्यों नहीं करते हैं getParameterMap? जो आपको किसी <String, String[]>भी रास्ते का नक्शा देगा ।
इयान वी

6

आपके लिए एकमात्र तरीका यह होगा कि आप फ़िल्टर में संपूर्ण इनपुट स्ट्रीम का स्वयं उपभोग करें, जो आप चाहते हैं, उससे लें, और फिर आपके द्वारा पढ़ी गई सामग्री के लिए एक नया InputStream बनाएं, और उस InputStream को ServletRequestWrapper (या HttpServletRequestWrapper) में डालें।

नकारात्मक पक्ष यह है कि आपको खुद को पेलोड को पार करना होगा, मानक उस क्षमता को आपके लिए उपलब्ध नहीं करता है।

एडेंडा -

जैसा कि मैंने कहा, आपको HttpServletRequestWrapper को देखना होगा।

एक फ़िल्टर में, आप FilterChain.doFilter (अनुरोध, प्रतिक्रिया) को कॉल करके जारी रखते हैं।

तुच्छ फिल्टर के लिए, अनुरोध और प्रतिक्रिया वही होती है जो फ़िल्टर में पारित हो जाती है। ऐसा नहीं होना चाहिए। आप उन्हें अपने स्वयं के अनुरोधों और / या प्रतिक्रियाओं से बदल सकते हैं।

HttpServletRequestWrapper को विशेष रूप से इसे सुविधाजनक बनाने के लिए डिज़ाइन किया गया है। आप इसे मूल अनुरोध पास करते हैं, और फिर आप सभी कॉलों को रोक सकते हैं। आप इस का अपना उपवर्ग बनाएँ, और अपने खुद के एक के साथ getInputStream विधि बदलें। आप मूल अनुरोध के इनपुट स्ट्रीम को बदल नहीं सकते हैं, इसलिए इसके बजाय आपके पास यह आवरण है और अपना इनपुट स्ट्रीम वापस करें।

सबसे सरल मामला एक बाइट बफर में मूल अनुरोध इनपुट स्ट्रीम का उपभोग करना है, जो भी जादू आप उस पर चाहते हैं वह करें, फिर उस बफ़र से एक नया बाइटएर्रायइंटरप्रीमस्ट्रीम बनाएं। यह वही है जो आपके रैपर में वापस आ जाता है, जिसे FilterChain.doFilter मेथड में पास किया जाता है।

आपको ServletInputStream को subclass करना होगा और अपने ByteArrayInputStream के लिए एक और आवरण बनाना होगा, लेकिन यह कोई बड़ी बात नहीं है।


मैं InputStream को पढ़ने और इसे पुनर्स्थापित करने का प्रबंधन नहीं कर सकता, स्ट्रीम तक सीधे पहुंच के लिए कोई सेट / सेट विधियाँ नहीं हैं। आपका प्रस्ताव अच्छा लगता है, लेकिन मैं यह नहीं देखता कि इसे कैसे लागू किया जाए।
अमुनिज

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

फ़िल्टर जावा वर्ग में,

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

यदि आपके कोई प्रश्न हैं, तो कृपया मुझे बताएं


3

तो यह मूल रूप से लिट्टी का उत्तर है जो सर्वलेटइन्स्ट्रीम के लिए नई आवश्यकताओं के लिए अद्यतन किया गया है।

अर्थात् (सर्वलेटइन्स्ट्रीम के लिए), एक को लागू करना होगा:

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

कृपया इस कोड को नमक के दाने के साथ लें।

MOST महत्वपूर्ण "परीक्षण" है यदि कोई 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" को सादे ओले "ऑब्जेक्ट" से बदल सकते हैं।

यह उत्तर कई अलग-अलग 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

वसंत ने इसके लिए एक समर्थन के साथ बनाया है 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 body = message.substring(message.indexOf("{"), message.lastIndexOf("]"));


मैं ऑडिट लॉग जनरेट करने के लिए आपके समाधान का उपयोग करने की उम्मीद कर रहा था, लेकिन मुझे यह रिकॉर्ड करने की आवश्यकता है कि क्या अनुरोध सफल था, क्या मैं http प्रतिक्रिया में हुक कर सकता हूं और इस वर्ग के भीतर कोड प्राप्त कर सकता हूं।
14

1

सिर्फ ओवर राइटिंग getInputStream()ने मेरे मामले में काम नहीं किया। मेरा सर्वर कार्यान्वयन इस पद्धति को कॉल किए बिना मापदंडों को पार्स करने के लिए लगता है। मुझे कोई अन्य तरीका नहीं मिला, लेकिन सभी चार getParameter * के तरीकों को भी फिर से लागू करें। यहाँ का कोड getParameterMap(Apache Http Client और 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
जेट्टी के पास दुर्भाग्य से यह मुद्दा है, grepcode.com/file/repo1.maven.org/maven2/org.eclipse.jetty/…
mikeapr4

आप शायद Tomcat 7 या Servlet 3.0 के साथ उपयोग कर रहे हैं? क्या आपके पास अन्य 3 विधियों के लिए भी कोड है?
सिल्वर

अन्य 3 विधियाँ बस getPameterMap () को कॉल करें और आवश्यक मूल्य प्राप्त करें।
30th

0

यदि आपके पास अनुरोध पर नियंत्रण है, तो आप सामग्री प्रकार को बाइनरी / ऑक्टेट-स्ट्रीम पर सेट कर सकते हैं । यह इनपुट स्ट्रीम का उपभोग किए बिना मापदंडों के लिए क्वेरी करने की अनुमति देता है।

हालाँकि, यह कुछ एप्लिकेशन सर्वर के लिए विशिष्ट हो सकता है। मैंने केवल टॉमकैट का परीक्षण किया, जेट्टी https://stackoverflow.com/a/11434646/957103 के अनुसार उसी तरह व्यवहार करता है ।


0

विधि getContentAsByteArray () स्प्रिंग वर्ग की सामग्रीसामान्यविकास कई बार शरीर को पढ़ता है, लेकिन एक ही कक्षा के तरीके getInputStream () और getReader () शरीर को कई बार नहीं पढ़ते हैं:

"यह वर्ग InputStream का उपभोग करके अनुरोध निकाय को कैश करता है। यदि हम किसी एक फ़िल्टर में InputStream पढ़ते हैं, तो फ़िल्टर श्रृंखला में अन्य बाद वाले फ़िल्टर इसे और नहीं पढ़ सकते। इस सीमा के कारण, यह वर्ग सभी में उपयुक्त नहीं है। स्थितियों। "

मेरे मामले में इस समस्या को हल करने वाले अधिक सामान्य समाधान के लिए मेरी स्प्रिंग बूट परियोजना में तीन वर्गों को जोड़ना था (और फाइल करने के लिए आवश्यक निर्भरताएं):

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

मैंने पोम पर निम्न निर्भरताएँ भी जोड़ीं:

<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
ऐसा लगता है कि ट्यूटोरियल के लिंक में अब वायरस है।
व्रत

-2

सबसे पहले हमें फ़िल्टर के भीतर मापदंडों को नहीं पढ़ना चाहिए। आमतौर पर हेडर को कुछ प्रमाणीकरण कार्य करने के लिए फ़िल्टर में पढ़ा जाता है। कहा जा रहा है कि कोई भी चारस्ट्रीम का उपयोग करके HttpRequest बॉडी को फ़िल्टर या इंटरसेप्टर में पूरी तरह से पढ़ सकता है:

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

यह बाद में पढ़ी गई बातों को प्रभावित नहीं करता है।


हाँ यह करता है। यदि आप एक बार ऐसा करते हैं, request.getReader()तो एक पाठक को लौटा देगा जिसमें केवल बाद की रीड्स पर एक खाली स्ट्रिंग है।
क्रिस्टोफेट

1
मैं इस नए शरीर को स्रोत के रूप में उपयोग करने के लिए getReader () और getInputStream () विधियों को अधिलेखित करने के मामले में काम करूंगा।
रॉड्रिगो बोरबा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.