Goフレームワーク Gin について
Ginを使うことにしました。 README.mdを通読したので、メモしておきます。
Ginを選んだ理由
困ったときにコード読みたかったので、リフレクションを使っているMartiniはコード追いにくいような気がしたからやめた。 お酒のマティーニが好きだったので、Martiniにしたかったけど。。 お酒つながりでGinにした。パフォーマンスもMartiniよりイイらしい。
Gin Web Framework(訳)
GinはGoで書かれたWebフレームワークです。Ginはマティーニと類似のAPIを提供し、httprouterのおかげでマティーニの40倍ほど高速です。もしあなたがパフォーマンスと生産性を必要としているのであれば、Ginを気に入るでしょう。
インストール&使い方
ダウンロードとインストール
$ go get github.com/gin-gonic/gin
プログラム(app.go)
package main import ( "github.com/gin-gonic/gin" "net/http" ) func main() { r := gin.Default() r.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "Hello World") }) r.Run() // listen and server on 0.0.0.0:8080 }
ビルド&起動
$ go build app.go $ ./app
http://localhost:8080/
にアクセスするとHello World
と表示される。
APIサンプル集
ginのgithubにありますが、簡単に訳してメモしておきます。
GET,POST,PUT,PATCH,DELETE,OPTIONSの使い方
func main() { // デフォルトのミドルウェアとともにginルーターを作成 // ミドルウェアはロガーとリカバリー(クラッシュフリー) router := gin.Default() router.GET("/someGet", getting) router.POST("/somePost", posting) router.PUT("/somePut", putting) router.DELETE("/someDelete", deleting) router.PATCH("/somePatch", patching) router.HEAD("/someHead", head) router.OPTIONS("/someOptions", options) // PORT環境変数が指定されていない場合は8080ポートで待受 router.Run() // router.Run(":3000") // ポートをハードコーディングしたい場合 }
パス内のパラメタ
*
と:
の違いに注目。
func main() { router := gin.Default() // このハンドラは /user/john にマッチするが、 /user/ や /user にはマッチしない router.GET("/user/:name", func(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, "Hello %s", name) }) // でもこれは /user/john/ や /user/john/send にマッチする // /user/john にマッチするルーターがなければ、/user/john/ にリダイレクトされる router.GET("/user/:name/*action", func(c *gin.Context) { name := c.Param("name") action := c.Param("action") message := name + " is " + action c.String(http.StatusOK, message) }) router.Run(":8080") }
クエリストリングパラメタ
GETの値の参照方法。
func main() { router := gin.Default() // クエリストリングパラメーターは既存のrequest objectの下でパースされる // このリクエストは /welcome?firstname=Jane&lastname=Doe へ返答する router.GET("/welcome", func(c *gin.Context) { firstname := c.DefaultQuery("firstname", "Guest") // Geustはデフォルト値? lastname := c.Query("lastname") // c.Request.URL.Query().Get("lastname") のショートカット c.String(http.StatusOK, "Hello %s %s", firstname, lastname) }) router.Run(":8080") }
Multipart/Urlencoded Form
POSTの値の参照方法。
func main() { router := gin.Default() router.POST("/form_post", func(c *gin.Context) { message := c.PostForm("message") nick := c.DefaultPostForm("nick", "anonymous") // anonymousはデフォルト値 c.JSON(200, gin.H{ "status": "posted", "message": message, "nick": nick, }) }) router.Run(":8080") }
POST時にクエリストリングも参照する
func main() { router := gin.Default() router.POST("/post", func(c *gin.Context) { id := c.Query("id") page := c.DefaultQuery("page", "0") name := c.PostForm("name") message := c.PostForm("message") fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message) }) router.Run(":8080") }
ファイルのアップロード
func main() { router := gin.Default() router.POST("/upload", func(c *gin.Context) { file, header , err := c.Request.FormFile("upload") filename := header.Filename fmt.Println(header.Filename) out, err := os.Create("./tmp/"+filename+".png") if err != nil { log.Fatal(err) } defer out.Close() _, err = io.Copy(out, file) if err != nil { log.Fatal(err) } }) router.Run(":8080") }
グルーピング ルーティング
APIのバージョンとかに使えそう?
func main() { router := gin.Default() // Simple group: v1 v1 := router.Group("/v1") { v1.POST("/login", loginEndpoint) v1.POST("/submit", submitEndpoint) v1.POST("/read", readEndpoint) } // Simple group: v2 v2 := router.Group("/v2") { v2.POST("/login", loginEndpoint) v2.POST("/submit", submitEndpoint) v2.POST("/read", readEndpoint) } router.Run(":8080") }
ミドルウェアを使用する
func main() { // デフォルトのミドルウェアなしのGinルーターを作成 r := gin.New() // グローバルミドルウェア r.Use(gin.Logger()) r.Use(gin.Recovery()) // ルーターごとのミドルウェア, 使いたいだけ追加できます。 r.GET("/benchmark", MyBenchLogger(), benchEndpoint) // 認証グループ // authorized := r.Group("/", AuthRequired()) // これは以下のようにもかけます authorized := r.Group("/") // この場合authorizedグループだけにAuthRequired() ミドルウェアを使う。 authorized.Use(AuthRequired()) { authorized.POST("/login", loginEndpoint) authorized.POST("/submit", submitEndpoint) authorized.POST("/read", readEndpoint) // ネストされたグループ testing := authorized.Group("testing") testing.GET("/analytics", analyticsEndpoint) } // Listen and server on 0.0.0.0:8080 r.Run(":8080") }
モデルバインディングとバリデーション
リクエストボディを型にバインドするには、モデルバインディングを使用してください。 GinはJSON,XMLと標準的なフォームの値(foo=bar&hoge=fuga)をサポートしています。
// JSONからバインド type Login struct { User string `form:"user" json:"user" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } func main() { router := gin.Default() // JSONをバインディングする例({"user": "manu", "password": "123"}) router.POST("/loginJSON", func(c *gin.Context) { var json Login // Login型変数 if c.BindJSON(&json) == nil { // c.BindJSON関数を使ってバインドする BindJSONの戻り値はerrorオブジェクトかな if json.User == "manu" && json.Password == "123" { c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) } else { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) } } }) // HTML formをバインドする例(user=manu&password=123) router.POST("/loginForm", func(c *gin.Context) { var form Login // Login型変数 // content-typeにより元データの形式(JSON,XML,form)を予想してバインドする if c.Bind(&form) == nil { if form.User == "manu" && form.Password == "123" { c.JSON(http.StatusOK, gin.H{"status": "you are logged in"}) } else { c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"}) } } }) // Listen and server on 0.0.0.0:8080 router.Run(":8080") }
Multipart/Urlencoded(ポスト) のバインディング
同様にできる
XML,JSON,YAMLの結果を返す
func main() { r := gin.Default() // gin.H は map[string]interface{} へのショートカット r.GET("/someJSON", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) r.GET("/moreJSON", func(c *gin.Context) { // 構造体も使える var msg struct { Name string `json:"user"` Message string Number int } msg.Name = "Lena" msg.Message = "hey" msg.Number = 123 // この msg.Name は JSONないでは "user"となる // 出力{"user": "Lena", "Message": "hey", "Number": 123} c.JSON(http.StatusOK, msg) }) r.GET("/someXML", func(c *gin.Context) { c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) r.GET("/someYAML", func(c *gin.Context) { c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK}) }) // Listen and server on 0.0.0.0:8080 r.Run(":8080") }
静的ファイル
静的コンテンツは以下のようにディレクトリやファイルごとにマッピングしておける。
func main() { router := gin.Default() router.Static("/assets", "./assets") router.StaticFS("/more_static", http.Dir("my_file_system")) router.StaticFile("/favicon.ico", "./resources/favicon.ico") // Listen and server on 0.0.0.0:8080 router.Run(":8080") }
HTMLを返却する
LoadHTMLTemplates()関数を使う。
func main() { router := gin.Default() router.LoadHTMLGlob("templates/*") // 事前にテンプレートをロード //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html") // ファイル指定でロード router.GET("/index", func(c *gin.Context) { // テンプレートを使って、値を置き換えてHTMLレスポンスを応答 c.HTML(http.StatusOK, "index.tmpl", gin.H{ "title": "Main website", }) }) router.Run(":8080") }
templates/index.html
<html> <h1> {{ .title }} </h1> </html>
同じ名前でディレクトリが異なるテンプレートの使い方
func main() { router := gin.Default() router.LoadHTMLGlob("templates/**/*") router.GET("/posts/index", func(c *gin.Context) { c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{ "title": "Posts", }) }) router.GET("/users/index", func(c *gin.Context) { c.HTML(http.StatusOK, "users/index.tmpl", gin.H{ "title": "Users", }) }) router.Run(":8080") }
リダイレクト
r.GET("/test", func(c *gin.Context) { c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") })
独自ミドルウェアの使い方
func Logger() gin.HandlerFunc { return func(c *gin.Context) { t := time.Now() // Set example variable c.Set("example", "12345") // リクエスト前 c.Next() // リクエスト後 latency := time.Since(t) log.Print(latency) // 送信したリクエストにアクセスできる status := c.Writer.Status() log.Println(status) } } func main() { r := gin.New() r.Use(Logger()) r.GET("/test", func(c *gin.Context) { example := c.MustGet("example").(string) // it would print: "12345" log.Println(example) }) // Listen and server on 0.0.0.0:8080 r.Run(":8080") }
ミドルウェア内でのゴルーチン
ミドルウェアやハンドラー内でゴルーチンを使う場合、オリジナルのcontextは使うべきでない。リードオンリーコピーを使うようにしなさい。
func main() { r := gin.Default() r.GET("/long_async", func(c *gin.Context) { // create copy to be used inside the goroutine cCp := c.Copy() go func() { // simulate a long task with time.Sleep(). 5 seconds time.Sleep(5 * time.Second) // note that you are using the copied context "cCp", IMPORTANT log.Println("Done! in path " + cCp.Request.URL.Path) }() }) r.GET("/long_sync", func(c *gin.Context) { // simulate a long task with time.Sleep(). 5 seconds time.Sleep(5 * time.Second) // since we are NOT using a goroutine, we do not have to copy the context log.Println("Done! in path " + c.Request.URL.Path) }) // Listen and server on 0.0.0.0:8080 r.Run(":8080") }
独自のHTTPの設定
http.ListenAndServe()
を以下のように直接使う
func main() { router := gin.Default() http.ListenAndServe(":8080", router) }
または
func main() { router := gin.Default() s := &http.Server{ Addr: ":8080", Handler: router, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, MaxHeaderBytes: 1 << 20, } s.ListenAndServe() }
グレースフルリスタート
グレースフルリスタートしたいですか?いくつかの方法があります。 fvbock/endlessでデフォルトのListenAndServeを置換できます。
router := gin.Default() router.GET("/", handler) // [...] endless.ListenAndServe(":4242", router)
endlessの代わりにmannersというものもあります。
Ginを使ったプロジェクトの例
Go: Webアプリケーションを作るチュートリアル
wikiを作るチュートリアル をやってみたのでメモ。
GoはPHPみたいに1リクエスト1プロセスでなく、ServletやNodeJSのように1つのプロセスで複数のリクエストを受け付ける方式。
以下の様なことが身につきました。
Goツアーを読んだ その3(最後)
Goツアーの最終章のConcurrency(並列処理)について読みました。 このあたりを見ると、Webアプリの開発言語も開発でますが、バッチ処理やデータ集計などもパフォーマンスを意識して記述していける言語な印象を受けました。
concurrency より
- goroutine(ゴルーチン)はGOの並列処理のための軽量なスレッド
go fn()
のようにgo
をつけて関数を呼ぶことで実行する- チャネル型を使って値の送受信を行うとゴルーチンの同期が可能
ch := make(chan int)
のように変数の宣言を行う- チャネルのバッファが詰まると、実行時にdeadlockが発生する
- チャネルのクローズを判別するには
v,ok := <-ch
のokがfalseなこと for i := range ch {}
のように記述することでチャネルのクローズまでループするselect
ステートメントの話があったけど、ちょっとイメージできなかったのでとばす- 排他制御のための
sync.Mutex
のLock
,Unlock
メソッドで実現する
Goツアーを読んだ その2
moretypesより
- 配列は型の1部なので、サイズは変更できない
- goにおいてスライスは配列より一般的
- スライスは
var s[]int = a[1:4]
と書く(aは配列) - スライスは配列の参照になり、スライスの要素を変更したら配列の要素も変更される
- 全範囲を表すスライスは
a[:]
ほかにもa[:10]
a[0:]
とか書ける - スライスの要素数と容量は動的に変更できlen(s),cap(s)で確認できる
- スライスのゼロ値はnil
make
関数でスライスが作れる.これが動的な配列を扱う方法になる。- スライスの要素にはスライスも含めてなんでも入れることが可能
- スライスに要素を追加するときは
append
関数を使う。 容量の調整も行われる。 - イテレーションは
for i,v := range s {}
i:index, v:value, s: slice - 連想配列は
map
。make
関数で作成できる。m := make(map[string]string)
- キーの存在は
elem, ok := m[key]
でOKがtrue/falseで判別できる - 関数も変数。
fn := func(){}
とかできるし、関数の引数にすることも可能
methods より
- goにはclassは無い
- 型にメソッドを定義することで似た仕組みを実現.
func(v Vertex) hoge() {}
でVertex型に新しくhoge関数を追加している - ポインタレシーバを使う事でいわゆるthisで値を操作するようにオブジェクト自体の値を操作できる(ポインタレシーバがインスタンス関数みたいな感じか)
- ポインタレシーバーを使わないとレシーバーがコピーされるのでパフォーマンス的によくない。← staticと違う部分
- インターフェースの実装は必要なメソッドを実装するだけ。
implements
で指定する必要はない var i interface{}
でi
にどんな型でも代入できる.v,ok := i.(string)
でアサーションもできる- Stringerインターフェースがある。型を文字列で表現する関数String()を実装する。←javaのtoString()てきな?
- 複数戻り値はerrorか否かの判別自体によく使われる。
io
パッケージでデータストリームの操作image
パッケージで画像関連の操作
Go ツアーを読んだ その1
ツアーを参考に、なるほどと思った点を列挙しました。
ツアー
https://go-tour-jp.appspot.com/welcome/1
basic より
- gofmtコマンドでコードの整形ができる
- 関数は複数の戻り値を返せる
- 関数の戻り値の変数名を関数宣言時にできる(何が便利かはイメージつかない)
- 変数宣言は varを使う。型は後置。
var i int
- 複数の初期化はこんな感じでできる
var i , j int = 3 , 5
- 関数内では := を使うことでvar宣言の代わりができる.
hoge := "HOGE"
- 初期化なしの宣言の場合はゼロ値(型によって異なるデフォルト値)で初期化される
- 定数は
const
で宣言
flowcontrol より
- for 初期化;条件;後処理 {}
- PHPなどの
while(true){}
はfor true {}
で記述 - 無限ループは
for {}
- switchはcaseの最後で自動でbreakする。breakしたくない場合はfallthroughを記述
- deferステートメントで、関数内の特定の部分を関数の実行終了後に実行できる
- 複数のdeferステートメントがある場合はLIFOで実行される
moretypes より
- Goはポインタを扱える.ポインタは変数のメモリアドレスを指す
- ポインタのゼロ値はnil
- *オペレーターはポインタの先の変数を指す
- &オペレーターは変数のポインタを指す
- ポインタ経由で変数の値を操作することをdereferencing や indirecting と呼ぶ
- 構造体あり、要素へのアクセスはドット
- 配列の長さは型の一部
var a [10]int
- 配列のスライスは a[1:4] こんな風に書ける 今日はここまで。
Effective Go
初めてのGo Lang インストールと動作確認
Goでちょっとしたサーバーサイドのプログラムを書こうと思っています。Goを選んだ理由は、やったこと無い言語が良かったからです。 Ruby や Swift と悩みましたが、せっかくなので今までやってきたことと一番遠いものを選びました。
ダウンロードとインストール
- ダウンロードページからMac用のバイナリをダウンロード
- ぽちぽち進めてインストール
- パスの設定
.bash_profile
にexport PATH=$PATH:/usr/local/go/bin
を追加 - 確認 whichでgoのパスが表示されていればOK
$ which go /usr/local/go/bin/go
GOPATHの設定とHello World
ワークスペースとして使用するディレクトリをGOPATHと呼ぶ
.bash_profile
に export GOPATH=$HOME/go_work
を追加.
go_work
ディレクトリは自分で作成する
確認
# 必要なディレクトリ作成 mkdir -p $GOPATH/src/github.com/user/hello cd $GOPATH # プログラム作成 echo 'package main import "fmt" func main() { fmt.Printf("hello, world\n") }' > $GOPATH/src/github.com/user/hello/hello.go # コンパイル go install github.com/user/hello
binディレクトリが生成されていて以下のコマンドが動作すればOK
$GOPATH/bin/hello # hello worldと表示される
参考
wordpress テーマの翻訳・日本語化
wordpressでメディアを作っていると、自分でゼロからデザインするのは大変だから、既存のテーマのお世話になることが多いと思います。 しかし、既存のテーマでも自分の思い通りのものは少ないので、参考にしながらカスタマイズしていく必要があります。
今回はテーマの日本語化をしてみました。
辞書ファイルの場所(potファイル)
辞書ファイルの場所はwp-content/themes/<テーマ名>/languages/<テーマ名>.pot
にあります。
日本語化の方法
一見するとこのpotファイルを以下のように編集すると、翻訳されるのではと思い試してみましたが、上手く行きませんでした。
# @ _s msgid "Continue Reading" msgstr "日本語にする"
確かにこの方法でうまくいくと結局1言語にしか対応できなくなってしまいます。
日本語化する方法は幾つかあるようですが、今回はPoeditという専用のツールを使用しました。
- ツールをダウンロードし、起動
- 新規カタログ
- <テーマ名>.podを選択
- 翻訳したい言語を選択。今回は日本語
- msgid毎に対応した日本語を入力。(サジェストのようなものもあります)
- 保存
保存すると ja.po と ja.moというファイルが出来ます。ja.poはpotファイルと同じ形式です。 ja.moはja.poがコンパイルされたようなファイルで、おそらく実際プログラムからはこちらが参照されるとおもます。 両ファイルをlanguagesディレクトリ以下に保存して、ページをリロードすると、日本語化されています。
Localization | Theme Developer Handbook | WordPress Developer Resources
雑感
全て自分でプログラムを組むのではなく、使い古されたソフトの設計や便利さを上手く学んで行きたいですね。