This topic shows the sample code of EdgeRoutine and the execution results of the sample code in some use cases.

Background information

Sample code of 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));
}

Scenarios

  • Hello World

    Use case: This example shows how to build a serverless service on an edge node where content can be directly generated. This way, Alibaba Cloud CDN does not need to redirect requests to the origin server.

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

    Use case: This example shows how to build a simple service that enables event tracking on edge nodes. Event tracking collects information about requests sent to edge nodes, such as the IP address, geographic location, and device information.

    Command:
    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

    Use case: This example shows how to build a simple proxy on an edge node. The built-in fetch API operation is called in the JavaScript code to create a custom HTTP request. The edge node then returns the requested content to the client.

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

    Use case: This example shows how to add a header to a back-to-origin request.

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

    Use case: This example shows how to add a header to the response of a back-to-origin request.

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

    Use case: This example shows how to run a simple A/B test.

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

    Use case: This example shows how to enable aggregation of content retrieved from different origin servers. This feature returns aggregated content retrieved from different origin servers to the clients.

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

    Use case: This example shows how to prefetch content from the origin server. Prefetch tasks are performed asynchronously while the edge node returns responses to the clients. This feature is expected to be supported by later versions.

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

    Use case: This example shows how to retrieve content from multiple origin servers and return the content that is retrieved the earliest.

    Command:
    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

    Use case: This example shows how to build an Edge Side Includes (ESI) service.

    Command:
    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

    Use case: This example shows how to enable logging on an edge node. After the edge node returns a response to a client, relevant log data is asynchronously generated and delivered to your server.

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

    Use case: This example shows how to enable edge nodes to redirect to the corresponding address to retrieve and return content to clients after the edge nodes receive the HTTP 302 status code from the origin server.

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

    Use case: This example shows how to enable edge nodes to redirect requests.

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

    Use case: This example shows how to deploy a simple anti-bot service on edge nodes.

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

    Use case: This example shows how to enable Web Application Firewall (WAF) on edge nodes to block requests under specified conditions.

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