emahiro/b.log

日々の勉強の記録とか育児の記録とか。

app.yamlでmainを指定して静的ファイルをサーブする

Overview

Google App Engine 2ndGenにてapp.yamlの設定でmain property を指定して、mainで指定した path をプロジェクトの root として静的ファイル( static や template 配下のファイル)が正常に読み込めるかどうか調べました。

以前も静的ファイルのサーブに関連したエントリは書いたのですが、今回はそこからアップデートのあった内容も含めて記載してます。

ema-hiro.hatenablog.com

main propertyについて

公式ドキュメントには以下のように記載されています。

Optional. The path or fully qualified package name of the main package.

You must declare the path to the main package if your package main is not in the same directory as your app.yaml. The main element supports file paths relative to app.yaml or full package names.

cf. app.yaml Configuration File  |  App Engine standard environment for Go 1.11 docs  |  Google Cloud

  1. ファイルの相対パスはmain propertyで指定されたpathの相対パスになります。
  2. main.goがapp.yamlと同階層にないとき、main packageのパスを定義します。

app.yamlの設定

以下のようなディレクトリ構成に置いて

├── app
│   └── cmd
│       └── main.go
├── app.yaml
└── go.mod

app.yaml で main property に ./app/cmd を指定することで、このアプリケーションにおける main package の path を設定します。
これにより app.yaml と main.go が同階層になくても main で指定されたディレクトリの main package が app.yaml と同階層にあるものとして読み込むことができます。

sample app.yaml

runtime: go111
service: gae-go111-app
main: ./app/cmd 

GAE 2nd Generation 以降はデプロイした時に CloudBuild でアプリケーションのビルドが行われるので、CloudBuild の実行ログから main package を ./app/cmd に変更してるログを確認することができます。

Building /tmp/stagingXXXXXXXXX/srv, with main package at ./app/cmd, saving to /tmp/stagingXXXXXXXXX/usr/local/bin/start

正常に main が再設定されてる場合は上記のようなログが出力されます。

goのコードから静的ファイルを読み込む

実際に go のファイルの中で特定のファイルを開きたい、ようなケースがあった場合も main を指定することにより、main.go が app.yaml と違う階層にあった場合も、app.yaml がある階層をルートとして go のコードから呼ぶことができるようになります。
そのため、app.yaml を配置した階層と同じ階層に特定のファイルを配置して、そのファイルをダイレクトに指定する(相対パス等を考えなくていい)ことでファイルにアクセスすることができます。
具体的には ./src/app/cmd/main.go の内部で以下のような実装をしたとしても、app.yaml そのものが出力されます。

f, err := os.Open("app.yaml")
if err != nil {
    panic(err)
}

io.Writer(os.Stdout, f)

templateを読み込む

go のコードから静的なファイルを読み込む時と同様に app.yaml があるディレクトリと同じ階層に template のディレクトリを作成し go のコードから相対パスなしで直接 template のファイルを指定することで読み込むことができます。

ディレクトリ構成は以下です。

├── app
│   └── cmd
│       └── main.go
├── app.yaml
├── go.mod
├── handler
│   └── index.go
└── templates
    └── index.tmpl

template を呼び出す側は変更ありません。

tmpl, err := template.ParseFiles("templates/index.tmpl")
if err != nil {
    panic(err)
}
if err := tmpl.Execute(w, nil); err != nil {
    panic(err)
}

static ファイルを読み込む

最後に js や css といったファイルのサーブの設定ついてですが、これは元々の設定と変わらず handler property で設定します。
/static/js/index.js というファイルを読みたい場合は、app.yaml の static_dir に static を指定します。

ディレクトリ構成は以下

├── app
│   └── cmd
│       └── main.go
├── app.yaml
├── go.mod
├── handler
│   └── index.go
├── static
│   └── js
│       └── index.js
└── templates
    └── index.tmpl

sample app.yaml

runtime: go111
service: gae-go111-app
main: ./app/cmd
handlers:
  - url: /static
    static_dir: static
    secure: always

sample index.tmpl

{{ define "index.tmpl" }}
    <html lang="ja">
    <head>
        <title>test</title>
    </head>
    <body>
    <p>hello world</p>
    </body>
    <script src="/static/index.js" ></script>
    </html>
{{ end }}

これらを実際に設定してみて、デプロイすると template を読み込んだ時に一緒に js も読み込まれます。

※ localで go run ./PathTo/main.go で起動した場合、static ディレクトリにルーティングされない(appengine を起動しないといけない) ので、デプロイするか、 dev_appserver.py app.yaml で appengine を起動させてみての確認が必須です。

まとめ

app.yaml の main を指定することで 1st Generation の時のようなプロジェクト構成でも静的なファイルをサーブして読み込むことが可能です。

ref

コードはこちらに置いておきました。

github.com