Skip to content

Instantly share code, notes, and snippets.

@hrmsk66
Last active July 1, 2022 02:39
Show Gist options
  • Save hrmsk66/8ecbea70b1b71f8f8161e63b3084c8cf to your computer and use it in GitHub Desktop.
Save hrmsk66/8ecbea70b1b71f8f8161e63b3084c8cf to your computer and use it in GitHub Desktop.

はじめに

Compute@Edge の開発の流れやツールを紹介するためのハンズオンワークショップです。

ワークショップへ参加する為には Fastly CLI のインストールと JavaScript の開発環境が必要です。 まだ準備ができていない場合は、こちらの記事を参考にご対応ください。 https://zenn.dev/hrmsk66/articles/74e2e890726e99

Fastly CLI のバージョンの違いによるトラブルを避けるために fastly update コマンドを実行しておいてください。

アジェンダ

  1. プロジェクトの作成
  2. Hello, World!
  3. バックエンドサーバの設定
  4. クライアント IP と環境変数
  5. プロジェクトのデプロイ
  6. ライブログモニタリング
  7. キャッシュオーバーライド
  8. ディクショナリ
  9. 外部パッケージを使う
  10. プロジェクトの作り方の紹介

1. プロジェクトの作成

適当なディレクトリを作成して、ディレクトリ内で fastly compute init を実行してください。プロンプトに従って開発言語とスターターキットを選択していきます。今回は [2] JavaScript[2] Empty starter for Javascript を選択してください。

mkdir example && cd example
fastly compute init

コマンドの実行がおわると、以下のようなファイル・ディレクトリが作成されているはずです。 このワークショップのなかで編集するのは index.jsfastly.toml だけです。

├── README.md
├── fastly.toml
├── node_modules
├── npm-shrinkwrap.json
├── package.json
├── webpack.config.js
└── src
    └── index.js

2. Hello, World!

src/index.js を見てみましょう。

/// <reference types="@fastly/js-compute" />

addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));

async function handleRequest(event) {
  return new Response("OK", { status: 200 });
}

addEventListener によって fetch イベントが呼ばれたときに実行する処理を登録しています。fetch はクライアントからのリクエストを受信したときに発生するイベントです。

handleRequest に渡されている引数 event にはリクエストの情報等が含まれています。リクエストされたパスや、リクエストヘッダの値に基づく処理を実行する場合は event から情報を取得します。

アプリケーションのロジックは handleRequest の中に追加します。レスポンスメッセージを変更してみましょう。

/// <reference types="@fastly/js-compute" />

addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));

async function handleRequest(event) {
  return new Response("Hello, World!", { status: 200 });
}

開発用のローカルサーバで動作を確認してみましょう。 プロジェクトのディレクトリで fastly compute serve を実行して、ブラウザでアクセスしてみてください。

ローカルテストについて

ほとんどの機能をテストすることができるものの、ローカルテストは Fastly で動作するサービスの完全なレプリカではありません。例えばキャッシュの機能はありませんし、Geo ロケーションの情報取得には対応していません。ローカルテストの制約についてはこちらのドキュメントに記載されています。

Constraints and limitations

3. バックエンドサーバの設定

エッジだけで処理が完結するサービスをつくることもできますが、バックエンドサーバからのレスポンスをキャッシュする CDN 的な使い方もできますし、サーバのレスポンスをカスタマイズしてクライアントに返すこともできます。

バックエンドサーバを追加してみましょう。

fastly.toml にバックエンドサーバの定義を追加する

まずバックエンドリクエストの宛先を設定します。 ローカルテストでは fastly.toml にバックエンドサーバの設定を記述します。バックエンドサーバはローカルマシン上で動作する別のサーバでもいいですし、外部のサーバでも構いません。

fastly.toml に以下の内容をコピペしてください。今回は example.com をバックエンドサーバとして使います。

[local_server]
  [local_server.backends]
    [local_server.backends.backend_1]
      url = "https://example.com"
      override_host = "example.com"

override_host について

ローカルテストでのリクエストの宛先は 127.0.0.1:7676 ですので Host ヘッダの値も特に指定しない限りその値になります。リクエストをそのままバックエンドに転送するとサーバによってはエラーが返ってきます。 上記のように override_host を設定すると、Host ヘッダはローカルの HTTP サーバからバックエンドにリクエストを送信するときに指定した値で上書きされます。

index.js にコードを追加する

