生涯未熟

生涯未熟

プログラミングをちょこちょこと。

echoのmiddlewareで一度読んだRequestBodyをもう一度読む

Gopherの皆さんはecho使ってますでしょうか?僕は薄くて好きなのでよく使ってます。

さて、echoにはmiddlewareという機能がございます。
Middleware | Echo - High performance, minimalist Go web framework

ハンドラの実行前に処理差し込めるやつですが、このmiddlewareとして以下のような処理を差し込むとします。

func CheckJSON(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        jsonBlob, err := ioutil.ReadAll(c.Request().Body)
        if err != nil {
            return err
        }
        s := string(jsonBlob)
        if !validator.IsJSON(s) {
            return c.NoContent(http.StatusBadRequest)
        }

        return next(c)
    }
}

飛んできたリクエストがJSONかどうか?というのをチェックするmiddlewareになります。
さて、このmiddlewareを実行した後にハンドラでこのような処理を実行するとしましょう。

func Hoge(c echo.Context) error {
    var jsonBody map[string]interface{}
    jsonBlob, err := ioutil.ReadAll(c.Request().Body)
    if err != nil {
        return err
    }
    err := json.Unmarshal(jsonBlob, &jsonBody)
    if err != nil {
        return errors.New("failed unmarshal json")
    }

    return c.NoContent(200)
}

またもやリクエストから読み込んでる処理ですね。
さて、これを実行すると failed unmarshal json のエラーになります。
これは ioutil.ReadAll がc.Request().Bodyを全て読み込んでいるため、次回以降読み込もうとするとnilが返ってくるためです。

で、ここからどうやって2回目の読み込みを実現するかですが、答えは簡単で「Bodyの内容を上書く」という解決方法があります。

func CheckJSON(next echo.HandlerFunc) echo.HandlerFunc {
    return func(c echo.Context) error {
        jsonBlob, err := ioutil.ReadAll(c.Request().Body)
        if err != nil {
            return err
        }
        s := string(jsonBlob)
        if !validator.IsJSON(s) {
            return c.NoContent(http.StatusBadRequest)
        }

        c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(jsonBlob))

        return next(c)
    }
}

こうすることで2回目にキチンと ioutil.ReadAll を実行しても内容を読み込んでくれます。