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

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



## 参考文献
- https://github.com/labstack/echo/issues/782
- https://medium.com/@xoen/golang-read-from-an-io-readwriter-without-loosing-its-content-2c6911805361