/ へのリクエストを example.com へ送信し、それ以外のパスへのリクエストには今まで通り Hello, World! を返すようにコーディングします。

/// <reference types="@fastly/js-compute" />

addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));

async function handleRequest(event) {
  const req = event.request;
  const url = new URL(req.url);

  if (url.pathname === "/") {
    return fetch(req, {
      backend: "backend_1",
    });
  }

  return new Response("Hello, World!", { status: 200 });
}

fastly compute serve を実行しなおして動作を確認してください。

4. クライアント IP と環境変数

環境変数を使ってサービスの ID やバージョン、リクエストを処理している POP やサーバ名を取得することができます。 例えば、リクエストを処理しているサーバ名は以下のコードで取得することができます。

const server_name = fastly.env.get("FASTLY_HOSTNAME");

クライアント IP は event.client.address から取得することができます。

const client_ip = event.client.address;

これらの情報とリクエストされたパスを  JSON 形式でログに出力してみましょう。

/// <reference types="@fastly/js-compute" />

addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));

async function handleRequest(event) {
  const req = event.request;
  const url = new URL(req.url);

  const client_ip = event.client.address;
  const server_name = fastly.env.get("FASTLY_HOSTNAME");
  console.log(JSON.stringify({ path: url.pathname, client: client_ip, server: server_name }));

  if (url.pathname === "/") {
    return fetch(req, {
      backend: "backend_1",
    });
  }

  return new Response("Hello, World!", { status: 200 });
}

VCL のサービスでは Web UI にログフォーマットを入力するところがありますが、Compute@Edge のログフォーマットはこのようにコードの中で表現します。

fastly compute serve を実行しなおしてリクエストを送信すると、以下のメッセージが表示されると思います。

Log: {"path":"/","client":"127.0.0.1","server":"localhost"}

ご覧の通りローカルテストでは、FASTLY_HOSTNAME には localhost が格納されています。これを利用してローカルで動いている場合と、Fastly で動いている場合とで処理を書き分けることができます。

その他の環境変数

Compute@Edge で使用できるその他の環境変数はこちらに記載されています。

Environment variables reference for Compute@Edge

JavaScript SDK のドキュメント

こちらは JS SDK のドキュメントです。 event.client.address やその他の API について記載されています。

JavaScript SDK

5. プロジェクトのデプロイ

プロジェクトを Fastly にデプロイして先ほどのログがどのように表示されるか確認してみましょう。 fastly compute publish を実行してビルドとデプロイを行います。初回はビルドが問題なく完了するとサービスを設定するためのプロンプトが表示されます。

  • ドメインは edgecompute.appのサブドメインが自動的に生成されるのでそれを利用します
  • バックエンドサーバのホスト名とポート番号にはそれぞれ example.com443 を設定します
  • Backend name  はデフォルトのbackend_1のままにします
$ fastly compute publish
✓ Initializing...

( 省略 )

Create new service: [y/N] y

✓ Initializing...
✓ Creating service...

Domain: [yearly-funny-hermit.edgecompute.app]

Backend (hostname or IP address, or leave blank to stop adding backends): example.com
Backend port number: [80] 443
Backend name: [backend_1]
( 省略 )

デプロイが終わるとサービスと、サービスの管理画面の URL が表示されます。ブラウザでアクセスしてみてください。

6. ライブログモニタリング

プロジェクトのルートディレクトリで fastly log-tail を実行すると Fastly にデプロイしたサービスの stdout, stderr へのアウトプットがリアルタイムにコンソールに配信されます。リクエストを送信するとこのようなログが表示されると思います。

stdout | c23b0f12 | Log: {"path":"/","client":"xxx.xxx.xxx.xxx","server":"cache-hnd18744"}

c23b0f12 はリクエスト毎にアサインされる ID の一部です。アプリケーションが 1 リクエストに対して複数のログを生成する場合、この ID を使ってログをグルーピングすることができます。完全な ID は環境変数 FASTLY_TRACE_ID で取得することができます。

ライブログモニタリングは開発中のデバッグツールとして有用ですが、扱えるログのボリュームは限られています。プロダクション環境のログ等、ハイボリュームなログを取得する場合はログストリーミングをご利用ください。

7. キャッシュオーバーライド

Compute@Edge のデフォルトキャッシュルールは基本的には VCL のサービスと同じで、バックエンドサーバからのレスポンスはキャッシュ関連ヘッダの指示に従ってキャッシュされます。example.com のレスポンスには以下のヘッダがあるため、何もしなければ 604800s (1 week) の TTL がセットされます。

