GAE(Google App Engine) で Golang 使った REST API(echo 編) #golang


以前の記事（[GAE(Google App Engine) で Golang 使った REST API](https://qiita.com/ynozue/items/22967c3c8e12129d7527)）を作りましたが、今回は echo (Golang 用のフレームワーク) を使った REST API を作ってみようと思います。

Google App Engine 用のサンプルは[こちら](https://echo.labstack.com/cookbook/google-app-engine)を参考にしました。

# プログラム構成
基本的にはサンプルプログラム通りですが、root ディレクトリに通常の go コマンドからの起動用のコード（app-standalone.go）を置いて本体のプログラムは gae_echo ディレクトリ直下に配置しています。

```sh
$ tree
├── README.md
├── app-standalone.go
└─── gae_echo
    ├── app-engine.go
    ├── app-engine.yaml
    ├── app.go
    └── users.go
```

# ソースコード
通常の go コマンドからの起動用のプログラムと echo インスタンス生成用のコードになります。

```go:app.go
package gae_echo

// reference our echo instance and create it early
var e = createMux()

func Start() {
	e.Logger.Fatal(e.Start(":8080"))
}
```

通常の go コマンドからの起動用のプログラムと echo インスタンス生成用のコードになります。

```go:app-engine.go
package gae_echo

import (
	"net/http"

	"github.com/labstack/echo"
)

func createMux() *echo.Echo {
	e := echo.New()
	// note: we don't need to provide the middleware or static handlers, that's taken care of by the platform
	// app engine has it's own "main" wrapper - we just need to hook echo into the default handler
	http.Handle("/", e)
	return e
}
```

ユーザー情報（IDと名前だけだけど、、、）を登録/参照する API の本体で、データをメモリ上に保存します。

```go:users.go
package gae_echo

import (
	"net/http"

	"github.com/labstack/echo"
	"github.com/labstack/echo/middleware"
)

type (
	user struct {
		ID   string `json:"id"`
		Name string `json:"name"`
	}
)

var (
	users map[string]user
)

func init() {
	users = map[string]user{
		"1": user{
			ID:   "1",
			Name: "Wreck-It Ralph",
		},
	}

	// hook into the echo instance to create an endpoint group
	// and add specific middleware to it plus handlers
	g := e.Group("/users")
	g.Use(middleware.CORS())

	g.POST("", createUser)
	g.GET("", getUsers)
	g.GET("/:id", getUser)
}

func createUser(c echo.Context) error {
	u := new(user)
	if err := c.Bind(u); err != nil {
		return err
	}
	users[u.ID] = *u
	return c.JSON(http.StatusCreated, u)
}

func getUsers(c echo.Context) error {
	return c.JSON(http.StatusOK, users)
}

func getUser(c echo.Context) error {
	return c.JSON(http.StatusOK, users[c.Param("id")])
}
```
サンプルの yaml から必要な部分だけ抜き出しています。

```app-engine.yaml
application: hello-gae-go
version: 1
runtime: go
api_version: go1.8

handlers:
- url: /.*
  script: _go_app
```

サンプルはうまく動かなかったので、ちょっと変更しています。

```go:app-standalone.go
// +build !appengine,!appenginevm

package main

import (
	"github.com/ynozue/hellow-gae-go/gae_echo"
)

func main() {
	gae_echo.Start()
}
```

# 動作確認
## 起動
```sh
$ goapp serve gae_echo/app-engine.yaml

INFO     2017-10-15 13:47:45,383 devappserver2.py:115] Skipping SDK update check.
INFO     2017-10-15 13:47:45,528 api_server.py:299] Starting API server at: http://localhost:57500
INFO     2017-10-15 13:47:45,539 dispatcher.py:224] Starting module "default" running at: http://localhost:8080
INFO     2017-10-15 13:47:45,541 admin_server.py:116] Starting admin server at: http://localhost:8000
```

## curl での動作確認
### 一覧参照
```sh
$ curl http://localhost:8080/users | python -m json.tool

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    74  100    74    0     0   3755      0 --:--:-- --:--:-- --:--:--  3894
{
    "1": {
        "id": "1",
        "name": "Wreck-It Ralph"
    }
```
### 登録
```sh
$ curl -X POST \
 -H 'Content-Type: application/json' \
 -d '{"id":"2","name":"Joe Smith"}' \
 http://localhost:8080/users | python -m json.tool

    % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    58  100    29  100    29    896    896 --:--:-- --:--:-- --:--:--   906
{
    "id": "2",
    "name": "Joe Smith"
}
```
### 一覧参照（登録後）
```sh
$ curl http://localhost:8080/users | python -m json.tool
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    74  100    74    0     0   3755      0 --:--:-- --:--:-- --:--:--  3894
{
    "1": {
        "id": "1",
        "name": "Wreck-It Ralph"
    },
    "2": {
        "id": "2",
        "name": "Joe Smith"
    }
}
```
### 詳細参照
```sh
$ curl http://localhost:8080/users/2 | python -m json.tool

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    29  100    29    0     0   2759      0 --:--:-- --:--:-- --:--:--  2900
{
    "id": "2",
    "name": "Joe Smith"
}
```

## まとめ
net/http パッケージを利用するよりかは POST データのパースや URI に対応するプログラムの切り分けなどが簡単にできました。
echo には色々な機能が用意されてるみたいなので、サンプルを試しながら調べていこうかと思います。

# Appendix
* [ソースコード](https://github.com/ynozue/hello-gae-go/tree/echo)