GojiによるWebアプリケーションの作成
ここからはいよいよ、Gojiフレームワーク(以下、Goji)を使ってWeb APIを作成していきます。
net/http
Gojiについて解説する前に、Goの標準パッケージである「net/http
」に触れておきましょう。Gojiは net/http
パッケージに対する薄いラッパーとして構成されています。そのため、net/http
パッケージについての知識も必要になるのです。
次のコードは、net/http
パッケージだけを使用して作成した最小限のWebアプリケーションです。
package main import ( "io" "net/http" ) func handler(w http.ResponseWriter, req *http.Request) { io.WriteString(w, "Hello, World!¥n") } func main() { http.HandleFunc("/hello", handler) http.ListenAndServe(":8080", nil) }
Eclipseで実行させてWebサーバが起動したら、Webブラウザから「http://localhost:8080/hello」へアクセスしてみます。「Hello, World!」とWebブラウザ上に表示されれば成功です。
このようにGoは、本体にWebアプリケーションを作成するための枠組みを備えています。Gojiは net/http
パッケージが提供するHTTPサーバ機能の基本実装へ、有用な機能(柔軟なルーティング、ロギング、ミドルウェアなど)を追加するという仕組みで構築されています。
Gojiの導入
Gojiのインストールには、Gocode同様に go get
コマンドを利用します。
C:¥> go get -u github.com/zenazn/goji
go get
コマンドの実行に成功したら、Eclipseのプロジェクト上から環境変数 GOPATH
に含まれるパッケージの内容を確認してみます。
環境変数 GOPATH
に、goji
パッケージが含まれていることが確認できました。それではさっそく使ってみましょう。まずは、下記のサンプルコードを動かしてみます。
package main import ( "fmt" "net/http" "github.com/zenazn/goji" "github.com/zenazn/goji/web" ) // 「Hello, World!」を出力するハンドラ func hello(c web.C, w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, %s!", c.URLParams["name"]) } func main() { // ルーティングの定義とサーバの起動 goji.Get("/hello/:name", hello) goji.Serve() }
コードを保存してEclipseで実行します。net/http
パッケージ使用バージョンの動作とは異なり、コンソールにログが表示されます。Webブラウザを開いて「http://localhost:8000/hello/goji」にアクセスしてみましょう。
「/hello/:name」というルーティングの「:name」部分をパラメータとして取り出して表示に反映させていることが分かります。また、Eclipseのコンソールには次のように、アクセスログとその処理ステータスが出力されています。
ここから少しコードの詳細を追います。まずはWebアプリケーションの処理の中心となるハンドラの定義についてです。
func handler(c web.C, w http.ResponseWriter, r *http.Request) { // http.Requestはリクエスト情報を保持する fmt.Fprintf(w, "URL=%s, Host=%s, Method=%s, RemoteAddr=%s", r.URL, r.Host, r.Method, r.RemoteAddr) // web.Cを使用してURLのパラメータを取り出せる fmt.Fprintf(w, "id=%s, name=%s", c.URLParams["id"], c.URLParams["name"]) }
net/httpで定義するハンドラは http.ResponseWriter
型と *http.Request
型の2つの引数をとりますが、Gojiのハンドラには web.C
型の引数が追加されています。この構造体により、URLに含まれるパラメータを簡単に取り出すことができます。
このハンドラが行うべきことは、http.ResponseWriter
へのレスポンス出力です。上記のコード例では、Content-Type
が "text/plain
" であるテキストデータを出力しています。単純なHTMLを出力するのであれば、Goのヒアドキュメントを使って次のように書くこともできます。
func handler(c web.C, w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `<!DOCTYPE html> <html lang="ja"> <head></head> <body> <h1>タイトル</h1> </body> </html>`) }
お気づきかもしれませんが、Gojiはテンプレートファイルを基にしたビュー生成の機能を一切備えていません。ビューのためのテンプレートエンジンが必要であれば、Goの標準パッケージである「text/template
」や「html/template
」などを組み込んで使用するか、あるいはビュー機能を提供してくれる外部パッケージを導入するなどの作業が必要になります。
ハンドラを作成したら、次にどのようなURLパスから処理が呼び出されるかを定義します。Gojiは net/http
パッケージよりも柔軟性が高いルーティング定義方法を備えています。
// GETメソッドで/hello/:name にマッチする場合のルーティング goji.Get("/hello/:name", xxxxx) // 複数のURLパラメータも定義できる goji.Get("/articles/:year/:month/:day", xxxxx) // Postメソッドの場合 goji.Post("/articles/post", xxxxx) // * を使ったルーティング(後続のパスはハンドラー内から c.URLParams["*"] で取得できる) goji.Get("/prefix/*", xxxxx) // 正規表現(名前付きグルーピングで /pages/100 の場合はパラメータ id=100 が取得できる) goji.Get(regexp.MustCompile(`^/pages/(?P<id>¥d+)$`), xxxxx) // httpモジュールで定義済みのNotFoundハンドラを使う goji.Get("/not_found", http.NotFound)
このようにハンドラを書いて、それをルーティングに定義するという流れがGojiを利用したWebアプリケーション開発の中心的な作業になります。ルーティング定義が終わったら goji.Serve()
を呼び出すことでWebサーバの実行が開始されます。
goji.Serve()
GojiのWebサーバは8000番ポートで起動しますが、このポート番号を変えるインターフェースは goji
には存在しません。細かい仕組みは省略しますが goji.Serve()
を使用してポート番号やバインドするIPアドレスを変更する場合は、ビルドが完了した実行ファイルに -bind
オプションを指定して実行する必要があります。
仮に実行ファイル名が websrv.exe
で、Webサーバを8080番ポートで起動する場合は下記のように実行します。
C:¥> websrv.exe -bind :8080
Gojiにはここまでで紹介した以外にも重要な機能があります。より深く知りたい場合は「zenazn/goji」の「exapmle」フォルダ以下のサンプルが役に立ちます。
JSON操作の基本
近年のWeb APIにおいて、クライアント・サーバ間での情報のやり取りには「JSON」が事実上の標準として使用されます。ここでは、GoでJSONフォーマットを取り扱う方法について解説します。
json.Marshal
GoでJSONフォーマットを操作するには、標準パッケージ「encoding/json
」を使用します。次に示すコードでは「ユーザID」「名前」「メールアドレス」の3つのフィールドを持つ User
構造体を作成し、その内容をJSONフォーマットへ変換しています。
package main import ( "fmt" "encoding/json" ) type User struct { Id int Name string Email string } func main() { user := &User{1, "Yamada", "yamada@example.com"} bs, _ := json.Marshal(user) fmt.Printf("%s", bs) } // 出力結果 // {“Id”:1,”Name”:”Yamada”,”Email”:”yamada@example.com”}
構造体からJSONフォーマットへ変換するといっても、単純な構造体を json.Marshal()
に渡しているだけです。簡単ですね(コードを簡略化するため、json.Marshal()
のエラー処理は省略しています)。
上記の例では、構造体のフィールド名がそのままJSONフォーマットの連想配列のキー値にマッピングされており、便利といえば便利なのですがマッピング先を個別に指定できないのは困りものです。そこで User
構造体に「タグ」を追加してみます。
type User struct { Id int `json:"user_id"` Name string `json:"user_name"` Email string `json:"user_email"` }
Goでは、構造体の各フィールドに対して任意の「タグ」を追加することができます。encoding/json
パッケージは、構造体に付加された「 json
」から始まるタグの内容を、連想配列のキー値としてマッピングします。もう一度プログラムを実行して、出力結果がどのように変わるか確かめてみましょう。
{"user_id":1,"user_name":"Yamada","user_email":"yamada@example.com"}
Id
フィールドは user_id
へ、その他のフィールドも指定した内容でマッピングされました。
json.Unmarshal
Goの構造体からJSONフォーマットへの変換が成功したので、次は逆パターンを試してみましょう。
package main import ( "fmt" "encoding/json" ) type User struct { Id int `json:"user_id"` Name string `json:"user_name"` Email string `json:"user_email"` } func main() { var u User str := `{"user_id":1,"user_name":"Yamada","user_email":"yamada@example.com"}` json.Unmarshal([]byte(str), &u) fmt.Printf("%#v", u) // "%#v"を使用すると構造体のフィールド名も出力される } // 出力結果 // main.User{Id:1, Name:"Yamada", Email:"yamada@example.com"}
json.Unmarshal()
に []byte
型に変換したJSONフォーマットを与えると、第2引数に指定したポインタが指す構造体に情報を書き出すことができます。json.Marshal()
と同様に非常にシンプルなコードで書けることが分かりました。