cache-control: max-age=604800

VCL のサービスと同様、適用する TTL を指定したり、キャッシュしないように指示する等、デフォルトの動作を一部オーバーライドすることもできます。 example.com からのレスポンスをキャッシュしないように動作を変更してみましょう。CacheOverride を使います。

/// <reference types="@fastly/js-compute" />

addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));

async function handleRequest(event) {
  const req = event.request;
  const url = new URL(req.url);

  const client_ip = event.client.address;
  const server_name = fastly.env.get("FASTLY_HOSTNAME");
  console.log(JSON.stringify({ path: url.pathname, client: client_ip, server: server_name }));

  if (url.pathname === "/") {
    const cacheOverride = new CacheOverride("pass");

    return fetch(req, {
      backend: "backend_1",
      cacheOverride,
    });
  }

  return new Response("Hello, World!", { status: 200 });
}

fastly compute publish を実行して動作を確認してみましょう。以下のようにレスポンスの x-cache ヘッダの右端の値が MISS になっていれば設定が効いていると考えられます(example.com は GCP でキャッシュしているので x-cache のひとつめの値は HIT になることが多いです)。

< x-cache: HIT, MISS

CacheOverride のその他の設定例についてこちらに記載されています。

Compute@Edge キャッシュまわりの制約

2022 年 6 月現在、以下の動作には対応していません。

  • Set-Cookie をもつレスポンスをキャッシュする
  • Cache-Control private もしくは no-cache をもつレスポンスをキャッシュする
  • オリジンからのレスポンスを Fastly にキャッシュする前に Vary ヘッダをカスタマイズする
  • stale-if-error を Compute@Edge のプログラムからセットする

ローカルテストの制約

ローカルテストにはキャッシュの機能はありません。CacheOverride は無視されます。

8. ディクショナリ

Compute@Edge のコードから Dictionary を参照することができます。Dictionary  は Key-Value 型のデータストアです。 Dictionary を使ってアプリケーションにリダイレクトのロジックを追加してみましょう。

fastly.toml に Dictionary の定義を追加する

ローカルテストでは fastly.toml の中に  Dictionary の設定を記述します。バックエンドサーバの下に定義を追加してください。

[local_server]
  [local_server.backends]
    [local_server.backends.backend_1]
      override_host = "example.com"
      url = "https://example.com"

  [local_server.dictionaries]
    [local_server.dictionaries.redirects]
      format = "inline-toml"
    [local_server.dictionaries.redirects.contents]
      "/cat" = "https://http.cat/200"
      "/dog" = "https://httpstatusdogs.com/200-ok"

Dictionary のコンテンツは別ファイル(JSON 形式)に移して以下のように fastly.toml から参照させることもできます。

  [local_server.dictionaries]
    [local_server.dictionaries.redirects]
      file = "redirects.json"
      format = "json"

JSON ファイルの内容は以下のようになるでしょう。

{
  "/cat": "https://http.cat/200",
  "/dog": "https://httpstatusdogs.com/200-ok"
}

index.js にコードを追加する

リクエストされたパスが  Dictionary に登録されていたら、対応する   URL にリダイレクトするようにコードを追加します。

/// <reference types="@fastly/js-compute" />

addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));

async function handleRequest(event) {
  const req = event.request;
  const url = new URL(req.url);

  const client_ip = event.client.address;
  const server_name = fastly.env.get("FASTLY_HOSTNAME");
  console.log(JSON.stringify({ path: url.pathname, client: client_ip, server: server_name }));

  if (url.pathname === "/") {
    const cacheOverride = new CacheOverride("pass");

    return fetch(req, {
      backend: "backend_1",
      cacheOverride,
    });
  }

  const dict = new Dictionary("redirects");
  const redirect = dict.get(url.pathname);
  if (redirect) {
    headers = new Headers({ location: redirect });
    return new Response("", {
      status: 302,
      headers,
    });
  }

  return new Response("Hello, World!", { status: 200 });
}

パスが見つからない場合、dict.get(url.pathname)null を返します。 nulliffalse 判定となるので、リダイレクトの対象になりません。

fastly compute serve を実行してブラウザで /cat/dog にアクセスしてみてください。

9. 外部パッケージを使う

cheerio という外部パッケージを使って example.com のレスポンスをエッジで書き換えてみましょう。 (cheerio は  jQuery ライクな構文で HTML を解析し、値の取得や書き換えを可能にするパッケージです)

