1. ホーム
  2. http

[解決済み] Golang がリクエストボディを複数回読み込む [duplicate].

2023-06-03 01:06:15

質問

logginMiddlewareを自作しています。基本的には、リクエストとレスポンスのbodyを記録する必要があります。問題は、bodyを読み込む際に、bodyが空になってしまい、2回読み込むことができないことです。 これは、ReadCloserタイプであるために起こることだと理解しています。ボディを最初に巻き戻す方法はありますか?

解決方法を教えてください。

リクエストボディの検査とモッキング

最初にボディを読んだら、それを保存しておく必要があります。そうすれば、一度読み終えたら、新しい io.ReadCloser を元のデータから構築されたリクエストボディとして設定します。そのため、チェーンを進めると、次のハンドラは同じボディを読み取ることができます。

一つの選択肢は、ボディ全体を ioutil.ReadAll() を使って、本文をバイト単位で読むことです。

を使うことができます。 bytes.NewBuffer() を取得するために io.Reader をバイトスライスから取得します。

最後に足りない部分は io.Reader であり io.ReadCloser というのも bytes.Buffer には Close() メソッドがありません。このため ioutil.NopCloser() で囲みます。 io.Reader をラップし io.ReadCloser で、その追加された Close() メソッドは no-op (何もしない) になります。

new"ボディを作成するために使用するバイトスライスの内容を変更することもできることに注意してください。あなたはそれを完全に制御することができます。

しかし、データのみを変更した場合、content-length や checksums のような他の HTTP フィールドが無効になる可能性があるため、注意が必要です。後続のハンドラーがそれらをチェックする場合、それらも修正する必要があります。

レスポンスボディの検査/修正

レスポンスボディも読みたい場合は、レスポンスボディにある http.ResponseWriter をラップし、ラッパーをチェーンで渡す必要があります。このラッパーは、送信されたデータをキャッシュすることができ、 後から、あるいはその場で(後続のハンドラが書き込む際に)検査することができます。

ここでは、単純な ResponseWriter ラッパーです。これはデータをキャッシュするだけなので、後続のハンドラが戻った後でも利用可能です。

type MyResponseWriter struct {
    http.ResponseWriter
    buf *bytes.Buffer
}

func (mrw *MyResponseWriter) Write(p []byte) (int, error) {
    return mrw.buf.Write(p)
}

なお MyResponseWriter.Write() はデータをバッファに書き込むだけです。また、オンザフライで検査することもできます ( Write() メソッドで)その場で検査し、データをすぐにラップされた/埋め込まれた ResponseWriter . データを修正することもできます。あなたは完全にコントロールすることができます。

しかし、後続のハンドラはレスポンスデータに関連する HTTP レスポンスヘッダ (長さやチェックサムなど) を送信することがあるので、これにも注意が必要です。

完全な例

これらの部品を組み合わせて、完全な動作例を示します。

func loginmw(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        body, err := ioutil.ReadAll(r.Body)
        if err != nil {
            log.Printf("Error reading body: %v", err)
            http.Error(w, "can't read body", http.StatusBadRequest)
            return
        }

        // Work / inspect body. You may even modify it!

        // And now set a new body, which will simulate the same data we read:
        r.Body = ioutil.NopCloser(bytes.NewBuffer(body))

        // Create a response wrapper:
        mrw := &MyResponseWriter{
            ResponseWriter: w,
            buf:            &bytes.Buffer{},
        }

        // Call next handler, passing the response wrapper:
        handler.ServeHTTP(mrw, r)

        // Now inspect response, and finally send it out:
        // (You can also modify it before sending it out!)
        if _, err := io.Copy(w, mrw.buf); err != nil {
            log.Printf("Failed to send out response: %v", err)
        }
    })
}