📘

短縮URLをGAEのdispatch.yamlで作った話

2020/09/21に公開

こんにちは、syumaiです。zenn.dev には初めての投稿となります!
最近Google App Engine (以下GAEとします) を使って tw.syum.ai (Twitter) や gh.syum.ai (GitHub) と言ったサブドメインを使った短縮URLを作ったのでその話を書きます。

短縮URLを作った経緯

(経緯はどうでもいいという方はすっ飛ばして読んでください)

私は syum.ai というドメインを持っていて、これをポートフォリオサイト的に作ったものをまとめる運用をしていました。
ただ、置いているページもかなり質素なもので、とても有効に使えているとは言えず、せめてもう少し色んな用途で使えるようにしたいと思っていました。

Gandiとの出会い

最近いくつかのドメインをGandiと言うレジストラに移管した時に知ったのですが、なんとGandiには無料のURL転送サービスが存在し、これを使うとSNSアカウントなどへの転送を行うURLを簡単に作れるとのことでした。(元ツイート)

実際に、 twitter.syumai.comtwitter.com/__syumai に転送するようにしてみたのですが、設定から5分程度で使えるようになりとても感動しました。

syum.aiでもURL転送したい

これを見て、syum.aiでも同じことをやりたいと思ったのですが、syum.aiをGandiに移管すると、ドメインの更新料が年間2000円上がることがわかり断念しました。
とは言え、ただ諦めると言うのは癪だったので、syum.aiをデプロイしているGAEの機能を使って短縮URLを作る方法は無いか模索することにしました。

実装方針

GAEでサブドメインを使ったURL転送を行うにあたり、

  • A. アプリケーションでリクエストされたURLのホストを確認し、リダイレクトする
  • B. アプリケーション外のレイヤでホスト名をハンドルして転送を行う

の2つ方針を立てました。

~Aについては、何かしらの方法でホストを知ることが出来ないか調べてみましたが、ドキュメント上にも該当の情報は無く、パスしか見えないね…とわかったため早々に諦めました。~
(2021/9/21 追記) HTTPのHostヘッダ見たらいいだけだよ!!これでやります!
(2021/9/21 さらに追記) gorilla/muxを使ってHostで分岐して実装しました!dispatch.yamlの削除に成功しています!
https://github.com/syumai/syum.ai/pull/6/files


ここからはどうしてもdispatch.yamlが使いたい人向けの説明となります。

Bについては、どうやらdispatch.yamlと言う物が使えそう、と言うことがわかりました。

dispatch.yamlとは

dispatch.yamlは、GAEのapp.yamlと同レイヤー (厳密には違う) に配置する設定ファイルで、これを使うことでルーティングルールを上書きすることが出来ます。
ここで、公式ドキュメントのdispatch.yamlを抜粋したものを示します。

dispatch:
  ...
  # Default service serves simple hostname request.
  - url: "simple-sample.appspot.com/"
    service: default

  # Send all mobile traffic to the mobile frontend.
  - url: "*/mobile/*"
    service: mobile-frontend

  ...
  # Matches the hostname "customer1.myapp.com", but not "1.customer1.myapp.com".
  - url: "customer1.myapp.com/*"
    service: static-backend

これを見ると、どうやらホスト名をハンドルしてルーティング先のserviceを切り替えることが可能そうだということがわかります。
次に、serviceについて見てみます。

GAEのserviceとは

serviceは、GAEで動かすアプリケーションの単位に相当します。
service一つに対してapp.yamlが一つ必要となり、app.yamlの service キーに値を設定することで名前を指定できます。これを省略したserviceの名前はdefaultとなります。
詳しくはapp.yamlのドキュメントを参照ください。

実装方針まとめ

以上より、

  • 転送に使いたいドメイン毎にserviceを作って、アプリケーション内にリダイレクト処理を仕込む
  • dispatch.yamlでホスト名をハンドルし、各ドメインに対応するserviceにルーティングする

