loading...
Cover image for 用 Cloudflare Worker 來佈署靜態網頁

用 Cloudflare Worker 來佈署靜態網頁

floatflower profile image FloatFlower ・4 min read

得益於 Cloudflare 在全球超過 200 多個節點,我們可以很輕易的將我們的應用程式快速的推向 Cloudflare 的全球網路邊緣,這可以讓我們的應用程式的 Response Time 變得更低,如一個以 SPA 技術開發的 Web Application 就可以很輕易的佈署至 Cloudflare Worker。


基礎環境建立

首先我們需要安裝用於佈署 Worker 的工具:@cloudflare/wrangler,我們可以透過 npm 來安裝這個工具:

~$ sudo npm install -g @cloudflare/wrangler

安裝完畢之後我們進入 Cloudflare 的網站,在右欄的部份找到這個區塊,並按下 "取得您的 API Token"。

Alt Text

進入頁面之後點選 API Token 的 Tab 並按下"建立 Token"

Alt Text

並且在建立的頁面中點選 "編輯 Cloudflare Worker",並進入頁面

Alt Text

在權限的區塊,這裡選定的區塊是 "編輯 cloudflare worker 必要的權限",因此若沒有其他的需求,就不用額外調整權限區塊。在帳戶資源部份請至少選擇 "包含" 自己的帳戶。區域資源的部份可以選擇特定區域,以免不小心異動其他網域的內容,如果沒有這類的疑慮,也可以直接選擇包含所有區域。

確定之後就會跳出摘要頁面,確認資料無誤之後就可以按下"建立 Token":

Alt Text

取得上述的 Token 之後我們就可以回到 Terminal,輸入指令後按照要求將 API Token 貼上

~$ wrangler config

To find your API Token, go to https://dash.cloudflare.com/profile/api-tokens
and create it using the "Edit Cloudflare Workers" template.

If you are trying to use your Global API Key instead of an API Token
(Not Recommended), run `wrangler config --api-key`.
Enter API Token: 
B4jLkMW933P3P5tsDOqLbKZLi9UMLPEkH8xQdOI7
 Validating credentials...
 Successfully configured. You can find your configuration file at: /home/floatflower/.wrangler/config/default.toml

看到 Successfully configured 之後就代表環境已經設定好囉。

佈署網站

建立一個靜態網頁模板

~$ wrangler generate --site mysite

Creating project called `mysite`...
 Done! New project created /home/floatflower/mysite
 You will need to update the following fields in the created wrangler.toml file before continuing:
 You can find your account_id in the right sidebar of your account's Workers page, and zone_id in the right sidebar of a zone's overview tab at https://dash.cloudflare.com
- account_id

~$ cd mysite

接著我們就會拿到這些檔案:

.
├── node_modules
├── package-lock.json
├── public
│   ├── 404.html
│   ├── favicon.ico
│   ├── img
│   │   ├── 200-wrangler-ferris.gif
│   │   └── 404-wrangler-ferris.gif
│   └── index.html
├── workers-site
│   ├── index.js
│   ├── package.json
│   └── package-lock.json
└── wrangler.toml

  • public 資料夾就是放置靜態網站的資料夾,你可以把你的靜態網頁放在裡面。
  • worker-site 就是 Cloudflare Worker 的程式碼,你可以在這裡定義自己的 Cloudflare Worker 行為,但是在這裡我們也不用做其他改變,直接用預設的就可以了。

調整 wrangler.toml

我們打開 wrangler.toml 裡面含有這些內容:

# worker 名稱
name = "mysite" 
type = "webpack"
account_id = ""

# 如果選擇 true 的話,Cloudflare 會提供給你一個 .workers.dev 網域的網址,因為我們要提供網域,所以我們將其設為 false。
workers_dev = false
# 如果 workers_dev 是 false 時,這個 route 一定要提供,我這邊示範的網址是 mysite.fres.host。
route = "mysite.fres.host/*"
zone_id = ""

[site]
bucket = "./public"

其中的 account_id 以及 zone_id 我們就會回到剛才取得 API Token 那個區塊:

Alt Text

其中的區域識別碼貼至 zone_id 以及帳戶識別碼貼至 account_id

佈署上線

接著我們執行佈署的指令

~$ wrangler publish

...

 Built successfully, built project size is 13 KiB.
 Created namespace for Workers Site "__mysite-workers_sites_assets"
 Success
 Uploading site files
 Deployed to the following routes:
