はじめに

今まで、FirebaseNetlifyを利用していたが、Google認証で手軽にアクセス制限をかけたくなったので、いろいろゴニョゴニョ出来そうなGoogle App Engine(以下 GAE)でホスティングすることにした。

前提

  • GCPでプロジェクトを作成済み
  • GAEのアプリケーションを作成済み
  • public ディレクトリ配下に公開したいファイルが用意されている

シンプルにHTMLを返せば良いケース

特にリダイレクト等の特別な処理は無しで、ただHTMLファイルを良い感じに表示してくれれば良いだけの場合。
このやり方だと、インスタンス自体使わない気がするので、とってもヘルシーな気がする。
app.yaml だけ用意すればOK。

app.yaml

runtime: php73

handlers:
- url: /
  static_files: public/index.html
  upload: public/index.html
- url: /(.*)/
  static_files: public/\1/index.html
  upload: public/(.*)/index.html
- url: /(.*)
  static_files: public/\1
  upload: public/(.*)

runtime

PHPは使わないんだけども、何かしらいるので。
ちなみにPHPは、特にPHPのファイルがなくても怒られないので選んでみた。
逆にGoなんかは *.go ファイルがないと怒られたりする。

handlers

デフォルトでは、 / で終わっているURLは、自動で index.html を表示してくれない。
なので、ここの設定で補完している。

リダイレクト等いろいろ処理をはさみたい場合

構成をシンプルにするために全てのリクエストをプログラムを通して表示するケース。
個人的な趣味でGoを利用しているけど、もちろん他の言語でもOK。
app.yaml の他に エンドポイントとなるプログラム(ここでは app.go )を用意すれば良い。

app.yaml

runtime: go112

handlers:
- url: /.*
  script: auto

app.go

やっていることは

  • 存在しているファイルを返す
  • / アクセス時の index.html の補完
  • リダイレクト処理

のみ。

package main

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
)

const redirect_file = "redirects.json"

type Redirect struct {
	Source string `json:"source"`
	Destination string `json:"destination"`
	Type int `json:"type"`
}

func main() {
	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(":"+port, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		redirect(w, r)
		http.FileServer(http.Dir("public")).ServeHTTP(w, r)
	})); err != nil {
		log.Fatal(err)
	}
}

func redirect(w http.ResponseWriter, r *http.Request) {
	_, err := os.Stat(redirect_file)
	if os.IsNotExist(err) {
		return
	}

	bytes, err := ioutil.ReadFile(redirect_file); if err != nil {
		log.Fatal(err)
	}
	var redirects []Redirect
	if err := json.Unmarshal(bytes, &redirects); err != nil {
		log.Fatal(err)
	}

	for _, red := range redirects {
		reg := regexp.MustCompile(red.Source)
		if reg.MatchString(r.URL.Path) == false {
			continue
		}
		if regexp.MustCompile("^http").MatchString(red.Destination) {
			r.URL, err = url.Parse(reg.ReplaceAllString(r.URL.Path, red.Destination)); if err != nil {
				log.Fatal(err)
			}
		} else {
			r.URL.Path = reg.ReplaceAllString(r.URL.Path, red.Destination)
		}
		http.Redirect(w, r, r.URL.String(), red.Type)
	}
}

redirects.json

リダイレクトのルールを記載するJSONファイル。
Firebaseのフォーマットを真似してみた。
リライトのルールは正規表現で書く感じ。
もちろんこういった機能が不要だったら、もっとシンプルに出来る。

[
  {"source" : "/blog/(.*)", "destination" : "https://blog.example.com/$1", "type" : 301}
  {"source" : "/posts/12345", "destination" : "/posts/2020/01/01/12345", "type" : 301}
]

デプロイ

$ gcloud app deploy

特別なことは必要なし。

おわりに

実際にやってみると、速度的には満足だし、GCPの機能を使ってアクセス制限もかけられるし大満足。
あと意外な副産物として、リクエストログが見られるという点。
リダイレクト漏れとか、404エラー対応とかがはかどって良い。
Firebaseと比べて、1日当たりの転送量の制限も緩くなるので、それもありがたい感じ。
大満足。