Cloudflare Pages (Functions) と Stream と Access を使って動画サイトを作る

# 概要

こちらのブログにあるデモアプリケーションを作ります。

> ブログ：[Build your next video application on Cloudflare](https://blog.cloudflare.com/build-video-applications-cloudflare/)
>
> デモサイト：https://cf-pages-stream.pages.dev/
>
> ソースコード：[cloudflare/pages-stream-demo](https://github.com/cloudflare/pages-stream-demo)

↓今回出来上がるものはこんな感じ。

![image-20211224143602507](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211224143602507.png)

# Cloudflare Pages (Functions)

[Cloudflare Pages](https://pages.cloudflare.com/) を使えば、Git レポジトリにあるコードからビルドし、サイトをCloudflare のエッジにデプロイできます。無料プランがあります。

![image-20211224144059105](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211224144059105.png)

先日の発表で [Functions](https://developers.cloudflare.com/pages/platform/functions) がオープンベータになり、静的コンテンツに加えて動的な API のデプロイも Pages プロジェクト内で完結する形でサポートされました。

> 以前は、Wrangler を使って手動で Worker をデプロイすることでしか、Pages サイトに動的機能を追加することができませんでした。関数では、プロジェクトのファイルシステム規約を利用することで、Pages プロジェクトから直接 Workers プラットフォームを利用することができます。これにより、git push 時に静的コンテンツと動的コンテンツの両方を含むサイト全体をデプロイすることができます。

具体的には `/functions` ディレクトリ内にコードを配置します。

> まず、プロジェクトのルートに /functions ディレクトリを作成します。このディレクトリにFunctionsファイルを書くと、あらかじめ指定されたルートにカスタム機能を持ったWorkerが自動生成されます。

オープンベータ期間中は Functions について費用は発生しないようなので少し試してみます。

> オープンベータ期間中は、Pagesプロジェクト内にFunctionsを導入するための追加費用は発生しません。ただし、ベータ版では、1日あたり10万件の起動要求の合計が制限されています。1日の制限に達した場合、PagesはFunctionsの実行を停止し、静的アセットのみを提供するようにフォールバックします。

# Cloudflare Stream

[Cloudflare Stream](https://developers.cloudflare.com/stream/) は Cloudflare のエッジから動画を配信するサービスです。

> Cloudflare Streamは、エンドツーエンドの動画インフラをスケールアップして提供します。Streamがストレージ、エンコーディング、アダプティブ・ビットレート再生を行うので、お客様はコアアイデアに集中することができます。

こちらは従量課金の有料サービスになっていますが、使い始めるには非常に敷居の低いサービスになってます。

> [Cloudflare Streamの請求 – Cloudflareヘルプセンター](https://support.cloudflare.com/hc/ja/articles/360016450871-Cloudflare-Stream%E3%81%AE%E8%AB%8B%E6%B1%82)
>
> 価格設定は、以下に示すように使用量とストレージに基づきます。
> ユーザーに配信されるビデオの分数：
>
>     1,000分あたりUSD $1.00/月
>
> Cloudflare Streamに保存したビデオの分数：
>
>     1,000分あたりUSD $5.00
>     事前請求

# Cloudflare Access

Cloudflare を通すことで認証を簡単に実装できるサービスです。

最大50人のユーザーまで無料なプランがあるので、そちらを使います。

> [Cloudflare Access | ゼロトラストアプリケーションAccess | Cloudflare](https://www.cloudflare.com/ja-jp/teams/access/)
>
> お客様のIDプロバイダーやエンドポイント保護プラットフォームと連携して、拒否をデフォルト設定とするゼロトラストルールを適用し、企業アプリケーション、プライベートIPスペース、ホスト名へのアクセスを制限します。VPNより速く、安全にユーザーをつなぎます。
>
> ![img](https://www.cloudflare.com/static/2d667f99e33f60bb1e82392205924147/simple-ztna-request-flow-diagram.svg)

# Git レポジトリ作成

`gh`コマンドでレポジトリを作成します。

```shell
brew install gh
gh auth login
gh repo create pages-stream-demo2 --public
```

ソースコードを`push`します。

```shell
git clone https://github.com/cloudflare/pages-stream-demo
mv pages-stream-demo pages-stream-demo2
cd pages-stream-demo2
git remote rm origin
git remote add origin git@github.com:kyouheicf/pages-stream-demo2

git add --all
git commit -m "Initial Commit"
git push -u origin HEAD:main
```

次の手順で、こちらの環境変数を用意していきます。

```shell
% cat .env.example 
CF_ACCOUNT_ID=
CF_API_TOKEN_STREAM=
CF_ACCESS_APP_AUD=
CF_ACCESS_TEAM_NAME=
CF_STREAM_SIGNING_KEY=
```

# `CF_ACCOUNT_ID` 取得

https://dash.cloudflare.com/?to=/:account/:zone/ のページ右下から入手できます。

![image-20211227150756715](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227150756715.png)

# `CF_API_TOKEN_STREAM` 作成

https://dash.cloudflare.com/profile/api-tokens から作成できます。

![image-20211227152017030](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227152017030.png)

Custom token を作成します。

![image-20211227152046573](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227152046573.png)

権限 Account > Stream > Edit を付与し、対象アカウントを選択します。

![image-20211227152204859](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227152204859.png)

Token を作成します。

![image-20211227152228345](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227152228345.png)

Token は1度しか表示されないので、忘れないように保存します。

![image-20211227152335964](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227152335964.png)



# `CF_STREAM_SIGNING_KEY` 作成

Token を使って https://api.cloudflare.com/#stream-signing-keys-create-a-signing-key の API に POST し、得られる`jwk` を使います。

こちらも1度しか表示されないので、忘れないように保存します。

> [Securing your Stream · Cloudflare Stream docs](https://developers.cloudflare.com/stream/viewing-videos/securing-your-stream#option-2-generating-signed-tokens-without-calling-the-token-endpoint)

```shell
% curl -X POST -H "Authorization: Bearer $TOKEN"  "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/stream/keys"
{
  "result": {
    "id": "49b256xxxxx",
    "pem": "LS0tLxxxxx",
    "jwk": "eyJ1cxxxxx",
    "created": "2021-12-27T15:39:58.383165616Z"
  },
  "success": true,
  "errors": [],
  "messages": []
}
```

# `CF_ACCESS_TEAM_NAME` 取得

https://dash.cloudflare.com/sign-up/teams からサインアップするか、[Start from the Cloudflare Dashboard](https://developers.cloudflare.com/cloudflare-one/setup#start-from-the-cloudflare-dashboard) のガイドに従って、Cloudflare for Teams を有効にします。

そのときに指定する [team-name](https://developers.cloudflare.com/cloudflare-one/glossary#team-name) をパラメータとして使います。

> `<your-team-name>.cloudflareaccess.com`

# `CF_ACCESS_APP_AUD` 作成

`/admin`を認証で保護するポリシーを作成しておきます。

![image-20211227161455300](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227161455300.png)

こちらの `Application Audience (AUD) Tag`をパラメータとして使います。

![image-20211227161618279](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227161618279.png)

許可するユーザーグループを設定します。

![image-20211227161740872](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227161740872.png)

認証の IdP は事前に統合済みの選択肢から選べます。

![image-20211227161830105](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227161830105.png)

# Pages Project 作成

上記の準備が完了したら、Pages Project を作成します。

https://dash.cloudflare.com/?to=/:account/pages から「Create a Project」に進みます。

![image-20211227162312045](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227162312045.png)

ビルド設定は以下のようにします。

- Framework preset = None
- Build command = exit 0
- Build output directory = /public

![image-20211227162901553](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227162901553.png)

最後に環境変数を設定してデプロイします。

![image-20211227163029976](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227163029976.png)

ビルド＆デプロイは約3分かかりますが、これだけで動画サイトができあがります。

![image-20211227171544583](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227171544583.png)

# カスタムドメイン設定

最後に、以下の画面からカスタムドメインを設定します。

> [Adding a custom domain](https://developers.cloudflare.com/pages/get-started#adding-a-custom-domain)

![image-20211228005027916](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211228005027916.png)

Cloudflare アカウントに登録してあるゾーンを設定します。

![image-20211228005100799](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211228005100799.png)

こちらの設定を経ずに CNAME だけを登録すると動かないため、こちらの設定フローを経るようにしてください。

> カスタムドメインが正常に追加されるようにするには、上記のカスタムドメインの追加手順を実行する必要があります。Cloudflare Pagesダッシュボードでドメイン（またはサブドメイン）を関連付けずに、Cloudflare Pagesサイトを指すカスタムCNAMEレコードを手動で追加すると、ドメインがCNAMEレコードアドレスで解決できず、 [`522` エラー](https://support.cloudflare.com/hc/en-us/articles/115003011431-Troubleshooting-Cloudflare-5XX-errors#522error)が表示されることになります。

![image-20211228005235722](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211228005235722.png)

# まとめ
社内向けの研修動画などの配信に使えるサイトが簡単かつエッジのサーバーレスに作れるので、非常におすすめです。
詳しい処理はソースコードを確認してみてください。以下、まとめです。

- `/functions` ディレクトリにコードを配置することで動的な API も同時に実装できる
- `_middleware_.ts` で Cloudflare Access による認証等を実装できる
- 1つのコード内で `onRequestGet`, `onRequestPost`, `onRequestPatch`, `onRequestDelete` などそれぞれのリクエストメソッドに対応した処理を記載できる

```shell
.
├── functions
│   ├── admin
│   │   ├── _middleware_.ts
│   │   ├── live_inputs
│   │   │   └── [id]?.ts
│   │   ├── profile
│   │   │   └── index.ts
│   │   └── videos
│   │       └── [id]?.ts
│   └── videos
│       └── [id]?.ts
├── licence
├── package.json
├── pages-functions-beta.json
├── public
│   ├── admin
│   │   └── index.html
│   └── index.html
└── src
    ├── cfAccess.ts
    └── cfStream.ts
```

テスト用の動画は https://test-videos.co.uk/ からダウンロードできます。

![image-20211227171313941](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211227171313941.png)

`/admin` はこんな感じです。おつかれさまでした。

![image-20211228005904431](https://cdn.jsdelivr.net/gh/kyouheicf/image@master/uPic/image-20211228005904431.png)