本文为您展示边缘程序的示例源码,并通过几个示例场景介绍示例源码的运行结果。

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

    需求:该示例场景实现一个简单的边缘Serverless服务,无需回源,直接在边缘节点生成内容。

    命令:
    curl -v 'http://edge.ialicdn.com/a/b?x=y' -d '{"name": "helloworld"}'
    hello world
  • Geo

    需求:该示例场景实现一个简单的边缘打点服务,可以采集到边缘节点的请求相关信息:如IP、地理、设备信息等。

    命令:
    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"
    Geo
  • Fetch

    需求:该示例场景实现一个简单的边缘代理服务,在JS代码中调用内置api fetch做了HTTP自请求,响应给客户端fetch的最终内容。

    命令:
    curl -v 'http://edge.ialicdn.com/a/b?x=y' -d '{"name": "fetch", "url": "http://a.*.com/xx"}'
    fetch
  • Request

    需求:该示例场景实现一个简单的请求添加回源头功能。

    命令:
    curl -v 'http://edge.ialicdn.com/?x=y' -d '{"name":"request", "headers":{"aa":"bb"}, "body":"Hello ER!"}'
    request
  • Response

    需求:该示例场景实现一个简单的添加回源响应头功能。

    命令:
    curl -v 'http://edge.ialicdn.com/?xx=yy' -d '{"name":"response", "headers":{"ra":"rb"}}'
    response
  • AB test

    需求:该示例场景实现一个简单的AB测试的功能。

    命令:
    curl -v 'http://edge.ialicdn.com/?x=y' -d '{"name":"ab-test"}' -H "user-agent: a/canary-client/b"
    ab test
  • Multi origin

    需求:该示例场景实现一个简单的边缘同拉多源合并功能,将不同源站的网页内容聚合后返回给客户端。

    命令:
    curl -v 'http://edge.ialicdn.com/?x=y' -d '{"name":"multi-origin"}'
    origin
  • Precache/Prefetch

    需求:该示例场景实现一个简单的CDN预热功能,预热任务在响应客户端时将异步完成(需下个版本支持,目前忽略)。

    命令:
    curl -v 'http://edge.ialicdn.com/' -d '{"name": "prefetch", "prefetch": ["http://a.*.com/prefetch", "http://b.*.com/prefetch"]}'
    Precache/Prefetch •Precache/Prefetch •1
  • Race

    需求:该示例场景实现一个简单的回源同拉功能,将回源速度最快的源站的内容优先返回给客户端。

    命令:
    curl -v 'http://edge.ialicdn.com/?x=y' -d '{"name":"race", "fetchList" : [ "https://www.taobao.com", "https://www.tmall.com", "https://www.alipay.com" ]}'
    race
  • ESI

    需求:该示例场景实现一个简单的ESI服务。

    命令:
    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"}'
    esi
  • Log

    需求:该示例场景实现一个简单的边缘日志服务,在响应结束后异步地生成日志并回传给您的Server。

    命令:
    curl -v 'http://edge.ialicdn.com/' -d '{"name":"log"}'
    log
  • 3xx

    需求:该示例场景实现一个简单的回源302跟随功能。

    命令:
    curl -v 'http://edge.ialicdn.com/' -d '{"name":"3xx"}'
    3xx
  • Redirect

    需求:该示例场景实现一个简单的边缘请求重定向功能。

    命令:
    curl -v 'http://edge.ialicdn.com/a/b?x=y' -d '{"name": "redirect"}'
    redirect
  • Deny bot

    需求:该示例场景实现一个简单的边缘反爬虫服务。

    命令:
    curl -v 'http://edge.ialicdn.com' -d '{"name":"deny-bot"}' -H "user-agent: xxxspider"
    deny bot
  • Waf

    需求:该示例场景实现一个简单的边缘waf服务,当满足某些条件时,将禁止该请求。

    命令:
    curl -v 'http://edge.ialicdn.com' -d '{"name":"waf", "city":"Hangzhou"}'
    waf