
前回は簡単な Go Web アプリケーションを GAE の環境に配置(deploy)するところまで記載しました。
今回は、続けて Go アプリ以外の静的コンテンツの公開と Go のテンプレートの利用を行っていきます。
GAE で静的ファイルを公開する
GAE の場合は、リクエストされたパスによってどう処理するかのマッピングを app.yaml 構成ファイルで行います。この構成ファイルが肝になり、プラットフォームとコンテンツのマッピング、コンテンツのハンドラなども app.yaml で指定します。詳細は以下のページに記載があります。
トップページの / で終わっている URL の場合に index.html に割り当てる
ここではまずパスに / まで指定された URL の場合は、index.html の静的ファイルを参照するように指定します。
app.yaml
handlers: - url: /$ static_files: public/index.html upload: public/index.html - url: /.* script: auto
handlers 配下の指定は上から順に評価され、最初にマッチした部分がリクエストの処理に適用されます。
最初の url: /$ の部分が正規表現で最後($)が / で終わる URL を意味しており、その場合には、static_filesで指定しているようにpublic ディレクトリ配下の index.htmlを参照しなさいと GAE に指示しています。また、upload でアップロードする対象のファイルを指定しています。
次の url: /.* は全てのパターンにマッチしており、それまでのパターンにマッチしなかった場合には、ここでは Go の Web アプリを参照することになります。それまでにマッチしていない URL のパターンは全て Go で拾うよ、という意味です。ですので、最初に動作させていました https://xxx.appspot.com/hello はここで処理されます。
この時、ローカルのファイルの構成は以下の通りとなっています。
$ tree
.
├── app.yaml
├── helloworld.go
├── helloworld_test.go
├── index.yaml
└── public
└── index.html
public/index.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>GAE App Static</title> </head> <body> <h2>GAE App Static file</h2> </body> </html>
この状態でデプロイ(gcloud app deploy)し、https://xxx.appspot.com/ (xxxは適切なホスト名に置き換えてください)でアクセスします。

ちゃんと静的ファイル index.html が表示されました。
HTML、画像ファイルなど、全ての静的ファイルも参照できるようにする
その他の HTML、画像ファイル、CSS、JS なども参照できるようにしてみましょう。
以下の設定を追加します。
app.yaml
diff --git a/app.yaml b/app.yaml index 4688cd5..ba21b70 100644 --- a/app.yaml +++ b/app.yaml @@ -4,5 +4,8 @@ handlers: - url: /$ static_files: public/index.html upload: public/index.html + - url: /(.+\.(html|css|js|gif|png|jpg))$ + static_files: public/\1 + upload: public/.+\.(html|css|js|gif|png|jpg)$ - url: /.* script: auto
ファイルの構成は以下の通りです。
$ tree
.
├── app.yaml
├── helloworld.go
├── helloworld_test.go
├── index.yaml
└── public
├── about.html
└── index.html
curl で about.html をつっついてみます。
$ curl -X GET http://xxx.appspot.com/about.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>About</title>
</head>
<body>
<h2>About</h2>
</body>
</html>
OK です。
画像ファイルも参照してみます。設定は既に app.yaml に追加していますので、コンテンツだけ用意します。
$ tree
.
├── app.yaml
├── helloworld.go
├── helloworld_test.go
├── index.yaml
└── public
├── about.html
├── assets
│ └── images
│ └── 652f4b3ae27d42ce5cd07a032165deb6.png
├── contact
│ └── hi.html
│ └── index.html
├── css
└── index.html
public/contact/hi.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Hi</title> </head> <body> <h2>Hi</h2> <img src="/assets/images/652f4b3ae27d42ce5cd07a032165deb6.png"> </body> </html>
http://xxx.appspot.com/contact/hi.html にアクセスします。

OK です。
その他の / で終わっている URL の場合も index.html に割り当てる
先程、トップページ(ルート)だけ index.html を補完する設定を入れてろいましたので、それ以外の / で終わっている URL の場合にも index.html に割り当てる設定を入れておきます。
現状、http://xxx.appspot.com/ は以下のように補完されています。

ただし、http://xxx.appspot.com/contact/ でアクセスしますと、KO です。

当然、http://xxx.appspot.com/contact/index.html と直接指定すれば問題ありません。

app.yaml に以下の設定を追加します。
app.yaml
diff --git a/app.yaml b/app.yaml index ba21b70..12050f4 100644 --- a/app.yaml +++ b/app.yaml @@ -4,6 +4,9 @@ handlers: - url: /$ static_files: public/index.html upload: public/index.html + - url: /(.*)/$ + static_files: public/\1/index.html + upload: public/.*/index.html - url: /(.+\.(html|css|js|gif|png|jpg))$ static_files: public/\1 upload: public/.+\.(html|css|js|gif|png|jpg)$
これで http://xxx.appspot.com/contact/ で参照しても表示されるようになりました。

