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 も学ぶ必要があります。しかし、非常に便利なフレームワークです。
ぜひ使ってみてはどうでしょうか。
ファイルの変更を監視してコマンドを実行するスクリプト
openssl sha256 コマンドでファイルのハッシュ値を生成し、それが変わっていたら引数で指定したコマンドを実行するスクリプト。
何気に初めて自分で使うためにシェルスクリプトを書きました。
スクリプト
#!/bin/bash function __usage() { cat <<EOS usage: $(basename $0) <target filename> <command> EOS } __check() { echo `openssl sha256 -r $1 | awk '{print $1}'` } if [ $# -ne 2 ]; then __usage exit 1 fi echo "watching: $1" INTERVAL=1 # second last=`__check $1` while true; do sleep $INTERVAL current=`__check $1` if [ "$last" != "$current" ]; then echo 'execute...' eval $2 last=$current echo 'done' fi done
使い方
./watch.sh filename 'command'
ぼくの場合はwebpack --watch
でビルドされたファイルをローカルサーバを立ててる別のディレクトリにコピーして画面に反映させるために使いました。
参考にした記事
MT-03納車と立ちゴケ、そしてこれから
先日、MT-03を納車しました。
MT03納車しました! pic.twitter.com/TQC6tHUTKk
— じょん (@johnmanjiro13) 2020年10月3日
もともと高専在学中に二輪免許を取ろうと思っていたんですが、結局そのお金でギターを買っちゃったので取れず。 大学院の休学中にも取ろうかと思い立ちましたが単純にお金がなくて取れず。 そして社会人になってやっと免許を取ることができました。
一緒に免許を取ろうと誘ってくれた同期には感謝です。
MT-03を選んだ経緯
ということで初めてのバイクに選んだのはYAMAHAのMT-03、2020年モデル。
もともとカウルがないバイクがいいなぁと思っていたのと、クラシックな見た目が好きだったのでホーネット250を検討してました。
ただやはり古いバイクだということと、最近モトブログなんかでホーネット乗ってる人が多い影響かめちゃくちゃ高かったので選択肢から外してしましました。 あの音とでっかいタンク、タイヤは魅力的だったんですけどね。後悔はないです。
そこで色々みていた中でいいなと思ったのがMT-03とSR400。
もともと高専時代にビーノに乗ってたこともあってなんとなくYAMAHAに親近感があったので結構YAMAHAに寄りました(大学時代はリトルカブに乗ってたけど)。
ただSRはキックしかないのが個人的には結構大きく、最終的にMT-03を選びました。
ちなみにMT-25じゃなくMT-03にしたのは、250ccでパワー不足とか感じたら嫌だなぁと思ったからです。
立ちゴケ
納車して2日後、今まで電車で通っていた映画館にバイクで行ってみようと思い仕事終わりにバイクに乗ってみました。
駐輪場にバイクを駐めようとした時、ぐらっと右側にバランスを崩してしまい無事立ちゴケ。 一時停止していて、若干坂道になっていたのと、無理にハンドルを切って進もうとしたせいだと思います。
立ちゴケ自体はいずれするもんだろうと思っていたので、そこまでパニックにはならずすぐバイクを立てて傷の確認。 幸いにボディに大きな傷はなく、マフラーに少し傷がついたのとブレーキレバーが曲がったくらいで済みました。
今のところ気持ちは全然平気です。ただもうこけないようにしようと誓いました。
ブレーキレバーは近いうちに交換してみようと思います。純正かなぁ。
これから
バイクは納車したんですが、ここのところ台風や秋雨前線の影響で雨が続き全然乗れてません。
土日にも人との予定が入っていたり仕事の輪番で運転を控えたりしてるので、なかなか時間がとれていない現状。
バイクに乗るために自分の現状を変えようかなと思い始めているところです。
人と会う予定を輪番の日に合わせるとかね。
まだまだ乗れてないので本当の楽しみには触れられていない感じもしますが、それでも楽しいです。一人で結構遠くまで乗ったりしたいな。
[Go] handlerに引数を追加する
先日、GoでAPIを書いていた時にServeHTTP関数に引数を追加したい場合があったのでその対処法。
GoのHandlerは次のように定義されています。
type Handler interface { ServeHTTP(ResponseWriter, *Request) }
このinterfaceを満たした関数を実装することでhandlerを作ることができますが、場合によっては引数を増やしたいということがあります。 この時ただServeHTTP関数に引数を追加してしまうと、Handler interfaceを満たさなくなるので使えません。
そこで、引数を追加した関数をラップしたServeHTTP関数をHandlerに持たせます。
title := "handlerに引数を追加する" type handler struct { } func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request ) { handle(w, r, title) } func handle(w http.ResponseWriter, r *http.Request, title string) { // 処理 }
muxでルーティングしている場合にも、無名関数でラップすることで使えます。
r := mux.NewRouter() r.HandleFunc("/title", func(w http.ResponseWriter, r *http.Request) { handler(w, r, title) }) func handler(w http.ResponseWrite, r *http.Request, title string) { // 処理 }
考えてみれば当たり前のことなんですが、今まで使う機会がなかったのでなかなか思いつきませんでした。
echo用にzerologのラッパーzeroechoをつくった
最近仕事でechoとzerologを使う機会があったので、勉強がてらにwrapperを作ってみました。 ぶっちゃけ使うことはないと思います。
実装
echo.Loggerのinterfaceは次のように定義されてます。
Logger interface { Output() io.Writer SetOutput(w io.Writer) Prefix() string SetPrefix(p string) Level() log.Lvl SetLevel(v log.Lvl) SetHeader(h string) Print(i ...interface{}) Printf(format string, args ...interface{}) Printj(j log.JSON) Debug(i ...interface{}) Debugf(format string, args ...interface{}) Debugj(j log.JSON) Info(i ...interface{}) Infof(format string, args ...interface{}) Infoj(j log.JSON) Warn(i ...interface{}) Warnf(format string, args ...interface{}) Warnj(j log.JSON) Error(i ...interface{}) Errorf(format string, args ...interface{}) Errorj(j log.JSON) Fatal(i ...interface{}) Fatalj(j log.JSON) Fatalf(format string, args ...interface{}) Panic(i ...interface{}) Panicj(j log.JSON) Panicf(format string, args ...interface{}) }
そこで、これらのメソッドをもつLoggerを実装しました。
https://github.com/johnmanjiro13/zeroecho/blob/master/log.go
ついでにmiddlewareも実装したので、リクエストログもこれで扱えます。
https://github.com/johnmanjiro13/zeroecho/blob/master/middleware.go
パッケージ公開
そもそもパッケージを公開するのが初めてだったので調べたんですが、GoはGithubにプッシュしたらもうgo get
できるんですね。知らなかった。
バージョンもGithubのタグで管理できます。思いのほか簡単でびっくりしました。
まとめ
今回実装するにあたりechoのlog周りのコードやzerologのコードを読んだのが面白かったです。
echoはシンプルに使えていいなぁと思ったので、もうちょっと他の部分のコードも読んでみようかと思います。
みんなのGo言語 改訂2版がすごくよかった
先日、みんなのGo言語 改訂2版を読み終えました。
【改訂2版 みんなのGo言語/松木 雅幸他】実践的な内容で勉強になる → https://t.co/Iqg4D124qr #bookmeter
— じょん (@johnmanjiro13) 2020年7月29日
実は1版の方も一度購入していたんですよね。ただその時はまだ本格的にGoを書いたこともなく、そもそもGoで何ができるかもよく知らないという状態だったので全然内容に関心がわかず、読まないままでした。
しかし、就職して業務でGoを書くようになってから1年以上経ちました。 最近自分が書いているコードに関しても「もっといい書き方はないか」と考えることも増え、さらにGoを使ってなにか個人的な開発をしたいと(漠然とですが)思うまでになりました。 今ならこの本を楽しんで読めるのではと思い、手に取った次第です。
ここでは個人的にいいなと思った点をまとめていきます。
みんなのGo言語 改訂2版
概要
本書は、複数のエンジニアの方々*1が書かれた共著本です。全体を通してGo言語の文法などには触れておらず、各著者が業務でGoを活用する上のノウハウが書かれています。 そのため、ある程度Goの言語仕様を理解している方が読みやすいと思います。
個人的に求めていたものがGoを実践的に使うノウハウそのものだったので、非常にハマりました。
ちなみに普段書籍はKindleで買うのですが、今回は手でパラパラとめくることが多そうな本だったので紙媒体で購入しました。
構成
本書は全7章で構成されています。
第1章. Goによるチーム開発のはじめ方とコードを書く上での心得
第2章. マルチプラットフォームで動作する社内ツールのつくり方
第3章. 実用的なアプリケーションを作るために
第4章. コマンドラインツールを作る
第5章. The Dark Arts Of Reflection
第6章. Goのテストに関するツールセット
第7章. データベースの使い方
このうち第7章は2版から追加されたものです。
面白かったところ
どの章も非常に面白かったです。 ここでは特に面白いと思ったところを挙げていきます。
Goでマルチプラットフォームに対応する
これは第2章で取り上げられている内容です。GoはWindowsやLinuxといった異なる環境に対してもほぼ同じ実装を使うことができますし、各OS向けのビルドができるためランタイムを実行環境にインストールしたりする必要がありません。
しかし、実際にはある程度のルールがあり、それを守らないとマルチプラットフォームで動作させることはできません。 たとえばディレクトリのパス解決にはpathパッケージではなくてpath/filepathパッケージを使おうとか、runtime.GOOSが使えるよとか、そういう話です。
なぜこの章が面白かったかというと、まさにここで書かれているようなOSごとの実装を最近見ていて個人的にタイムリーだったからです。
最近まで社内勉強会の一環として、Futureさんのテックブログ*2を参考に、Goの標準パッケージのコードリーディングを行っていました。そこで見た(上述した)path/filepathパッケージ*3がまさにOSごとに実装されていました。
OSを超えてよしなに使えるパッケージだからそりゃOSごとに実装されているよねという話ではあるんですが。
ちょうどタイムリーな話題だったので印象に残りました。また純粋に自分でコマンドを配布したいような場合にはどうしてもマルチプラットフォームに対応する必要があるので、役立つし非常に面白かったです。
ちなみにGoの標準パッケージリーディングは面白いのでおすすめです。社内でよく使うライブラリでもいいかもしれませんね。
コマンドラインツールの作り方
これは第4章で取り上げられている内容です。GoはCLIツールをとても作りやすいです。前述したように複数プラットフォームへも対応しやすく、配布そのものも簡単です。もちろんpure Goでも書くことができますが、より開発しやすいように様々なサードパーティ製のパッケージが存在しています。 この章ではまずGoの標準パッケージを使った実装方法が紹介された後、サードパーティ製パッケージの紹介、CLIツールの実装方法が書かれています。
ぼくが初めてGoで開発したものがCLIだったので興味が湧きました。ちなみにその際はspf13/cobra*4を使って実装しました。 しかし今回面白いと感じたのは、ここで紹介されていたmitchellh/cli*5を使った実装です。
spf13/cobraは構造体に名前や使い方、実際の処理を追加していくことでコマンドを実装するのに対し、mitchellh/cliはコマンドをインターフェースとして定義するのが面白いなぁと感じました。 ただflagが使えないので使い勝手はあんまりよくなかったりするのかもしれません。
reflectパッケージ
これは第5章で取り上げられている内容です。GoにはReflection機能を提供するreflectパッケージがあります。ぼくの中でreflectといえば、「型を失うやべーやつ。手を出すな」くらいの認識しかありませんでした。
具体例と一緒にreflectパッケージの使い方が書かれていて、とてもわかりやすいです。また使用する際にハマりやすいポイントについても詳しく書かれていて、非常に参考になります。
実際に業務でも書くことがなかったので、改めて使い方や実装を知ることができたのは非常によかったです。ちなみにreflectは標準パッケージでも結構でてきたりしました。けどできれば書きたくないですね。
まとめ
簡単にではありますが、みんなのGo言語を読んで面白かった点を書いてみました。 人の経験から得られるノウハウはやっぱり参考になるなぁと実感します。
Goがある程度書ける、Goで何か作ってみたいという人にはとてもおすすめの本なのでぜひ手にとって読んでみてください。
Nuxt.jsとFirebaseでWebページを作成・公開した
個人ページ(johnmanjiro)をNuxt.jsとVuetifyで作り直してみました。
以前はHTMLベタ書きでとても見苦しいページだったんですが、少しは見やすくなったかなと思います。
(修正前のスクショを撮ってませんでした…)
なぜ作り直したのか
単純に、HTMLベタ書きなのをどうにかしたかったから。
ちょうど1年前くらいにドメインをとって(ちなみに .ro はルーマニアです)、まずはとにかくページを公開しようとHTMLで書きました。 そのままずっと放置していたので、いい加減JSフレームワーク使ったりして動きのあるページにしようと思い立ちました。
なぜNuxt.jsなのか
だいたい以下のような理由です。
- プロジェクトの生成を自動でやってくれる
- Vueで書ける
- UIデザインにVuetifyが使える
仕事でVueを書いた経験があったので、Vueを使ってみようと考えていました。
ただ自分でwebpackなどの準備をするのは面倒臭かったので、そこらへんを自動でやってくれるNuxt.jsを採用しました。
UIコンポーネントのVuetifyを使えるのも大きかったです(その辺のデザインとか考えるのが苦手)。
おかげでGoogleっぽいUIになりました。
Firebaseで公開
今回はペライチのWebページなので、簡単に公開できる機構さえあれば問題ありませんでした。
もともとVPSを借りていたんですが管理が面倒くさく、ホスティングしてくれてコマンド一発でデプロイできるFirebaseを採用しました。非常に楽に公開できて満足しています。
まとめ
Nuxt.jsとFirebaseでWebページを公開しました。
細かい内容に関してはメモ書き程度に今後書ければいいなと思ってます。