今日はAppengine上で動く第3の言語であるGoについてです
僕がgae/goしたい理由は以下の3つです
spin-upがきちがいじみて早い
実行速度そのものも早い (並行処理を言語レベルでサポートしてくれている)
Javaより書くのが楽しそう
以上のような理由から、gae/g楽しそうだなーと思ってます
experimental早く外れないかなー
gae/gを動かすには、goをインストール必要があります
インストール方法は公式を見ても良いし
日本語なら、Go言語のインストール - golang.jpを見るのも良いと思います
また、ymotongpoo / goenv — Bitbucketを使うのも良いかも
※gae/gのSDKの中にgoがあるから、go installしなくても良いんじゃないかと教えてもらいました
さて、goがインストールできたら、gae/gをSDKをdownloadします
Download and Install the SDK for App Engine | App Engine Documentation | Google Cloud
僕の環境はMacなので、go_appengine_sdk_darwin_amd64-1.7.7.zipをdownloadしました
こいつを適当な場所に解凍します
僕の環境では、以下に解凍しました
~/bin/google_appengine
それでは、適当な何かを作っていきます
http://www.amazon.co.jp/gp/product/4798031801?ie=UTF8&camp=1207&creative=8411&creativeASIN=4798031801&linkCode=shr&tag=sinmetal-22&qid=1365943307&sr=8-1&keywords=go+appengine
今回は横山さんの本に出てくる簡単なguest bookを作って見ることにしました
しかし、それには試練があります
本が出た後に、golangとgaeのversionが上がっていて、サンプルがそのままでは動かない・・・!
ということで、version上がって変わった辺りを適当にクリアしながら、進んでいきます
まず、作業用のディレクトリを作ります
~/bin/google_appengine/apps/gae-g-book
このディレクトリの中にファイルを作っていきます
最初にAppengineの設定ファイルであるapp.yamlを書きます
app.yaml
application: gae-g-book
version: 2013-01-27
runtime: go
api_version: go1
handlers:
- url: /style\.css
static_files: html/style.css
upload: html/style.css
- url: /.*
script: _go_app
gae/pをしている人には、お馴染みにファイルなのかも
僕はgae/jしかしたことがないので、よく分からないけど
gae/jでは、appengine-web.xmlに相当するファイルです
このままでは、よく分からないので、解説を入れてみました。
application: gae-g-book // application名
version: 2013-01-27 // appengine にデプロイする時に使うversion
runtime: go // go言語を使うので、go
api_version: go1 // go version 1を使うので、go1
handlers: // cssファイルをstatic fileとして登録している
- url: /style\.css
static_files: html/style.css
upload: html/style.css
- url: /.* // 全部のURLをgoで処理する
script: _go_app
こんな感じです
そして、実際にリクエストが来た時の処理が以下
main.go
package process
import (
"appengine"
"appengine/datastore"
"fmt"
"log"
"net/http"
"text/template"
"time"
)
// Datastoreにぶっこむために使う
type Guest struct {
Name string
Date time.Time
}
// 画面表示用に使う
type Guest_View struct {
Name string
Date string
}
// リクエストが来た時のルーティング
func init() {
http.HandleFunc("/", handler)
http.HandleFunc("/write", write)
http.HandleFunc("/list", list)
}
const inputForm = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>名前の登録</title>
</head>
<body>
<form method="POST" action="write">
<label>お名前<input type="text" name="name" /></label>
<input type="submit">
</form>
</body>
</html>
`
const guestTemplateHTML = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登録者リスト</title>
</head>
<body>
<table border="1">
<tr>
<th>名前</th>
<th>登録日時</th>
</tr>
{{range .}}
<tr>
{{if .Name}}
<td>{{.Name|html}}</td>
{{else}}
<td>名無し</td>
{{end}}
{{if .Date}}
<td>{{.Date|html}}</td>
{{else}}
<td> - </td>
{{end}}
</tr>
{{end}}
</table>
</body>
</html>
`
var guestTemplate = template.Must(template.New("guest").Parse(guestTemplateHTML))
// htmlをそのままResponseにぶっこむ
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s", inputForm)
}
// submit された値を、Datastoreに入れる
func write(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusNotFound)
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "Not Found")
return
}
c := appengine.NewContext(r)
// Datastoreへの書き込み
var g Guest
g.Name = r.FormValue("name")
g.Date = time.Now()
if _, err := datastore.Put(c, datastore.NewIncompleteKey(c, "Guest", nil), &g); err != nil {
http.Error(w, "Internal Server Error : "+err.Error(), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusFound)
}
// Datastoreに入っている値の一覧を表示
func list(w http.ResponseWriter, r *http.Request) {
c := appengine.NewContext(r)
q := datastore.NewQuery("Guest").Order("Date")
count, err := q.Count(c)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
guests := make([]Guest, 0, count)
if _, err := q.GetAll(c, &guests); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("guests len=%d", len(guests))
guest_views := make([]Guest_View, count)
for pos, guest := range guests {
guest_views[pos].Name = fmt.Sprintf("%s", guest.Name)
localTime := guest.Date.Format("2006/01/02 15:04:05")
guest_views[pos].Date = localTime
}
if err := guestTemplate.Execute(w, guest_views); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
簡単ですが、こんな感じです
こいつを、dev serverで動かしてみます
cd ~/bin/google_appengine/apps
dev_appserver.py gae-g-book
これで、dev serverが動きます
INFO 2013-04-14 13:49:51,111 dispatcher.py:150] Starting server "default" running at: http://localhost:8080
INFO 2013-04-14 13:49:51,113 admin_server.py:117] Starting admin server at: http://localhost:8000
上記のようなメッセージが表示されて、dev server と admin serverのURLが分かります
因みに僕が2月ぐらいに試した時は、admin serverはhttp://localhost:8080/_ah/adminとかだった気がしたけど、変わった?
以上で、今回おしまいです
細かいところの説明は、飛ばしちゃったりしているので、分からないことがあったらコメントかtwitterで聞いちゃってください
僕もまだgae/gは触り始めたばかりですが、面白そうなので、これからも触っていきたいと思います
UnitTestはどうすれば良いんだ?とか、mavenみたいなのあるのかな?とか、まだまだ疑問はありますが、少しずつ・・・
今回のソースはGithubに上げてあります
GitHub - sinmetal/gae-g-book