1. 序章

このチュートリアルでは、Springを使用してHttpServletRequestから本文を複数回読み取る方法を学習します。

HttpServletRequest は、 getInputStream()メソッドを公開して本文を読み取るインターフェイスです。 デフォルトでは、このInputStreamからのデータは1回だけ読み取ることができます

2. Mavenの依存関係

最初に必要なのは、適切な spring-webmvcおよびjavax.servletの依存関係です。

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

また、 application / json content-typeを使用しているため、jackson-databindの依存関係が必要です。

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.10.0</version>
</dependency>

Springはこのライブラリを使用してJSONとの間で変換を行います。

3. SpringのContentCachingRequestWrapper

Springは、ContentCachingRequestWrapperクラスを提供します。 このクラスは、本文を複数回読み取るためのメソッド getContentAsByteArray()を提供します

ただし、このクラスには制限があります。 getInputStream()メソッドとgetReader()メソッドを使用して本文を複数回読み取ることはできません。

このクラスは、 InputStream を使用して、リクエストの本文をキャッシュします。 フィルタの1つでInputStreamを読み取ると、フィルタチェーン内の他の後続のフィルタはそれを読み取ることができなくなります。 この制限のため、このクラスはすべての状況に適しているわけではありません。

この制限を克服するために、より汎用的なソリューションを見てみましょう。

4. HttpServletRequestを拡張しています

を作成しましょう新しいクラス– CachedBodyHttpServletRequest –HttpServletRequestWrapperを拡張します 。 このように、のすべての抽象メソッドをオーバーライドする必要はありません。 HttpServletRequest インターフェース

HttpServletRequestWrapper クラスには、2つの抽象メソッド getInputStream() getReader()があります。 これらのメソッドの両方をオーバーライドして、新しいコンストラクターを作成します。

4.1. コンストラクター

まず、コンストラクターを作成しましょう。 その中で、実際の InputStream から本文を読み取り、 byte[]オブジェクトに格納します。

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

その結果、本文を何度も読むことができるようになります。

4.2. getInputStream()

次に、 getInputStream()メソッドをオーバーライドしましょう。 このメソッドを使用して、生の本体を読み取り、それをオブジェクトに変換します。

このメソッドでは、CachedBodyServletInputStreamクラス ServletInputStreamの実装)の新しいオブジェクトを作成して返します。

@Override
public ServletInputStream getInputStream() throws IOException {
    return new CachedBodyServletInputStream(this.cachedBody);
}

4.3. getReader()

次に、 getReader()メソッドをオーバーライドします。 このメソッドは、BufferedReaderオブジェクトを返します。

@Override
public BufferedReader getReader() throws IOException {
    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
    return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}

5. ServletInputStreamの実装

ServletInputStreamを実装するクラス– CachedBodyServletInputStream –を作成しましょう。 このクラスでは、新しいコンストラクターを作成し、 isFinished() isReady()、および read()メソッドをオーバーライドします。

5.1. コンストラクター

まず、バイト配列をとる新しいコンストラクターを作成しましょう。

その中に、そのバイト配列を使用して新しいByteArrayInputStreamインスタンスを作成します。その後、グローバル変数 cachedBodyInputStream:に割り当てます。

public class CachedBodyServletInputStream extends ServletInputStream {

    private InputStream cachedBodyInputStream;

    public CachedBodyServletInputStream(byte[] cachedBody) {
        this.cachedBodyInputStream = new ByteArrayInputStream(cachedBody);
    }
}

5.2. read()

次に、 読んだ() 方法 。 このメソッドでは、 ByteArrayInputStream#read:

@Override
public int read() throws IOException {
    return cachedBodyInputStream.read();
}

5.3. isFinished()

次に、 isFinished()メソッドをオーバーライドします。 このメソッドは、InputStreamに読み取るデータがまだあるかどうかを示します。 読み取り可能なゼロバイトの場合、trueを返します。

@Override
public boolean isFinished() {
    return cachedBody.available() == 0;
}

5.4. isReady()

同様に、 isReady()メソッドをオーバーライドします。 このメソッドは、InputStreamが読み取りの準備ができているかどうかを示します。

InputStream をバイト配列に既にコピーしているので、 true を返し、常に使用可能であることを示します。

@Override
public boolean isReady() {
    return true;
}

6. フィルター

最後に、CachedBodyHttpServletRequestクラスを利用するための新しいフィルターを作成しましょう。 ここでは、SpringのOncePerRequestFilterクラスを拡張します。 このクラスには、抽象メソッド doFilterInternal()があります。

このメソッドでは、実際のリクエストオブジェクトからCachedBodyHttpServletRequestクラスのオブジェクトを作成します。

CachedBodyHttpServletRequest cachedBodyHttpServletRequest =
  new CachedBodyHttpServletRequest(request);

次に、この新しいリクエストラッパーオブジェクトをフィルターチェーンに渡します。 したがって、 getInputStream ()メソッドへの後続のすべての呼び出しは、オーバーライドされたメソッドを呼び出します。

filterChain.doFilter(cachedContentHttpServletRequest, response);

7. 結論

このチュートリアルでは、ContentCachingRequestWrapperクラスについて簡単に説明しました。 また、その制限も確認しました。

次に、HttpServletRequestWrapperクラスの新しい実装を作成しました。 getInputStream()メソッドをオーバーライドして、ServletInputStreamクラスのオブジェクトを返します。

最後に、リクエストラッパーオブジェクトをフィルターチェーンに渡すための新しいフィルターを作成しました。 そのため、リクエストを複数回読み取ることができました。

例の完全なソースコードは、GitHubにあります。