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代码效果参考:https://www.yopian.com/sitemap/post.xml
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) {
} else {await writer.write(value); break;
}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 {
} catch (e) {// 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));
}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));
}