ほげほげ

プログラミング、英会話、ヨガ、料理などの備忘録など。

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を使ったプロジェクトの例

  • drone Goで書かれたDockerで動くデリバリシステム
  • goruch Goで書かれたプッシュ通知サーバー

Go: Webアプリケーションを作るチュートリアル

wikiを作るチュートリアル をやってみたのでメモ。

GoはPHPみたいに1リクエスト1プロセスでなく、ServletやNodeJSのように1つのプロセスで複数のリクエストを受け付ける方式。

以下の様なことが身につきました。

  • 割りと実践的な内容で、Webの開発でよくある機能がシンプルにまとまっている
  • 簡単な型に関数を設定する方法(Pageのポインタをレシーバーとしたsaveメソッド)
  • エラーの場合はerrorオブジェクトを返すお作法(正常時はnil)
  • 複数戻り値の関数を使う場合に、必要ない戻り値を _ と記述し省略
  • 複数戻り値の返し方
  • テンプレートエンジンも標準パッケージに含まれる
  • テンプレートエンジンのメモリ内キャッシュの実装の仕方
  • 正規表現の基本的な使い方
  • 関数オブジェクトを上手く利用したコーディングの簡略化

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.MutexLock,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
  • 連想配列mapmake関数で作成できる。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

いつか目を通す https://golang.org/doc/effective_go.html

初めてのGo Lang インストールと動作確認

Goでちょっとしたサーバーサイドのプログラムを書こうと思っています。Goを選んだ理由は、やったこと無い言語が良かったからです。 Ruby や Swift と悩みましたが、せっかくなので今までやってきたことと一番遠いものを選びました。

ダウンロードとインストール

  1. ダウンロードページからMac用のバイナリをダウンロード
  2. ぽちぽち進めてインストール
  3. パスの設定 .bash_profileexport PATH=$PATH:/usr/local/go/bin を追加
  4. 確認 whichでgoのパスが表示されていればOK
$ which go
/usr/local/go/bin/go

GOPATHの設定とHello World

ワークスペースとして使用するディレクトリをGOPATHと呼ぶ

.bash_profileexport 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と表示される

参考

公式ページ:Getting Started

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

雑感

全て自分でプログラムを組むのではなく、使い古されたソフトの設計や便利さを上手く学んで行きたいですね。