mysite.fres.host/* => created

接著我們回到 Cloudflare Worker 的頁面:

Workers

就會看到一個 mysite.fres.host/* => mysite,但是還沒結束。我們要進入 DNS 的頁面,新增一個 DNS Record:

Alt Text

DNS Record 的值可以為隨意,因為全部都會被 Cloudflare 攔截起來,所以網域不管指向哪裡都可以,對應完畢之後我們就可以打開瀏覽器並訪問網址,在這裡我的網址是 https://mysite.fres.host

Alt Text

就會看到 Cloudflare 的預設頁面,做到這裡最基本的 Cloudflare 佈署就完成了。

番外篇:用 Cloudflare Worker 佈署 History mode 的 Vue SPA Application

首先我們先開啟一個 Vue SPA 的應用程式,並選擇開啟 History Mode,然後我們將剛才產生的 wrangler.toml 以及 worker-site 移到這個 mysite_spa 底下。

~$ vue create mysite_spa

接著在設定之前首先先看一下 Vue.js 官方文件提到當需要佈署 History mode 的應用程式時,所需要做的設定,以 Nginx 做範例:

location / {
    try_files $uri $uri/ /index.html;
}

概念就是先 try-file 看看有沒有路徑對應的文件,如果沒有則遞送 index.html。

更改 wrangler.toml

因為在 Vue Application 中 public 資料夾另有他用,因此我們不能照常將 wrangler.toml 的 bucket 設為 ./public 資料夾,而是要將其改成 ./dist

# 將 site 區塊的設定改成這樣
[site]
bucket = "./dist"

更改 worker-site/index.js

接著我們要修改 worker-site/index.js 檔案的內容,以符合上述提到的 Nginx 遞送 Vue Application 的邏輯。

我們先打開 worker-site/index.js

import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler'

/**
 * The DEBUG flag will do two things that help during development:
 * 1. we will skip caching on the edge, which makes it easier to
 *    debug.
 * 2. we will return an error message on exception in your Response rather
 *    than the default 404.html page.
 */
const DEBUG = false

addEventListener('fetch', event => {
    try {
        event.respondWith(handleEvent(event))
    } catch (e) {
        if (DEBUG) {
            return event.respondWith(
                new Response(e.message || e.toString(), {
                    status: 500,
                }),
            )
        }
        event.respondWith(new Response('Internal Error', { status: 500 }))
    }
})

async function handleEvent(event) {
    const url = new URL(event.request.url)
    let options = {}

    /**
   * You can add custom logic to how we fetch your assets
   * by configuring the function `mapRequestToAsset`
   */
    // options.mapRequestToAsset = handlePrefix(/^\/docs/)

    try {
        if (DEBUG) {
            // customize caching
            options.cacheControl = {
                bypassCache: true,
            }
        }

        const page = await getAssetFromKV(event, options)

        // allow headers to be altered
        const response = new Response(page.body, page)

        response.headers.set('X-XSS-Protection', '1; mode=block')
        response.headers.set('X-Content-Type-Options', 'nosniff')
        response.headers.set('X-Frame-Options', 'DENY')
        response.headers.set('Referrer-Policy', 'unsafe-url')
        response.headers.set('Feature-Policy', 'none')

        return response

    } catch (e) {
        // if an error is thrown try to serve the asset at 404.html
        if (!DEBUG) {
            try {
                let notFoundResponse = await getAssetFromKV(event, {
                    mapRequestToAsset: req => new Request(`${new URL(req.url).origin}/404.html`, req),
                })

                return new Response(notFoundResponse.body, { ...notFoundResponse, status: 404 })
            } catch (e) {}
        }

        return new Response(e.message || e.toString(), { status: 500 })
    }
}

/**
 * Here's one example of how to modify a request to
 * remove a specific prefix, in this case `/docs` from
 * the url. This can be useful if you are deploying to a
 * route on a zone, or if you only want your static content
 * to exist at a specific path.
 */
function handlePrefix(prefix) {
    return request => {
        // compute the default (e.g. / -> index.html)
        let defaultAssetKey = mapRequestToAsset(request)
        let url = new URL(defaultAssetKey.url)

        // strip the prefix from the path for lookup
        url.pathname = url.pathname.replace(prefix, '/')

        // inherit all other props from the default request
        return new Request(url.toString(), defaultAssetKey)
    }
}

程式有點長,不過我們並沒有要修改太多,找到以下這個區塊,這個區塊定義了當應用程式找不到指定檔案的時候就返回 404.html 並將 Http Status Code 設為 404 Not Found。

// if an error is thrown try to serve the asset at 404.html
if (!DEBUG) {
    try {
        let notFoundResponse = await getAssetFromKV(event, {
            mapRequestToAsset: req => new Request(`${new URL(req.url).origin}/404.html`, req),
        })

        return new Response(notFoundResponse.body, { ...notFoundResponse, status: 404 })
    } catch (e) {}
}

因此我們將這段程式修改成這樣:

// if an error is thrown try to serve the asset at 404.html
if (!DEBUG) {
    try {
        let notFoundResponse = await getAssetFromKV(event, {
            mapRequestToAsset: req => new Request(`${new URL(req.url).origin}/index.html`, req),
        })

        return new Response(notFoundResponse.body, { ...notFoundResponse, status: 200 })
    } catch (e) {}
}

這樣就可以在找不到檔案時遞送 index.html 給客戶端了,完成之後我們做一次打包,然後我們透過 wrangler dev 指令打開測試伺服器並訪問 http://localhost:8787 ,來看看設定是否正確。

~$ npm run build
~$ wrangler dev

在 Vue Application 的 Demo 頁面中就可以直接嘗試是否 History Vue Router 可以正常運作:

http://localhost:8787

Alt Text

http://localhost:8787/about

Alt Text

確定無誤之後我們就可以將其佈署至 Cloudflare Worker,最後我們訪問一下佈署的頁面:https://mysite.fres.host

~$ wrangler publish

就可以看到結果頁面:

Alt Text

Posted on by:

floatflower profile

FloatFlower

@floatflower

I am FloatFlower.Huang, a fullstack software engineer, use PHP and Typescript.

Discussion

pic
Editor guide