こんにちは。サーバサイドエンジニアをしている者です。
今回は副業で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できるようになります。