Goa v3でmultipart/form-dataを扱うために必要なこと
これは Go 2 Advent Calendar 2020 の16日目の記事です。
はじめに
先日、Goaを使って実装している API で Goa v1 から v3 へバージョンアップを行ないました。
Goa では v1 と v2/v3 の間で大きく仕様が変わっっており、ところどころ詰まる点があります。 特に、multipart/form-data を使ってファイルアップロードをしたいというような場合に、実装が大きく変わって若干詰まったのでご紹介します。
Goaでのmultipart/form-dataを使ったファイルアップロード
まず、Goa v1 と v3 でどのように違いがあるかを紹介します。
v1
v1 では、design で MultipartForm()
を呼び出すことで multipart/form-data を扱うことができるようになります。
また、ファイルを扱う際には File
型というプリミティブ型があり、これをペイロードの型として設定することで、multipart/form-data で送られたファイルを扱うことができるようになります。
var _ = Resource("profiles", func() { Action("submit", func() { Routing(POST("profiles")) Payload(ProfilePayload) MultipartForm() // Uses "multipart/form-data" encoding Description("Post accepts a multipart form encoded request") Response(OK, ResultMedia) }) }) var ProfilePayload = Type("ProfilePayload", func() { Attribute("name", String, "Name") Attribute("birthday", DateTime, "Birthday") Attribute("icon", File, "Icon") // Attribute "icon" contains a file Required("name", "birthday", "icon") })
v3
v3 では、MultipartForm()
が MultipartRequest()
になっています。これはv1 と同様、design で呼び出せば問題ありません。
しかし、v1 に存在していた File
型は v3 では廃止されており、代わりに Bytes
型が追加されました。また、v1 では multipart のデコード処理を Goa が裏側で行なってくれていたのですが、v3 では自分でデコード処理を実装する必要があります。
multipart.go の実装
multipart/form-data のデコード処理を実装した multipart.go を準備します。Goa のリポジトリに例が載っているので、そちらの実装が参考になります。今回はファイルアップロードの例なので、Bytes型のファイル本体とString型のファイル名が渡ってくる場合の実装を示します(といっても、Goa の例とそこまで大きな違いはありません)。
func ImageCreateDecoderFunc(mr *multipart.Reader, p **image.ImageCreatePayload) error { payload := image.ImageCreatePayload{} for { part, err := mr.NextPart() if err == io.EOF { break } if err != nil { return fmt.Errorf("failed to load part. err: %s", err) } _, params, err := mime.ParseMediaType(part.Header.Get("Content-Disposition")) if err != nil { return fmt.Errorf("failed to parse part. err: %s", err) } switch params["name"] { case "inputFile": bytes, err := ioutil.ReadAll(part) if err != nil { return fmt.Errorf("cannot read binary. err: %s", err) } payload.InputFile = bytes case "inputName": name, err := ioutil.ReadAll(part) if err != nil { return fmt.Errorf("cannot read input name. err: %s", err) } payload.InputName = string(key) } } *p = &payload return nil }
これで multipart 形式のデータをデコードすることができるようになります。
おわりに
今回は Goa v3 で multipart/form-data を扱う例を紹介しました。 Goa はv1 から v3 へ移行しようとすると結構大変な部分がありますし、そもそも Goa の design も学ぶ必要があります。しかし、非常に便利なフレームワークです。
ぜひ使ってみてはどうでしょうか。