98

Java サーブレット フィルターで 2 つの http 要求パラメーターにアクセスしようとしていますが、ここでは新しいことは何もありませんが、パラメーターが既に使用されていることに驚きました。このため、フィルタ チェーンでは使用できなくなりました。

これは、パラメーターが POST 要求の本文 (フォームの送信など) に含まれている場合にのみ発生するようです。

パラメータを読み取って消費しない方法はありますか?

これまでのところ、この参照のみを見つけました: Servlet Filter using request.getParameter loses Form data

ありがとう!

4

13 に答える 13

42

遅れたことは承知していますが、この質問は私にとってまだ関連性があり、この SO 投稿は Google で最もヒットしたものの 1 つです。他の誰かが数時間を節約できることを期待して、先に進んでソリューションを投稿します。

私の場合、すべてのリクエストとレスポンスを本文とともにログに記録する必要がありました。Spring Framework を使用すると、答えは実際には非常に簡単です。 ContentCachingRequestWrapperContentCachingResponseWrapperを使用するだけです。

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

        }

    }

}
于 2016-08-25T20:33:47.513 に答える
7

上記の回答は非常に役に立ちましたが、私の経験ではまだいくつかの問題がありました。tomcat 7 サーブレット 3.0 では、getParamter と getParamterValues も上書きする必要がありました。ここでのソリューションには、get-query パラメーターと post-body の両方が含まれます。生の文字列を簡単に取得できます。

他のソリューションと同様に、Apache commons-io と Google の 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();
    }
}
于 2016-04-14T10:09:38.343 に答える
6

唯一の方法は、フィルターで入力ストリーム全体を自分で消費し、そこから必要なものを取得してから、読み取ったコンテンツの新しい InputStream を作成し、その InputStream を ServletRequestWrapper (または HttpServletRequestWrapper) に入れることです。

欠点は、ペイロードを自分で解析する必要があることです。標準では、その機能を利用できません。

補遺 --

私が言ったように、HttpServletRequestWrapper を見る必要があります。

フィルターでは、引き続き FilterChain.doFilter(request, response) を呼び出します。

単純なフィルターの場合、要求と応答はフィルターに渡されたものと同じです。そうである必要はありません。それらを独自のリクエストやレスポンスに置き換えることができます。

HttpServletRequestWrapper は、これを容易にするために特別に設計されています。元のリクエストを渡すと、すべての呼び出しをインターセプトできます。これの独自のサブクラスを作成し、getInputStream メソッドを独自のものに置き換えます。元のリクエストの入力ストリームを変更することはできないため、代わりにこのラッパーを使用して独自の入力ストリームを返します。

最も単純なケースは、元の要求入力ストリームをバイト バッファーに取り込み、必要な魔法を実行してから、そのバッファーから新しい ByteArrayInputStream を作成することです。これは、FilterChain.doFilter メソッドに渡されるラッパーで返されるものです。

ServletInputStream をサブクラス化し、ByteArrayInputStream 用に別のラッパーを作成する必要がありますが、それも大したことではありません。

于 2012-04-18T14:16:07.947 に答える
6

私も同じ問題を抱えていましたが、以下のコードはよりシンプルで、うまく機能していると思います。

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

ご不明な点がございましたらお知らせください

于 2015-08-05T11:36:37.743 に答える
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("]"));

于 2019-02-26T12:33:46.233 に答える
1

私の場合、上書きするだけでgetInputStream()はうまくいきませんでした。私のサーバー実装は、このメソッドを呼び出さずにパラメーターを解析しているようです。他の方法は見つかりませんでしたが、4 つの 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;
}
于 2014-09-25T08:54:50.783 に答える
0

リクエストを制御できる場合は、コンテンツ タイプをbinary/octet-streamに設定できます。これにより、入力ストリームを消費せずにパラメーターを照会できます。

ただし、これは一部のアプリケーション サーバーに固有の場合があります。私は tomcat のみをテストしました。https://stackoverflow.com/a/11434646/957103によると、jetty は同じように動作するようです。

于 2014-08-27T14:03:46.603 に答える
-1

サーブレット フィルター チェーンを使用できますが、代わりに元のものを使用して、独自のリクエストを作成できます。 yourownrequests extends HttpServletRequestWrapper.

于 2013-06-30T01:48:14.223 に答える
-2

まず第一に、フィルター内のパラメーターを読み取るべきではありません。通常、ヘッダーはフィルターで読み取られ、いくつかの認証タスクを実行します。CharStreams を使用して、Filter または Interceptor で HttpRequest 本体を完全に読み取ることができると述べました。

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

これは、後続の読み取りにはまったく影響しません。

于 2016-12-23T16:46:41.647 に答える