emahiro/b.log

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

AppEngineで静的ファイルをサーブする設定について

ふとした拍子に忘れてしまうappengineでの静的ファイルをサーブする設定についてのメモです。

内容はほぼ Serving Static Content  |  App Engine standard environment for Go  |  Google Cloud に載ってる内容です。

例えば以下のようなディレクトリ構成を考えてstaticディレクトリとassetsの2つのディレクトリに静的ファイル( jsとかcssとかimgといったファイル )を保持してwebサイト上で使いたいケースを考えます。

GOPATH
├── assets
│   └── index.html
├── src
│   └── app
│       ├── app.yaml
│       └── main.go
└── static
    └── index.html

まずは適当にサーバーを用意します。

package main

import (
    "fmt"
    "log"
    "net/http"
    "os"
)

// App Handle Object
type App struct{}

// Index App Root Handler
func (a *App) Index(w http.ResponseWriter, r *http.Request) {
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("hello world"))
}

func main() {
    app := &App{}
    mux := http.NewServeMux()
    mux.HandleFunc("/", app.Index)

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
        log.Printf("Defaulting to port %s", port)
    }

    log.Printf("Listening on port %s", port)
    if err := http.ListenAndServe(fmt.Sprintf(":%s", port), mux); err != nil {
        panic(err)
    }
}

次にapp.yamlにサーブしたいファイルのディレクトリを指定します。せっかくなのでgo111でやってみます。
go111ではアプリケーションを作成するときのディレクトリ構成が 1.9以下のそれとは異なっているので例えば1.9の環境で以下のようなディレクトリ構成を考えると

GOPATH
├── assets
│   └── index.html
├── app.yaml
└── static
    └── index.html

このケースではにはyamlに定義する handlers の urlディレクティブのstatic_dir(実際サーブしているファイルのpathを指定するproperty)はapp.yamlと同階層にあるので以下のように記載しました。

runtime: go1.9
handlers:
  - url: /static
    static_dir: static
  - url: /assets
    static_dir: assets
  - url: /.*
    script: auto
  

ちなみに 1.9以下で指定するときは 最後の /.* より上部にサーブしたいディレクトリのルーティングの設定を書かないと正確にルーティングされません でした。なぜかは謎です。

go111の環境ではディレクトリ構成が異なります。最初に示した通り、go111ではapp.yamlはmainパッケージと同階層にあることが必要です。
そのため、app.yamlと実際にサーブするディレクトリは1.9以下のように同階層にあるわけではありません。(同階層にあるとした場合)

app.yamlから静的ファイルをサーブするディレクトリを指定するケースにおいてはそのディレクトリは相対パスで指定することになりますので以下のようになります。

runtime: go111
handlers:
  - url: /static
    static_dir: ../../static
    secure: always
  - url: /assets
    static_dir: ../../assets
    secure: always
  - url: /.*
    script: auto
    secure: always

※ go1.9の時にあった /.* より上に静的ファイルをサーブするディレクトリを記載しなくてもgo111のケースではちゃんと動作してくれました。

この状態で実際にgo111の環境でアプリを起動します。

$ dev_appserver.py src/app/app.yaml
$ curl -i -X GET http://localhost:8080/static/index.html
HTTP/1.1 200 OK
Content-length: 41
Content-type: text/html
ETag: "LTgyNzU4ODE4Ng=="
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Cache-Control: public
Server: Development/2.0
Date: Mon, 19 Nov 2018 17:42:50 GMT

<html>
  <p>hello static dir</p>
</html>

$ curl -i -X GET http://localhost:8080/assets/index.html
HTTP/1.1 200 OK
Content-length: 41
Content-type: text/html
ETag: "MTc0MjM0NzkzMg=="
Expires: Fri, 01 Jan 1990 00:00:00 GMT
Cache-Control: public
Server: Development/2.0
Date: Mon, 19 Nov 2018 17:42:48 GMT

<html>
  <p>hello assets dir</p>
</html>

正常に静的ファイルがルーティングされてることが確認できました。
追加で app.yamlの各propertyの意味は app.yaml Configuration File  |  App Engine standard environment for Go 1.11 docs  |  Google Cloud にあるので目を通してみると良いと思います。

ちなみに今回使ったhandlerには以下のように記載してあります。

Optional. A list of URL patterns and descriptions of how they should be handled. App Engine can handle URLs by executing application code, or by serving static files uploaded with the code, such as images, CSS, or JavaScript.

静的ファイルをサーブする時に使うpropertyと記載ありますね。
handlers propertyの要素については app.yaml Configuration File  |  App Engine standard environment for Go 1.11 docs  |  Google Cloud にあるのでこちらも合わせて目を通してみると良いと思います。

app.yamlのhandlers要素の最後に script: auto と書いてますが、これはドキュメントに以下のような静的ファイルをサーブするケースでの記載に則ったものです。

Optional. Specifies that requests to the specific handler should target your app. The only accepted value for the script element is auto because all traffic is served using the entrypoint command. In order to use static handlers, at least one of your handlers must contain the line script: auto to deploy successfully.

script: auto を設定してねと記載があるのでこれはおまじないのように必須の項目のようです。localで起動する分には必要ありませんが。

[追記]

1.9以下で指定するときは 最後の /.* より上部にサーブしたいディレクトリのルーティングの設定を書かないと正確にルーティングされません でした。なぜかは謎です。

これ、そもそも /.* に食われてその先(このルーティングより下部に設定したurl) は読み込まれないとFBもらいました。(そんな仕様知らんがな)