search
LoginSignup
100

More than 5 years have passed since last update.

posted at

updated at

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

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

参考

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
100