更多示例代碼
本文為您展示邊緣程序的示例源碼,并通過幾個示例場景介紹示例源碼的運行結(jié)果。
EdgeRoutine的示例源碼
addEventListener("fetch", function(event) {
event.respondWith(_handleRouter(event));
});
async function _handleRouter(event) {
let json = await event.request.json();
if (json) {
let name = json.name
switch(name) {
case "helloworld":
return _handleHelloWorld(event);
case "geo":
return _handleGeo(event);
case "fetch":
return _handleFetch(event, json);
case "request":
return _handleRequest(event, json);
case "response":
return _handleResponse(event, json);
// Cases
case "ab-test":
return _handleABTest(event, json);
case "multi-origin":
return _handleMultipleOriginConcate(event, json);
case "prefetch":
return _handlePrefetch(event, json);
case "race":
return _handleRace(event, json);
case "esi":
return _handleESI(event, json);
case "log":
return _handleEdgeLog(event, json);
case "3xx":
return _handleRedirect3XX(event, json);
case "redirect":
return _handleRedirectGeneral(event, json);
case "deny-bot":
return _handleDenyBot(event, json);
case "waf":
return _handleWAF(event, json);
default:
break;
}
}
return new Response(
`{ error : "invalid request" }`,
{
"status" : 403 ,
"statusText" : "Forbidden"
});
}
async function _handleHelloWorld(event) {
return new Response("Hello World!");
}
async function _handleGeo(event) {
const info = event.info;
let remote_addr = info.remote_addr;
let ip_isp_en = info.ip_isp_en;
let ip_city_en = info.ip_city_en;
let ip_region_en = info.ip_region_en;
let ip_country_en = info.ip_country_en;
let scheme = info.scheme;
let detector_device = info.detector_device;
let content = `Geo: ${remote_addr}, \
${ip_isp_en}, \
${ip_country_en}, \
${ip_city_en}, \
${ip_region_en},\
${scheme}, \
${detector_device}`;
return new Response(content);
}
async function _handleFetch(event, json) {
let fetchURL = json.url;
if (fetchURL) {
return await fetch(fetchURL);
}
return fetch("http://default.ialicdn.com");
}
async function _handleRequest(event, json) {
let headers = json.headers;
let body = json.body;
const fetchInit = {
body : body,
headers: headers
};
return fetch("http://default.ialicdn.com", fetchInit);
}
async function _handleResponse(event, json) {
let resp = await fetch("http://default.ialicdn.com");
let headers = json.headers;
for (var k in headers) {
resp.headers.set(k, headers[k]);
}
return resp;
}
/** ================*
* (1) DevOps |
* =================*/
function _shouldDoABTest(request) {
// (1) if request's user agent match a certain string
{
const ua = request.headers.get("user-agent");
if (ua && ua.match(/canary-client/)) {
return true;
}
}
// (2) whether we have special header
{
return request.headers.has("x-ab-test");
}
}
async function _handleABTest(event, json) {
event.request.headers.delete("content-length");
const fetchInit = {
method : event.request.method,
headers: event.request.headers,
body : "empty"
};
if (_shouldDoABTest(event.request)) {
return fetch("http://default.ialicdn.com/dev", fetchInit);
} else {
return fetch("http://default.ialicdn.com", fetchInit);
}
}
/** ==================================*
* (2) Multiple Origin Concatenation |
** ==================================*/
async function _handleMultipleOriginConcate(event, json) {
const respInit = {
headers: event.request.headers,
body : json.body
};
// (1) We try to concate www.alipay.com and www.tmall.com together
let {readable, writable} = new TransformStream();
async function controller() {
let r1 = await fetch("http://www.alipay.com");
let r2 = await fetch("https://www.tmall.com");
await r1.body.pipeTo(writable, {preventClose: true});
await r2.body.pipeTo(writable);
}
controller();
return new Response(readable, respInit);
}
/** ==================================*
* (3) Precache/Prefetch |
** ==================================*/
async function _fetchAndIgnore(url) {
try {
// Specify cdnProxy flag to make sure the request goes through the CDN
let resp = await fetch(url);//, {cdnProxy: true});
// Make sure to ignore the content otherwise the cache may not be valid
await resp.ignore();
} catch (e) {
console.error("invalid URL: %s", url);
}
}
async function _doPrefetchURLAsync(prefetchURL, event) {
for (const url of prefetchURL) {
event.waitUntil(_fetchAndIgnore(url));
}
}
async function _handlePrefetch(event, json) {
{
const prefetchURL = json.prefetch;
if (prefetchURL) {
// Do not await it and let it run in background
_doPrefetchURLAsync(prefetchURL, event);
return new Response("Done Prefetch");
}
}
return new Response("Miss Prefetch");
}
/** ==================================*
* (4) Race
** ==================================*/
async function _handleRace(event, json) {
let fetchList = json.fetchList;
if (fetchList) {
return Promise.race(fetchList.map((x) => fetch(x)));
} else {
return "forget to include fetchList field in your JSON";
}
}
/** ==================================*
* (5) Simple ESI
** ==================================*/
async function _handleESI(request, json) {
let { readable, writable } = new TransformStream();
let newResponse = new Response(readable);
if (!json.esi) {
return "forget to include template field in your JSON";
}
streamTransformBody(new BufferStream(json.esi), writable);
return newResponse;
}
async function handleTemplate(encoder, templateKey) {
const linkRegex = new RegExp("esi:include.*src=@(.*)@.*", 'gm');
let result = linkRegex.exec(templateKey);
let esi = "unknown";
if (!result) {
return encoder.encode(`<${templateKey}>`);
}
if (result[1]) {
esi = await subRequests(result[1]);
}
return encoder.encode(`${esi}`);
}
async function subRequests(target){
const init = {method: 'GET'};
let response = await fetch(target, init);
let text = await response.text();
return text;
}
async function streamTransformBody(readable, writable) {
const startTag = "<".charCodeAt(0);
const endTag = ">".charCodeAt(0);
let reader = readable.getReader();
let writer = writable.getWriter();
let templateChunks = null;
while (true) {
let { done, value } = await reader.read();
if (done) break;
while (value.byteLength > 0) {
if (templateChunks) {
let end = value.indexOf(endTag);
if (end === -1) {
templateChunks.push(value);
break;
} else {
templateChunks.push(value.subarray(0, end));
await writer.write(await translate(templateChunks));
templateChunks = null;
value = value.subarray(end + 1);
}
}
let start = value.indexOf(startTag);
if (start === -1) {
await writer.write(value);
break;
} else {
await writer.write(value.subarray(0, start));
value = value.subarray(start + 1);
templateChunks = [];
}
}
}
await writer.close();
}
async function translate(chunks) {
const decoder = new TextDecoder();
let templateKey = chunks.reduce(
(accumulator, chunk) =>
accumulator + decoder.decode(chunk, { stream: true }), "");
templateKey += decoder.decode();
return handleTemplate(new TextEncoder(), templateKey);
}
/** ==================================*
* (6) Edge side conditional log
** ==================================*/
async function _doEdgeLog(data, writer) {
let resp = await fetch("http://default.ialicdn.com/log",
{
method : "POST",
body : data,
headers: [["content-type", "application/json"]]
});
console.log("logged");
{
let stream = new BufferStream("++++++++++++++++++++++++++++++\n");
await stream.pipeTo(writer, {preventClose: true});
}
await resp.body.pipeTo(writer);
}
async function _handleEdgeLog(event, json) {
let start= Date.now();
let resp = await fetch("http://default.ialicdn.com", {
method : event.request.method,
headers: event.request.headers,
body : json.body
});
// Get a promise that is fired when we send out everything
let {readable, writable} = new TransformStream();
// (1) first let the fetch request's response goes back and then we post
// the log back as well internally
let endPromise = resp.body.pipeTo(writable, {preventClose: true});
// (2) wait for endPromise to be fired to make sure that the body has been
// piped back to the client, and then we do the log
event.waitUntil(endPromise.then(
(v) => {
let end = Date.now();
let diff= (end - start);
try {
// You have to await your async promise since wait until is not
// usable currently maybe. User can use wait until only before
// returning the main request for now
event.waitUntil(_doEdgeLog(`{ "cost(millisecond)" : ${diff} }`, writable));
} catch (e) {
console.error(`${e}`);
}
},
(v) => {
writable.abort();
console.error("failed");
}));
console.error("XXXX");
// return the response back
return new Response(readable, {
status: resp.status,
headers: resp.headers
});
}
/** ==================================*
* (7) redirect-3xx
** ==================================*/
async function _handleRedirect3XX(event, json) {
return fetch("http://www.taobao.com", {redirect: "follow"});
}
/** ==================================*
* (8) redirect
* (1) UserAgent
* (2) Geo information
** ==================================*/
async function _handleRedirectGeneral(event, json) {
const fetchInit = {
method : event.request.method,
body : json.body,
headers : event.request.headers
};
{
const ua = event.request.headers.get("user-agent");
if (ua && ua.match(/firefox/i)) {
return fetch("http://default.ialicdn.com/firefox", fetchInit);
}
if (ua && ua.match(/safari/i)) {
return fetch("http://default.ialicdn.com/safari", fetchInit);
}
}
{
if (event.info.detect_device && event.info.detect_device.match(/iphone/)) {
return fetch("http://default.ialicdn.com/iphone", fetchInit);
}
}
return new Response("unknown request", {status: 403});
}
/** ==================================*
* (9) Deny bot
** ==================================*/
async function _handleDenyBot(event, json) {
{
const ua = event.request.headers.get("user-agent");
if (ua && ua.match(new RegExp("xxxspider", "i"))) {
return new Response("Forbidden", {status: 403});
}
}
return fetch("http://default.ialicdn.com");
}
/** ==================================*
* (10) Simple WAF
** ==================================*/
async function _handleWAF(event, json) {
let city = json.city;
if (event.info.ip_city_en === city) {
return new Response("Forbidden", {status: 403});
}
// back to origin
return (JSON.stringify(event.info));
}
示例場景
Hello World
需求:該示例場景實現(xiàn)一個簡單的邊緣Serverless服務(wù),無需回源,直接在邊緣節(jié)點生成內(nèi)容。
命令:
curl -v 'http://edge.ialicdn.com/a/b?x=y' -d '{"name": "helloworld"}'
Geo
需求:該示例場景實現(xiàn)一個簡單的邊緣打點服務(wù),可以采集到邊緣節(jié)點的請求相關(guān)信息:如IP、地理、設(shè)備信息等。
命令:
curl -v 'http://edge.ialicdn.com/a/b?x=y' -d '{"name": "geo"}' -H "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.80 Safari/537.36"
Fetch
需求:該示例場景實現(xiàn)一個簡單的邊緣代理服務(wù),在JS代碼中調(diào)用內(nèi)置api fetch做了HTTP自請求,響應(yīng)給客戶端fetch的最終內(nèi)容。
命令:
curl -v 'http://edge.ialicdn.com/a/b?x=y' -d '{"name": "fetch", "url": "http://a.*.com/xx"}'
Request
需求:該示例場景實現(xiàn)一個簡單的請求添加回源頭功能。
命令:
curl -v 'http://edge.ialicdn.com/?x=y' -d '{"name":"request", "headers":{"aa":"bb"}, "body":"Hello ER!"}'
Response
需求:該示例場景實現(xiàn)一個簡單的添加回源響應(yīng)頭功能。
命令:
curl -v 'http://edge.ialicdn.com/?xx=yy' -d '{"name":"response", "headers":{"ra":"rb"}}'
AB test
需求:該示例場景實現(xiàn)一個簡單的AB測試的功能。
命令:
curl -v 'http://edge.ialicdn.com/?x=y' -d '{"name":"ab-test"}' -H "user-agent: a/canary-client/b"
Multi origin
需求:該示例場景實現(xiàn)一個簡單的邊緣同拉多源合并功能,將不同源站的網(wǎng)頁內(nèi)容聚合后返回給客戶端。
命令:
curl -v 'http://edge.ialicdn.com/?x=y' -d '{"name":"multi-origin"}'
Precache/Prefetch
需求:該示例場景實現(xiàn)一個簡單的CDN預(yù)熱功能,預(yù)熱任務(wù)在響應(yīng)客戶端時將異步完成(需下個版本支持,目前忽略)。
命令:
curl -v 'http://edge.ialicdn.com/' -d '{"name": "prefetch", "prefetch": ["http://a.*.com/prefetch", "http://b.*.com/prefetch"]}'
Race
需求:該示例場景實現(xiàn)一個簡單的回源同拉功能,將回源速度最快的源站的內(nèi)容優(yōu)先返回給客戶端。
命令:
curl -v 'http://edge.ialicdn.com/?x=y' -d '{"name":"race", "fetchList" : [ "https://www.taobao.com", "https://www.tmall.com", "https://www.alipay.com" ]}'
ESI
需求:該示例場景實現(xiàn)一個簡單的ESI服務(wù)。
命令:
curl -v 'http://edge.ialicdn.com/' -d '{"name":"esi","esi" : "<esi:include src=@http://www.alipay.com@> This is after the ESI for www.alipay.com"}'
Log
需求:該示例場景實現(xiàn)一個簡單的邊緣日志服務(wù),在響應(yīng)結(jié)束后異步地生成日志并回傳給您的Server。
命令:
curl -v 'http://edge.ialicdn.com/' -d '{"name":"log"}'
3xx
需求:該示例場景實現(xiàn)一個簡單的回源302跟隨功能。
命令:
curl -v 'http://edge.ialicdn.com/' -d '{"name":"3xx"}'
Redirect
需求:該示例場景實現(xiàn)一個簡單的邊緣請求重定向功能。
命令:
curl -v 'http://edge.ialicdn.com/a/b?x=y' -d '{"name": "redirect"}'
Deny bot
需求:該示例場景實現(xiàn)一個簡單的邊緣反爬蟲服務(wù)。
命令:
curl -v 'http://edge.ialicdn.com' -d '{"name":"deny-bot"}' -H "user-agent: xxxspider"
Waf
需求:該示例場景實現(xiàn)一個簡單的邊緣waf服務(wù),當(dāng)滿足某些條件時,將禁止該請求。
命令:
curl -v 'http://edge.ialicdn.com' -d '{"name":"waf", "city":"Hangzhou"}'