LoginSignup
4

More than 3 years have passed since last update.

Golang: echoフレームワークで複数回Bindする方法

Posted at

こんにちは。サーバサイドエンジニアをしている者です。
今回は副業でGoのechoサーバを使用していて、そこで詰まったことを解決する方法エントリーを書きます。

記事概要

echoサーバでRequestのBodyをechoのfuncのBindで構造体にデコードする際に複数回使用できない。
使用すると

code=400, message=EOF

となりBindができない。
それの解決方法を書きます。

環境

version
go 1.12.5
OS alpine3.10
echo 3.3.10

本文

前提

curl http://localhost:8888/api/sellers -XPOST -H "content-type: application/json" -d '{"seller_type": 1, "email": "fhoirj@hogr.com", "name": "ssss", "uid": "edeedddeddd", "company_name": "234", "company_location": "jofhwf", "department": "部署", "tel": "01099992222", "consultation": "dhaofe", "industry_id": 1}'

このリクエストを投げるときに
seller_typeを取得してそのseller_typeからどのオブジェクトにデコードをするかを判断するということをしたかった。
素直にやるとすると

r.e.POST(path, func(c echo.Context) error {
        dbHandler, ok := infra.ExecFromCtx(c)
        if !ok {
            return xerrors.New("db error")
        }

        sellerType := new(struct {
            SellerType int `json:"seller_type"`
        })

        // ここでsellerTypeをbindする
        if err := c.Bind(sellerType); err != nil {
            return err
        }

                // sellerTypeによって振り分ける、それぞれのオブジェクトにデコードする
        switch sellerType.SellerType {
        case 1:
            in := new(sellerusecase.CreateGeneralSellerInput)
            if err := c.Bind(in); err != nil {
                return err
            }

            return controller.NewSellerController(dbHandler).CreateGeneralSeller(c, in)
        case 2:
            in := new(sellerusecase.CreateEvalSellerInput)
            if err := c.Bind(in); err != nil {
                return err
            }

            return controller.NewSellerController(dbHandler).CreateEvalSeller(c, in)
        default:
            log.Fatal("fatal err")
        }

    }, filter.Transaction())

これを実行するとEOFとなり、エラーになる

解決方法

以下のようにする

r.e.POST(path, func(c echo.Context) error {
        dbHandler, ok := infra.ExecFromCtx(c)
        if !ok {
            return xerrors.New("db error")
        }

        sellerType := new(struct {
            SellerType int `json:"seller_type"`
        })
                // ここで一旦request bodyをbyte配列でもつ
        bodyBytes, err := ioutil.ReadAll(c.Request().Body)
        if err != nil {
            return err
        }

                // sellerTypeをまずは取得する
        if err := json.Unmarshal(bodyBytes, sellerType); err != nil {
            return err
        }

        // ここで予め別のアドレスにおいておいた、request bodyをcontextのbodyに入れ直す
        c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))

                // ここでsellerTypeによって振り分けて、それぞれのオブジェクトでbindする
        switch sellerType.SellerType {
        case 1:
            in := new(sellerusecase.CreateGeneralSellerInput)
            if err := c.Bind(in); err != nil {
                return err
            }

            return controller.NewSellerController(dbHandler).CreateGeneralSeller(c, in)
        case 2:
            in := new(sellerusecase.CreateEvalSellerInput)
            if err := c.Bind(in); err != nil {
                return err
            }

            return controller.NewSellerController(dbHandler).CreateEvalSeller(c, in)
        default:
            log.Fatal("fatal err")
        }

    }, filter.Transaction())

コードを見ればわかるかと思いますが説明すると、

// ここで一旦request bodyをbyte配列でもつ
bodyBytes, err := ioutil.ReadAll(c.Request().Body)
if err != nil {
    return err
}

ここでまずrequest bodyをバイト列で別のアドレスに入れます。

続いて

// sellerTypeをまずは取得する
if err := json.Unmarshal(bodyBytes, sellerType); err != nil {
    return err
}

// ここで予め別のアドレスにおいておいた、request bodyをcontextのbodyに入れ直す
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))

sellerTypeを先程格納したバイト配列から取得します。
その後ここが重要ですがc.Request().Bodyに改めて先程のrequest bodyを格納します。

そうすることで再度Bindできるようになります。

参考文献

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4