プロジェクトのディレクトリで npm i cheerio を実行してください。

src/index.js は以下のように変更します。

/// <reference types="@fastly/js-compute" />
import * as cheerio from "cheerio";

addEventListener("fetch", (event) => event.respondWith(handleRequest(event)));

async function handleRequest(event) {
  const req = event.request;
  const url = new URL(req.url);

  const client_ip = event.client.address;
  const server_name = fastly.env.get("FASTLY_HOSTNAME");
  console.log(JSON.stringify({ path: url.pathname, client: client_ip, server: server_name }));

  if (url.pathname === "/") {
    const cacheOverride = new CacheOverride("pass");

    let beresp = await fetch(req, {
      backend: "backend_1",
      cacheOverride,
    });
    return rewriteHTML(beresp);
  }

  const dict = new Dictionary("redirects");
  const redirect = dict.get(url.pathname);
  if (redirect) {
    headers = new Headers({ location: redirect });
    return new Response("", {
      status: 302,
      headers,
    });
  }

  return new Response("Hello, World!", { status: 200 });
}

async function rewriteHTML(beresp) {
  let body = await beresp.text();
  const $ = cheerio.load(body);
  $("h1").html("Hello from Compute@Edge!");

  return new Response($.html(), {
    status: beresp.status,
    headers: beresp.headers,
  });
}

fastly compute serve を実行してページを確認してみてください。

cheerioと同じように、多くの外部パッケージを Compute@Edge 上で使うことができますが、内部で特定のプラットフォームの API を呼んでいるパッケージは使用することができず、プログラムがクラッシュします。使いたいパッケージが  Compute@Edge  で使えるか分からない場合は、実際にインストールして試してみるのが手っ取り早いでしょう。

10. プロジェクトの作り方の紹介

最後にプロジェクトの作り方について紹介します。

ワークショップではスターターキットからプロジェクトをつくりましたが、Github のリポジトリや Fastly Fiddle をベースとしてプロジェクトを作成することもできます。

Git リポジトリからプロジェクトをつくる

example.com をデコレーションするコードサンプルのリポジトリを使ってプロジェクトを作成してみます。 (コードはワークショップで実装したレスポンス書き換えの発展型です)

https://github.com/hrmsk66/ecp-rewrite-html-js

適当なディレクトリを作成して、以下のコマンドを実行します。

fastly compute init --from=https://github.com/hrmsk66/ecp-rewrite-html-js

fastly compute serve を実行して動作を確認してみてください。

Fastly Fiddle からプロジェクトをつくる

次は Fiddle でつくったベーシック認証のコード例をつかってプロジェクトを作ってみます。

https://fiddle.fastlydemo.net/fiddle/c5dcaa46

fastly compute init --from=https://fiddle.fastlydemo.net/fiddle/c5dcaa46

このベーシック認証のソリューションは、アクセスを許可するユーザの情報をディクショナリにセットしておくことを想定しています。

まず、ユーザ・パスワードの組み合わせを Base64 でエンコードした文字列を生成します。 下記の例ではユーザは fastly パスワードは password としています。

echo -n "fastly:password" | base64
ZmFzdGx5OnBhc3N3b3Jk

fastly.toml にディクショナリを定義して、生成した文字列をキーとするエントリを追加すれば準備完了です。

  [local_server.dictionaries]
    [local_server.dictionaries.username_password]
      format = "inline-toml"
    [local_server.dictionaries.username_password.contents]
      "ZmFzdGx5OnBhc3N3b3Jk" = "ok"

fastly compute serve を実行して動作を確認してみてください。

Developer Hub の Code Examples の中には  Fiddle がページに埋め込まれているタイプのコード例がありますが、それらのコード例は同様にプロジェクトをつくるときのベースとして使用することができます。右上のINSTALLをクリックすると実行すべき fastly compute init コマンドが表示されるので試してみましょう。

Code examples in JavaScript

Fastly Fiddle について

開発体験は VS Code 等のエディタを使った方がよいと思いますが、ブラウザ上でコードが実行できますし、プロジェクトのベースとしても使えるので、チーム内でアイデアをシェアしたり、サポートチームに問い合わせするとき等に便利に使えると思います。Fastly Fiddle に入力したデータは URL が分かれば誰でもアクセスできますので、パスワードや API トークン等の機密情報を入力しないように注意してください。

Fiddle で使うことのできる外部パッケージはこちらにリストされています。

ドキュメントのリンク集 for JavaScript Developers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment