diff --git a/README.md b/README.md index 7f53d85..d20dae3 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,52 @@ github release、archive以及项目文件的加速项目,支持clone,有Clo `PREFIX`是前缀,默认(根路径情况为"/"),如果自定义路由为example.com/gh/*,请将PREFIX改为 '/gh/',注意,少一个杠都会错! +## AWS Lambda@Edge 部署 + +### CloudFront + +1. 前往 AWS CloudFront https://console.aws.amazon.com/cloudfront ,点击 Create distribution 。 +2. Step 1 + - 随便填一个 Distribution name,点击 Next 。 +3. Step 2 + - 选择 Origin type 里面的 Other 。 + - 选项 Origin / Custom origin 填写 aws.amazon.com 。 + - 选项 Settings / Cache settings 选择 Customize cache settings 。 + - 选项 Allowed HTTP methods 选择 GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE 。 + - 选项 Cache policy 选择 Caching Disabled 。 + - 选项 Origin request policy 选择 AllViewer 。 + - 选项 Response headers policy 选择 Managed-CORS-with-preflight-and-SecurityHeadersPolicy 。 + - 点击 Next 。 +4. Step 3 + - 选择 Do not enable security protections ,点击 Next 。 +5. Step 4 + - 点击 Create Distribution 。 +6. 记录新建的 Distribution ID 。 + +### Lambda - Code + +1. 前往 AWS Lambda https://console.aws.amazon.com/lambda ,点击 Create Function 。 +3. 输入名称,点击 Create Function 。 +4. 点击 Code 选项卡,复制 `index.js` 文件内容到 VSCode 在线编辑器中的 `index.mjs` 文件,将函数调用 `addEventListener` 注释掉。 +5. 点击 VSCode 在线编辑器左侧的 Deploy 。 +5. 点击 Configuration 选项卡,前往 Configuration 选项卡,点击 Edit,修改 Timeout 为 0 min 10 sec 。 + +### Lambda - Trigger + +1. 前往 AWS Lambda https://console.aws.amazon.com/lambda 。 +2. 点击 Function overview / Diagram 里面的 Add trigger 。 +3. 选择 CloudFront,点击 Deploy to Lambda@Edge 。 +4. 在弹出的页面中: + - Select an option 选择 Configure new CloudFront trigger 。 + - Distribution 选择刚刚新建的 Distribution ID 。 + - CloudFront event 选择 Viewer request 。 + - 勾选 Include body 和 Confirm 。 + - 点击 Deploy 。 +5. 如果提示权限问题,那么前往 Configuration 选项卡,点击 Edit 。 + - 将 Execution role 设为 Create a new role from AWS policy templates 。 + - 将 Policy templates 设为 Basic Lambda@Edge permissions (for CloudFront trigger) 。 + - 点击 Save 。 + ## Python版本部署 ### Docker部署 diff --git a/index.js b/index.js index 26d0506..a67a5aa 100644 --- a/index.js +++ b/index.js @@ -54,6 +54,7 @@ function newUrl(urlStr) { } +// comment out function call `addEventListener` for AWS Lambda usage addEventListener('fetch', e => { const ret = fetchHandler(e) .catch(err => makeRes('cfworker error:\n' + err.stack, 502)) @@ -70,6 +71,7 @@ function checkUrl(u) { return false } +// entry <= Cloudflare Worker /** * @param {FetchEvent} e */ @@ -184,3 +186,91 @@ async function proxy(urlObj, reqInit) { }) } +// entry <= AWS Lambda +export const handler = async (event) => { + const response = await fetchHandler(fromCloudFrontRequest(event.Records[0].cf.request)); + return toCloudFrontResponse(response, event.Records[0].cf.response); +}; + +const fromCloudFrontRequest = (cloudFrontRequest) => { + // viewer request event: + // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-event-structure.html + const cfReq = cloudFrontRequest; + const headers = {}; + Object.keys(cfReq.headers).map((lowerKey) => { + cfReq.headers[lowerKey].map(({ key, value }) => { + headers[key] = value; + }); + }); + const event = { + request: new Request(`https://${cfReq.headers.host[0].value}${cfReq.uri}?${cfReq.querystring}`, { + method: cfReq.method, + headers, + body: (cfReq.body && cfReq.body.data)? Buffer.from(cfReq.body.data, cfReq.body.encoding) : undefined, + }), + } + return event; +}; + +const toCloudFrontResponse = async (response) => { + // viewer request event: + // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-generating-http-responses.html + const res = response; + const cfRes = {}; + // status + cfRes.status = res.status; + cfRes.statusDescription = res.statusText; + // headers + res.headers.forEach((value, key) => { + if (!cfRes.headers) { cfRes.headers = {}; } + const lowerKey = key.toLowerCase(); + if (isBlackListedHeader(lowerKey)) { return } + cfRes.headers[lowerKey] = [{ key, value }]; + }); + // body + if (res.body) { + cfRes.body = Buffer.from(await res.arrayBuffer()).toString('base64'); + cfRes.bodyEncoding = 'base64'; + } + return cfRes; +}; + +const isBlackListedHeader = (lowerKey) => { + // viewer request event: + // https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/edge-function-restrictions-all.html + return /^x-amz-cf-/.test(lowerKey) || /^x-amz-cf-/.test(lowerKey) || [ + // disallowed headers + 'connection', + 'expect', + 'keep-alive', + 'proxy-authenticate', + 'proxy-authorization', + 'proxy-connection', + 'trailer', + 'upgrade', + 'x-accel-buffering', + 'x-accel-charset', + 'x-accel-limit-rate', + 'x-accel-redirect', + 'x-amzn-auth', + 'x-amzn-cf-billing', + 'x-amzn-cf-id', + 'x-amzn-cf-xff', + 'x-amzn-errortype', + 'x-amzn-fle-profile', + 'x-amzn-header-count', + 'x-amzn-header-order', + 'x-amzn-lambda-integration-tag', + 'x-amzn-requestid', + 'x-cache', + 'x-forwarded-proto', + 'x-real-ip', + // read-only headers in viewer request events + 'content-length', + 'host', + 'transfer-encoding', + 'via', + // let aws decide how to compress + 'content-encoding', + ].includes(lowerKey); +};