前回 goa で multipart/form-data
を扱う方法についてご紹介しました。
multipart/form-data
に関連してよく取り上げられるのがファイルアップロードですね。 HTML フォームを介したサーバへのファイル送信は多くのウェブサービスで利用されています。しかし前回の記事ではその話については言及しませんでした。理由は「ファイル」を表すデータ型がまだ goa に存在しなかったからです。
goa では Type DSL を使ってユーザ型を定義します。また、ユーザ型の各要素を定義するには Attribute DSL を使用します。
var ProfilePayload = Type("ProfilePayload", func() { Attribute("name", String, "Name") Attribute("birthday", DateTime, "Birthday") Required("name", "birthday") })
Attribute
の 2 番目の引数がデータ型です。上記の String
や DateTime
を含む、以下 7 種類が用意されています。
Boolean
Integer
Number
String
DateTime
UUID
Any
そして今回、新たに 8 種類目の File
型が追加されました。
では先ほどの design に File
型の要素を追加してみましょう。
var ProfilePayload = Type("ProfilePayload", func() { Attribute("name", String, "Name") Attribute("birthday", DateTime, "Birthday") Attribute("icon", File, "Icon") Required("name", "birthday", "icon") })
ここから生成される Go の構造体は以下の形になります。
// ProfilePayload user type. type ProfilePayload struct { // Birthday Birthday time.Time `form:"birthday" json:"birthday" xml:"birthday"` // Icon Icon *multipart.FileHeader `form:"icon,omitempty" json:"icon,omitempty" xml:"icon,omitempty"` // Name Name string `form:"name" json:"name" xml:"name"` }
Icon
の型が *multipart.FileHeader になっていますね。これは標準パッケージの mime/multipart で提供されている構造体で、ファイル名、 MIME ヘッダ、ファイルサイズを含んでいます。
type FileHeader struct { Filename string Header textproto.MIMEHeader Size int64 }
また、この構造体には Open() というメソッドが用意されており、ファイルを multipart.File として取得することが可能です。 multipart.File
は io.Reader
などを実装しているので通常のファイルと同じようにように操作することができます。
以上を踏まえて、アップロードされたファイルをサーバのローカルディレクトリに保存する処理を controller に実装してみましょう。
func (c *ProfilesController) Submit(ctx *app.SubmitProfilesContext) error { res := &app.ResultMedia{ Name: ctx.Payload.Name, Birthday: ctx.Payload.Birthday, } file, err := ctx.Payload.Icon.Open() if err != nil { return err } defer file.Close() f, err := os.OpenFile("./icons/"+ctx.Payload.Icon.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, file) return ctx.OK(res) }
前述の通り ctx.Payload.Icon
が *multipart.FileHeader
となっているので Open()
で multipart.File
を取得しています。あとはアップロードされたものと同じ名前のファイルを os.OpenFile() で作成し io.Copy() で書き込むだけです。
この例は既に公式サンプルにも収録されているので以下からご覧ください。
ファイルアップロードにも対応した goa が益々便利ですね!