google app engineでウェブアプリケーションを
とりあえず動かしてみたいときに行う場合。
アプリケーションを動かす
まずはアプリケーションを動かすところまで。
SDKのインストール
デプロイやローカルで動かすためにSDKをインストールする。
Google App Engine SDK
brew install go-app-engine-64 # 32bitの場合はgo-app-engine-32
configを置く
configファイルとなるapp.yaml
を置く
application: gaesample
version: 1
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
アプリケーションは、プロジェクトidを入れる。
https://console.developers.google.com/project から作ったりする。
goファイルを置く
package gaesample
import (
"fmt"
"net/http"
)
func init() {
http.HandleFunc("/", handler)
}
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s", "Hello World")
}
http.ListenAndServe()
を書く必要がない。
ローカルで実行
goapp serve
# アクセスできるようになる
$ curl http://localhost:8080
Hello World
deploy
goapp deploy
で、アプリケーションをデプロイできる。
Routing
http.HandleFunc()
を使って、設定もしていけるが、
Gojiを使って、ルーティングできるようにする。
go get github.com/zenazn/goji
import (
"fmt"
"net/http"
"github.com/zenazn/goji"
"github.com/zenazn/goji/web"
)
func init() {
http.Handle("/", goji.DefaultMux)
goji.Get("/", handler)
goji.Get("/user", handler2)
goji.Get("/user/:name", handler2)
}
func handler(c web.C, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s", "/")
}
func handler2(c web.C, w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s/%v", "/user", c.URLParams["name"])
}
結果
$ curl http://localhost:8080
/
$ curl http://localhost:8080/user
/user/
$ curl http://localhost:8080/user/123
/user/123
View
html/template
を使った場合,
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
{{ template "content" . }}
</body>
</html>
{{ define "content" }}
<p>Main Page {{ .Name }}</p>
{{ end }}
func init() {
http.Handle("/", goji.DefaultMux)
goji.Get("/", homeHandler)
}
func render(v string, w io.Writer, data map[string]interface{}) {
tmpl := template.Must(template.ParseFiles("views/layout.html", v))
tmpl.Execute(w, data)
}
func homeHandler(c web.C, w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{
"Name": "home",
}
render("views/home.html", w, data)
}
ファイル構成
$ tree .
.
├── app.yaml
├── main.go
└── views
├── home.html
└── layout.html
goapp serve
したあとにアクセスすれば、きちんとhtmlが返ってくる。
% curl http://localhost:8080/
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<body>
<p>Main Page home</p>
</body>
</html>
Debug log
appengine.context
にデバッグ用の関数が一通り用意されている。
func (c *context) Debugf(format string, args ...interface{}) { c.logf("DEBUG", format, args...) }
func (c *context) Infof(format string, args ...interface{}) { c.logf("INFO", format, args...) }
func (c *context) Warningf(format string, args ...interface{}) { c.logf("WARNING", format, args...) }
func (c *context) Errorf(format string, args ...interface{}) { c.logf("ERROR", format, args...) }
func (c *context) Criticalf(format string, args ...interface{}) { c.logf("CRITICAL", format, args...) }
Datastore API
appengine/datastore
を使ってデータを保存したり、取り出したりする場合。
modelの定義
ひとまずどんなデータを保存するかを決める
package gaesample
import "time"
type User struct {
Name string
Role string
HireDate time.Time
}
データの保存
appengine/datastore
を使って、データを保存できる。
import (
// ...省略
"appengine"
"appengine/datastore"
)
// ...
func homeHandler(c web.C, w http.ResponseWriter, r *http.Request) {
context := appengine.NewContext(r)
el := User{
Name: "Joe",
Role: "Manager",
HireDate: time.Now(),
}
key, err := datastore.Put(context, datastore.NewIncompleteKey(context, "users", nil), &el)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// ...
}
詳細は https://cloud.google.com/appengine/docs/go/datastore/
データの取り出し
func usersIndexHandler(c web.C, w http.ResponseWriter, r *http.Request) {
context := appengine.NewContext(r)
q := datastore.NewQuery("users").Limit(10)
users := make(map[string]User, 0)
for t := q.Run(context); ; {
var user User
key, err := t.Next(&user)
if err == datastore.Done {
break
}
if err != nil {
http.Error(w, err.Error(), 500)
return
}
users[key.String()] = user
}
data := map[string]interface{}{
"Name": "users/index",
"Users": users,
}
render("views/users/index.html", w, data)
}
{{define "content"}}
<h2>Users List</h2>
<table class="table">
<thead>
<tr>
<td>Key<td>
<td>Name<td>
<td>Role<td>
</tr>
</thead>
{{range $index, $group := .Users}}
<tr>
<td>{{$index}}<td>
<td>{{$group.Name}}<td>
<td>{{$group.Role}}<td>
</tr>
{{end}}
</table>
{{end}}
データを入れてアクセスすれば、それっぽく表示される
テスト
Unit Testを書く場合はappengine/aetest を使う。
(app engine testでaetestっぽい)
package gaesample
import (
"testing"
"time"
"appengine/aetest"
"appengine/datastore"
)
func TestUserIndexHandler(t *testing.T) {
c, err := aetest.NewContext(nil)
defer c.Close()
if err != nil {
t.Fatal(err)
}
key := datastore.NewKey(c, "users", "stringId", 0, nil)
el := User{
Name: "Joe",
Role: "Manager",
HireDate: time.Now(),
}
if _, err := datastore.Put(c, key, &el); err != nil {
t.Fatal(err)
}
user := User{}
if err := datastore.Get(c, key, &user); err != nil {
t.Fatal(err)
}
if user.Name != "Joe" {
t.Errorf("Not equal user.Name")
}
if user.Role != "Manager" {
t.Errorf("Not equal user.Name")
}
}
ローカルでのテスト実行の仕方は
$ goapp test