multipart/form-data
という MIME タイプがあります。これは主に HTML フォームの内容をサーバに対して POST する際に用いられます。
goa の Request Context には http.Request が埋まっているので、そこから MultipartReader() や ParseMultipartForm() を呼び出すことで multipart/form-data
を扱うことができます。以下は goa の公式サンプルに収録されているコードです。
func (c *ImageController) Upload(ctx *app.UploadImageContext) error { // Assumes the image is under multipart section named "file" reader, err := ctx.MultipartReader() if err != nil { return goa.ErrBadRequest("failed to load multipart request: %s", err) } if reader == nil { return goa.ErrBadRequest("not a multipart request") } var images []*app.ImageMedia for { p, err := reader.NextPart() if err == io.EOF { break } if err != nil { return goa.ErrBadRequest("failed to load part: %s", err) } f, err := os.OpenFile("./images/"+p.FileName(), os.O_WRONLY|os.O_CREATE, 0666) if err != nil { return fmt.Errorf("failed to save file: %s", err) // causes a 500 response } defer f.Close() io.Copy(f, p) data := c.saveImage(p.FileName()) images = append(images, &app.ImageMedia{ID: data.ID, Filename: data.Filename, UploadedAt: data.UploadedAt}) } return ctx.OK(images) }
multipart.Reader を介して、各 part の内容を取り出しています。
このように、従来から goa では multipart/form-data
を扱うことができたのですが、これはあくまでサーバ実装の話です。 design (設計) の中で multipart/form-data
であることを明示するための DSL は用意されていなかったので、 Swagger にも formData
として定義を出力することはできませんでした。
しかし今回、この機能に関する MultipartForm という新しい DSL が追加になりました。今後は design レベルで multipart/form-data
を取り扱うことができます。
MultipartForm
の使用例について、まずは design から見てみましょう。
var _ = Resource("profiles", func() { Action("submit", func() { Routing(POST("profiles")) Payload(ProfilePayload) MultipartForm() Description("Post accepts a multipart form encoded request") Response(OK, ResultMedia) }) }) var ProfilePayload = Type("ProfilePayload", func() { Attribute("name", String, "Name") Attribute("birthday", DateTime, "Birthday") Required("name", "birthday") })
MultipartForm
は Action DSL の中で使用します。これによって goa は当該 Action の Payload を multipart/form-data
として扱うようになります。
これを元に出力される Swagger 定義は以下のようになります。
/profiles: post: description: Post accepts a multipart form encoded request operationId: profiles#submit parameters: - description: Birthday in: formData name: birthday required: true type: string - description: Name in: formData name: name required: true type: string produces: - application/vnd.goa.example.form responses: "200": description: OK schema: $ref: '#/definitions/ResultMedia' schemes: - http summary: submit profiles tags: - profiles
parameters
に書かれている各パラメータが in: formData
になっています。これは OpenAPI Specification Version 2 で規定されている form-data を表す定義になります。
最後に controllers の実装です。冒頭に記述した通り、従来は multipart をパースして各 part をひとつずつ取り出す必要がありました。今回の機能追加後、コードは以下の形になります。
func (c *ProfilesController) Submit(ctx *app.SubmitProfilesContext) error { res := &app.ResultMedia{ Name: ctx.Payload.Name, Birthday: ctx.Payload.Birthday, } return ctx.OK(res) }
これだけです。 multipart のパースは、このコードに到達する前に goa が自動で行います。各 part の内容は既に ctx.Payload
のフィールドにセットされているので、それを使ったコードを書くだけで良いのです。
より詳細な例は以下をご覧ください。