Gojiとmgoを使って1時間くらいで超簡易API Serverを作ったのでメモ。
動機はGolangを最近書いてなかったので書きたくなった…くらいしかないです。
環境は Ubuntu 14.04で Golang 1.4を入れました。
$ wget https://storage.googleapis.com/golang/go1.4.linux-amd64.tar.gz
$ sudo tar zxvf ~/go1.4.linux-amd64.tar.gz -C /usr/local
$ vim ~/.bashrc
export GOROOT=/usr/local/go
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
$ go version
go version go1.4 linux/amd64
Goji
Gojiは A web microframework で, 下記のような特徴があります。
- net/http との互換性
- SinatraスタイルなURLパターンと正規表現パターンに加えて, カスタムパターンを定義可能
- ミドルウェア機構
- Einhorn, systemdをサポート
- グレースフルシャットダウン (Einhornを使うと zero-downtime graceful reload)
- WAFの中でも高速な部類 (Go HTTP request router and web framework benchmark)
Gojiのインストールです。
$ go get github.com/zenazn/goji
公式に書かれている Hello に URLパラメータを繋げて返す例。
default portは 8000 ですが, flag.Set(“bind”, “8080”) のようにflagをsetして変更できます。
package main
import (
"fmt"
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
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()
}
mgo
mgoは Golang向けの MongoDB driver で, 下記のような特徴があります。
- Clusterの発見/通信機能
- master serverの変化を検知して, 自動的に Failover
- 柔軟なシリアライゼーション (marshall / unmarshall)
- 同期インターフェイスと並行性
- GridFSのサポート
mgoのインストールです。
$ go get gopkg.in/mgo.v2
公式に書かれている MongoDBに接続し insertと findOneを実行する例です。
package main
import (
"fmt"
"log"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Person struct {
Name string
Phone string
}
func main() {
session, err := mgo.Dial("server1.example.com,server2.example.com")
if err != nil {
panic(err)
}
defer session.Close()
// Optional. Switch the session to a monotonic behavior.
session.SetMode(mgo.Monotonic, true)
c := session.DB("test").C("people")
err = c.Insert(&Person{"Ale", "+55 53 8116 9639"},
&Person{"Cla", "+55 53 8402 8510"})
if err != nil {
log.Fatal(err)
}
result := Person{}
err = c.Find(bson.M{"name": "Ale"}).One(&result)
if err != nil {
log.Fatal(err)
}
fmt.Println("Phone:", result.Phone)
}
おわりに
全体のコードとしては下記で, 定期的に Web scrapingしている複数のマシンで成否や失敗理由などの結果を jsonで返すAPI Serverです。
色々足りてないが趣味コードなので, これでも事足りてしまっている…
package main
import (
"encoding/json"
"flag"
"log"
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type ErrorRes struct {
Status int `json:"status-code"`
Message string `json:"message"`
}
type Log struct {
ID bson.ObjectId `bson:"_id"`
Account string `bson:"account"`
IsSucceeded bool `bson:"isSucceeded"`
Site string `bson:"site"`
Date string `bson:"date"`
Message []string `bson:"message"`
}
func findAll() (r []Log) {
session, err := mgo.Dial("mongodb://localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
con := session.DB("db-name").C("log")
err = con.Find(bson.M{}).All(&r)
if err != nil {
log.Fatal(err)
}
return r
}
func getLog(c web.C, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
if c.URLParams["APPID"] != "[Your APP ID]" {
encoder.Encode(&ErrorRes{401, "Incorrect App ID"})
return
}
result := findAll()
encoder.Encode(result)
}
func main() {
flag.Set("bind", ":8888")
goji.Get("/log/:APPID", getLog)
goji.Get("/logs/:APPID", http.RedirectHandler("/log/:APPID", 301))
goji.Serve()
}
Golangの生産性の高さと性能の両立を目指すアプローチにはやはり魅力を感じます。