むろっちのStacking

日々の中で学んだIT知識をメモして置く場所

Echoで複数回Bind可能なBinderを作る

背景


echoではBind構文によりstructにRequestのQueryやBody部をマッピングすることができる
しかしながらBody部は2回目以降Bindしようとすると以下のようにErrorが発生する

"code=400, message=EOF, internal=EOF"

これを複数回Bindできるようにしようと言うのが今回の趣旨である

原因


HeaderやURL.Queryはmap型map[string][]stringで定義されているが、
Bodyはio.ReadCloser型なので読み取り開始位置の移動exp) f.Seek(0,0)ができない

解決法


一度Body部を読み出してしまい、Read後に再度未使用のio.ReadCloser型のstructをBodyに代入するCustomBinderを作成する

環境

CustomBinder作成

module/binder.go

package module

import (
    "bytes"
    "io/ioutil"

    "github.com/labstack/echo/v4"
)

type CustomBinder struct {
    binder echo.DefaultBinder
}

func NewBinder() *CustomBinder {
    return &CustomBinder{
        binder: echo.DefaultBinder{},
    }
}
func (cb *CustomBinder) Bind(i interface{}, c echo.Context) (err error) {
    var b []byte
    if b, err = ioutil.ReadAll(c.Request().Body); err != nil {
        return
    }
    c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(b))

    err = cb.binder.Bind(i, c)

    req := c.Request()
    req.Body = ioutil.NopCloser(bytes.NewBuffer(b))
    c.SetRequest(req)

    return
}

main.go

package main

import (
    "fmt"
    "net/http"
    "sample1/module"

    "github.com/labstack/echo/v4"
)

func main() {
    // Echo instance
    e := echo.New()

      // customBinderでdefaultBinderを上書き
    e.Binder = module.NewBinder()

    // Routes
    e.POST("/", hello)

    // Start server
    e.Logger.Fatal(e.Start(":1323"))
}

type form struct {
    UserID int    `json:"user_id"`
    Name   string `json:"name"`
}

// Handler
func hello(c echo.Context) error {
    f1 := form{}
    if err := c.Bind(&f1); err != nil {
        return c.JSON(http.StatusBadRequest, err.Error())
    }
    fmt.Println("f1", f1.UserID)

    f2 := form{}
    if err := c.Bind(&f2); err != nil {
        return c.JSON(http.StatusBadRequest, err.Error())
    }
    fmt.Println("f2", f2.UserID)
    return c.String(http.StatusOK, "Hello, World!")
}

元の原因がgolanghttp.Request().Bodyの定義によるものなので、
別のFWでも考え方は使い回せると思う