Help us understand the problem. What is going on with this article?

GAE/Go+glide的な構成での環境構築 ~ローカルサーバー立ち上げまで~

More than 1 year has passed since last update.

GAE/Go+glide的な構成での環境構築とローカルサーバの起動までの手順備忘録

プロジェクト構成

この解説では、最終的に以下のようなディレクトリ構成になる。

$GOPATH
  ├── app
  │   ├── app.yaml      <= app.yamlはsrcディレクトリの外に配置
  │   └── main.go
  └── src
      ├── glide.yaml    <= glide.yamlはsrc直下
      ├── glide.lock
      ├── {package}     <= 基本的なソースコードはsrc以下のパッケージに
      └── vendor        <= アプリで利用する外部パッケージ

GAE/Goの仕様でapp.yamlが配置されているディレクトリ以下のコードが全てビルドされてしまう。
この仕様のため、vendoringを使用して外部ライブラリを利用しようとすると、ほぼ間違いなくビルドでこけてしまうのでapp.yamlはsrcディレクトリには含めないようにしている。
参考
Google App EngineでGoを動かすときに知っておくべきこと(ソースコード・ビルド編) - Hatena Blog

開発ツール

以下のツールをインストール

必須

その他(あると便利)

環境構築

プロジェクト作成

goenv未使用の場合
  • GOPATH以下にappディレクトリを作成する
  • appディレクトリ内に、app.yamlを作成する
app.yaml
application: {プロジェクト名}
version: 1
runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app
goenvを使用する場合
  • 以下のコマンドでプロジェクトを作成する
goenv -gae -go {$APPENGINE_SDK/goroot} -deps $GOPATH {プロジェクト名}
  • 自動生成されたディレクトリ内に、appディレクトリを作成
  • srcディレクトリ内にapp.yamlファイルが自動生成されているので、appディレクトリに移動する

この時点で以下のような構成になる

${GOPATH}
  ├── activate(goenvでプロジェクトを作成した場合)
  ├── app
  │   └── app.yaml
  └── src
      └── {プロジェクト名}.go(goenvでプロジェクトを作成した場合)

glide初期設定

glideの初期設定など

  • srcディレクトリ内でglide createを実行
  • glide.yamlが自動生成されるので、使用する外部パッケージを記載するか、glide get {パッケージ名}を実行する
glide.yaml
# cloud.google.com/go/storageを使用する場合
# glide getを実行した場合は自動でパッケージが追記される

package: .
import:
  - package: cloud.google.com/go
    subpackages:
      - storage

ここまでで以下のような構成になっているはず(glide.yamlを直接編集した場合はまだglide.lockvendorディレクトリは作成されない)

$GOPATH
  ├── activate(goenvでプロジェクトを作成した場合)
  ├── app
  │   └── app.yaml
  └── src
      ├── glide.yaml
      ├── glide.lock 
      ├── vendor
      └── {プロジェクト名}.go(goenvでプロジェクトを作成した場合)

サンプルコード

ローカルサーバーの起動確認と、glideでの外部ライブラリ管理確認のサンプルコード。
DataStoreを使用して簡単なデータ登録と取得を行うAPIを作成してみる。

  • ディレクトリ構成
$GOPATH
  ├── activate
  ├── app
  │   ├── app.yaml
  │   └── main.go
  └── src
      ├── glide.lock
      ├── glide.yaml
      ├── sample
      │   └── crud.go
      └── vendor
  • app.yaml
app.yaml
application: sample-crud
version: 1
runtime: go
api_version: go1

handlers:
- url: /.*
  script: _go_app
  • glide.yaml
glide.yaml
package: .
import:
- package: golang.org/x/net
  subpackages:
  - context
- package: google.golang.org/appengine
  version: ~1.0.0
  subpackages:
  - datastore
  • ソースコード
app/main.go
package main

import (
    "net/http"
    "sample"
)

func init() {
    http.Handle("/foo", sample.GetCRUDSampleHandler())
}
src/sample/crud.go
package sample

import (
    "encoding/json"
    "fmt"
    "net/http"

    "golang.org/x/net/context"
    "google.golang.org/appengine"
    "google.golang.org/appengine/datastore"
)

const KindFoo = "foo"

type Foo struct {
    Foo string `json:"foo"`
    Bar string `json:"bar"`
}

func GetCRUDSampleHandler() *CRUDSample {
    return new(CRUDSample)
}

type CRUDSample struct {
    ctx context.Context
    w   http.ResponseWriter
    r   *http.Request
}

func (c *CRUDSample) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    c.ctx, c.w, c.r = appengine.NewContext(r), w, r
    switch r.Method {
    case http.MethodGet:
        c.Get()
    case http.MethodPost:
        c.Post()
    default:
        w.WriteHeader(http.StatusNotFound)
        fmt.Fprintln(w, "Not found.")
    }
}

type RespGetFoo struct {
    Id  int64  `json:"id"`
    Foo string `json:"foo"`
    Bar string `json:"bar"`
}

func (c *CRUDSample) Get() {
    var foos []Foo
    keys, err := datastore.NewQuery(KindFoo).GetAll(c.ctx, &foos)
    if err != nil {
        c.responseServerError(err)
        return
    }
    result := make([]RespGetFoo, len(keys))
    for i, f := range foos {
        result[i] = RespGetFoo{keys[i].IntID(), f.Foo, f.Bar}
    }
    c.responseJson(http.StatusOK, result)
}

func (c *CRUDSample) Post() {
    var foo Foo
    err := json.NewDecoder(c.r.Body).Decode(&foo)
    if err != nil {
        c.responseServerError(err)
        return
    }
    key := datastore.NewIncompleteKey(c.ctx, KindFoo, nil)
    newKey, err := datastore.Put(c.ctx, key, &foo)
    if err != nil {
        c.responseServerError(err)
        return
    }
    response := map[string]int64{"id": newKey.IntID()}
    c.responseJson(http.StatusCreated, response)
}

func (c *CRUDSample) responseJson(status int, v interface{}) {
    c.w.WriteHeader(status)
    json.NewEncoder(c.w).Encode(v)
}

func (c *CRUDSample) responseServerError(err error) {
    c.w.WriteHeader(http.StatusInternalServerError)
    fmt.Fprintln(c.w, err)
}

実行

  • 依存ライブラリの取得
    srcディレクトリに移動して、glide upを実行。 プロジェクトや、外部ライブラリの依存関係を解決してvendorディレクトリにぶち込んでくれる。
$ cd src
$ glide up
  • ローカルサーバー起動
    appディレクトリに移動し、goapp serve app.yamlを実行
$ cd app
$ goapp serve app.yaml

注意点
ローカルサーバの起動にはpython2系が必要なため、ローカルサーバの起動前にpythonのバージョンを変更しておくこと。
pyenvなどを使っている場合は

$ cd app
$ pyenv local 2.7.13

とかを実行して、appディレクトリ以下のpythonバージョンのを固定しておくと便利

以上、GAE/Go+glideの環境準備からローカルサーバーの起動までの手順。
今後glideでのパッケージ管理は基本になるかと思うので、手順などや構成などを忘れないように。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした