Google App EngineでGoのウェブアプリケーションをまず動かしてみる

  • 94
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

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ファイルを置く

main.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
main.go
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を使った場合,

views/layout.html
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  </head>
  <body>
    {{ template "content" . }}
  </body>
</html>
views/home.html
{{ define "content" }}
<p>Main Page {{ .Name }}</p>
{{ end }}
main.go
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の定義

ひとまずどんなデータを保存するかを決める

models.go
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)
}
views/users/index.html
{{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}}

データを入れてアクセスすれば、それっぽく表示される

image

テスト

Unit Testを書く場合はappengine/aetest を使う。
(app engine testでaetestっぽい)

main_test
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

参考