Go のテンプレートを使う
Go のテンプレートを使います。Ruby でいうところの、erb、Java でいうところの jsp といったテンプレートエンジンです。
まずは、helloworld.go にテンプレートを使うための記述を追加しておきます。
helloworld.go
diff --git a/helloworld.go b/helloworld.go index e2b71d7..4b87a93 100644 --- a/helloworld.go +++ b/helloworld.go @@ -1,10 +1,13 @@ package main import ( + "bytes" "fmt" + "html/template" "log" "net/http" "os" + "time" ) func indexHandler(w http.ResponseWriter, r *http.Request) { @@ -18,8 +21,38 @@ func indexHandler(w http.ResponseWriter, r *http.Request) { } } +type tmplParams struct { + DaysOfWeek []string + Date time.Time +} + +func formatDate(t time.Time) string { + return t.Format("2006-01-02") +} + +func process(w http.ResponseWriter, r *http.Request) { + funcMap := template.FuncMap{ "fdate": formatDate } + t := template.New("show-days.html").Funcs(funcMap) + t, _ = t.ParseFiles("tmpl/show-days.html") + params := tmplParams{ + DaysOfWeek: []string{"月", "火", "水", "木", "金", "土", "日"}, + Date: time.Now(), + } + buf := &bytes.Buffer{} + err := t.Execute(buf, params) + if err != nil { + fmt.Fprintf(os.Stderr, "%+v\n", err) + http.Error(w, "something wrong", http.StatusInternalServerError) + } else { + buf.WriteTo(w) + } +} + func main() { http.HandleFunc("/hello", indexHandler) + http.HandleFunc("/process", process) port := os.Getenv("PORT") if port == "" {
テンプレートは以下になります。
tmpl/show-days.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Template Example</title> </head> <body> <p>今日は、{{ .Date | fdate }} です。</p> <ul> {{ range .DaysOfWeek }} <li>{{ . }}</li> {{ end }} </ul> </body> </html>
ちなみに、もっとシンプルな例でよかったのですが、ここでは幾つか別の要素も試しています。
- パラメータは構造体のデータとして受け渡しをしています。
- 受け取る方(
show-days.html)では、.自体が構造体のデータを指しますので、.以降にそのフィールドを指定することでアクセスできます。 - 構造体では、フィールドは大文字で始めておかないとテンプレート側では見えなくなります。(あたり前と言えばあたり前なのですが、よくうっかり小文字のままとしてしまいますので、注意です。)
- 受け取る方(
- 呼び出し側で関数を定義し、それをテンプレート側で使えるようにしています。
formatDateがそれです。template.New().Funcsでマッピングしており、テンプレート側では、fdateという関数名で使えます。
- テンプレートで使っている
|(パイプ)はパイプラインと言われるもので、 Unix のパイプと考え方は同じです。.Dateでtime.Time型の現在日時が取り出され、それがformatDate(fdate)の引数として渡されています。
- 通常、
template.Executeは直接http.ResponseWriterに write するように実装します(t.Execute(w, params))。ここで行っているバッファリングの処理は通常行う必要はありません。- ここでは、テンプレート側でのエラーも考慮し、一旦バッファリングした後に
buf.WriteToでhttp.ResponseWriterで write しています。こうしておかないと、テンプレート内でエラーが発生したとしても、t.Execute(w, params)は http のレスポンスを 200 で返して、処理できるところまで処理して返してしまいます。 - 通常であれば、テンプレートにロジックは持ち込まず、テンプレートにはデータだけを渡すべきです。ですので、ここまでの処理は必要なく、バッファリングせずに直接
http.ResponseWriterで write すればよいと思います。(t.Execute(w, params))
- ここでは、テンプレート側でのエラーも考慮し、一旦バッファリングした後に
現在のファイル構成は以下の通りです。
$ tree
.
├── app.yaml
├── helloworld.go
├── helloworld_test.go
├── index.yaml
├── public
│ ├── about.html
│ ├── assets
│ │ └── images
│ │ └── 652f4b3ae27d42ce5cd07a032165deb6.png
│ ├── contact
│ │ ├── hi.html
│ │ └── index.html
│ ├── css
│ └── index.html
└── tmpl
└── show-days.html
http://xxx.appspot.com/process にアクセスします。

OK ですね。
だいぶ長くなってしまいましたが、「Google App Engine (GAE) を使って Go Web アプリ/静的 Web コンテンツを公開する」は以上になります。