ことで、サブドメインを使ったURL転送をGAEで実現出来そう!と言うことがわかりました。

実装する

実装にあたっては次の作業が必要です。

  1. GAEへのカスタムドメインの設定
  2. DNSレコードの設定
  3. アプリケーションの実装
  4. app.yamlの分割、serviceのデプロイ
  5. dispatch.yamlのデプロイ

説明に使っているのは下記のリポジトリです。不明点があれば見てみてください。
https://github.com/syumai/syum.ai

1. GAEへのカスタムドメインの設定

これは転送元URLでHTTPSを使うために必要です。
GCP Consoleでポチポチするだけで、Googleが自動で証明書を発行・設定してくれます。

2. DNSレコードの設定

Googleから指定されるDNSレコードをDNSサーバー側で設定する必要があります。
今回はもともとsyum.aiを設定済みだったので、tw.syum.aiとgh.syum.aiからsyum.aiをCNAMEで指定する形で済ませてしまいました。

3. アプリケーションの実装

受け取ったlocationに対して雑に301リダイレクトするサーバーを作りました。

package redirect

import (
	"fmt"
	"net/http"
)

func Serve(location, port string) error {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Location", location)
		w.WriteHeader(301)
	})
	return http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
}

これを共通packageとして、各URLに対してmain.goを一つずつ作ります (環境変数でも良かったな…)
下記は tw.syum.ai/main.go の例です。

...

const location = "https://twitter.com/__syumai"

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Printf("Listening on port %s", port)

	if err := redirect.Serve(location, port); err != nil {
		log.Fatal(err)
	}
}

ディレクトリ構成はこのような形になりました。

.
├── gh.syum.ai
│   ├── app.yaml
│   ├── go.mod
│   ├── go.sum
│   └── main.go
├── tw.syum.ai
│   ├── app.yaml
│   ├── go.mod
│   ├── go.sum
│   └── main.go
└── redirect
    └── serve.go

4. app.yamlの分割、serviceのデプロイ

(直前の 3 で既に示してしまいましたが、) 各URLのディレクトリにapp.yamlを配置します。
app.yamlには、service名のみが必要となります。

runtime: go113

main: .

service: tw-syum-ai

ここで、gh.syum.aiとtw.syum.aiのディレクトリに配置したapp.yamlを使って、serviceをデプロイしておきます。

5. dispatch.yamlのデプロイ

最後に、dispatch.yamlをデプロイします。
dispatch.yamlの内容は次のようになりました。

dispatch:
  - url: "syum.ai/*"
    service: default

  - url: "tw.syum.ai/*"
    service: tw-syum-ai

  - url: "gh.syum.ai/*"
    service: gh-syum-ai

内容については見ての通りですが、ホスト名ごとに別々のserviceにルーティングするようにしています。

dispatch.yamlは、いつもGAEのアプリケーションをデプロイしている時と同じように下記コマンドでデプロイ出来ます。

gcloud app deploy dispatch.yaml --project $(GCP_PROJECT_NAME)

ここまで作業して、使いたい短縮URLにアクセス出来るようになりました!

やってみた感想

tw.syum.ai と gh.syum.ai と言うオシャレっぽいURLが使えるようになり嬉しいです。ただ、これを増やすのはまあまあ面倒そうと思いました (Porkbunに設定したDNSレコードが反映されるのにやたら時間がかかった印象)
あとは、serviceを分けているので、短縮URLが使われすぎるとGAEの無料枠をサクッと超えそうと言う罠もあるので、行儀の悪い人がイタズラしてこないか若干ドキドキします。まあ居ないと思いますが…
それから、初めからCloudflare等のCDNを噛ましていたらずっと楽に済んだと言う噂もあります。

ひとまずこれで、名刺に書くURLが短く出来た!と言う事でこれからはこのURLを使っていきます。

GitHubで編集を提案

Discussion