SiNBLOG

140文字に入らないことを、極稀に書くBlog

gae/gを動かす

今日は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