IBM Cloud Docs
Edge Functions 用例

Edge Functions 用例

以下用例仅作为示例提供,并非旨在实现环境中的完全重复。

测试以下任何代码时请谨慎操作,因为这可能会导致服务中断。

A/B 测试

您可以创建 CIS Edge Function 以控制 A/B 测试。

addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request))
})

async function fetchAndApply(request) {
  const name = 'experiment-0'
  let group          // 'control' or 'test', set below
  let isNew = false  // is the group newly-assigned?

  // Determine which group this request is in.
  const cookie = request.headers.get('Cookie')
  if (cookie && cookie.includes(`${name}=control`)) {
    group = 'control'
  } else if (cookie && cookie.includes(`${name}=test`)) {
    group = 'test'
  } else {
    // 50/50 Split
    group = Math.random() < 0.5 ? 'control' : 'test'
    isNew = true
  }

  // We'll prefix the request path with the experiment name. This way,
  // the origin server merely has to have two copies of the site under
  // top-level directories named "control" and "test".
  let url = new URL(request.url)
  // Note that `url.pathname` always begins with a `/`, so we don't
  // need to explicitly add one after `${group}`.
  url.pathname = `/${group}${url.pathname}`

  const modifiedRequest = new Request(url, {
    method: request.method,
    headers: request.headers
  })

  const response = await fetch(modifiedRequest)

  if (isNew) {
    // The experiment was newly-assigned, so add a Set-Cookie header
    // to the response.
    const newHeaders = new Headers(response.headers)
    newHeaders.append('Set-Cookie', `${name}=${group}; path=/`)
    return new Response(response.body, {
      status: response.status,
      statusText: response.statusText,
      headers: newHeaders
    })
  } else {
    // Return response unmodified.
    return response
  }
}

添加响应头

要修改响应头,请先生成响应的副本,以便可以使其可变。 然后,您可以使用标题界面添加、更改或删除标题。

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

/**
 * Set the `x-my-header` header
 * @param {Request} request
 */
async function handleRequest(request) {
  let response = await fetch(request);

  // Make the headers mutable by re-constructing the Response.
  response = new Response(response.body, response);
  response.headers.set('x-my-header', 'custom value');
  return response;
}

聚集多个请求

该示例向不同的API端点发出多个请求,汇总响应,并将其作为单个响应发送回来。

addEventListener('fetch', event => {
    event.respondWith(fetchAndApply(event.request))
})

/**
 * Make multiple requests,
 * aggregate the responses and
 * send it back as a single response
 */
async function fetchAndApply(request) {
    const init = {
      method: 'GET',
      headers: {'Authorization': 'XXXXXX'}
    }
    const [btcResp, ethResp, ltcResp] = await Promise.all([
      fetch('https://api.coinbase.com/v2/prices/BTC-USD/spot', init),
      fetch('https://api.coinbase.com/v2/prices/ETH-USD/spot', init),
      fetch('https://api.coinbase.com/v2/prices/LTC-USD/spot', init)
    ])

    const btc = await btcResp.json()
    const eth = await ethResp.json()
    const ltc = await ltcResp.json()

    let combined = {}
    combined['btc'] = btc['data'].amount
    combined['ltc'] = ltc['data'].amount
    combined['eth'] = eth['data'].amount

    const responseInit = {
      headers: {'Content-Type': 'application/json'}
    }
    return new Response(JSON.stringify(combined), responseInit)
}

条件路由

根据所使用的设备提供不同内容的最简单方法是,根据您所关心的条件重写请求的 URL。 请参阅以下示例。

设备类型

addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request))
})

async function fetchAndApply(request) {
  let uaSuffix = ''

  const ua = request.headers.get('user-agent')
  if (ua.match(/iphone/i) || ua.match(/ipod/i)) {
    uaSuffix = '/mobile'
  } else if (ua.match(/ipad/i)) {
    uaSuffix = '/tablet'
  }

  return fetch(request.url + uaSuffix, request)
}

定制标头

addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request))
})

async function fetchAndApply(request) {
  let suffix = ''
  //Assuming that the client is sending a custom header
  const cryptoCurrency = request.headers.get('X-Crypto-Currency')
  if (cryptoCurrency === 'BTC') {
    suffix = '/btc'
  } else if (cryptoCurrency === 'XRP') {
    suffix = '/xrp'
  } else if (cryptoCurrency === 'ETH') {
    suffix = '/eth'
  }

  return fetch(request.url + suffix, request)
}

无源响应

您可以直接从 Edge 返回响应。 无需向您的源发送请求。

忽略 POST 和 PUT HTTP 请求

忽略 POST 和 PUT HTTP 请求。 此片段允许将所有其他请求传递到源。

addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request))
})

async function fetchAndApply(request) {
  if (request.method === 'POST' || request.method === 'PUT') {
    return new Response('Sorry, this page is not available.',
        { status: 403, statusText: 'Forbidden' })
  }

  return fetch(request)
}

拒绝 Spider 或搜寻器

保护源避免不想要的 spider 或搜寻器。 在此情况下,如果用户代理程序为“annoying-robot”,那么 Edge 函数会返回响应,而不是将请求发送到源。

addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request))
})

async function fetchAndApply(request) {
  if (request.headers.get('user-agent').includes('annoying_robot')) {
    return new Response('Sorry, this page is not available.',
        { status: 403, statusText: 'Forbidden' })
  }

  return fetch(request)
}

阻止特定 IP 的连接

阻止列表 IP 地址。 这段代码阻止特定IP(此处为 225.0.0.1 )连接到源站。

addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request))
})

async function fetchAndApply(request) {
  if (request.headers.get('cf-connecting-ip') === '225.0.0.1') {
    return new Response('Sorry, this page is not available.',
        { status: 403, statusText: 'Forbidden' })
  }

  return fetch(request)
}

Post 请求

从 HTTP POST请求中读取内容:

addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request))
})

/**
 * Making a curl request that looks like
 * curl -X POST --data 'key=world' example.com
 * or
 * curl -X POST --form 'key=world' example.com
 */
async function fetchAndApply(request) {
  try {
    const postData = await request.formData();
    return new Response(`hello ${postData.get('key')}`)
  } catch (err) {
    return new Response('could not unbundle post data')
  }
}

从边缘函数创建一个 HTTP POST请求:

addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request))
})

/**
 * Create a POST request with body 'key=world'
 * Here, we are assuming that example.com acknowledges the POST request with body key=world
 */
async function fetchAndApply(request) {
  let content = 'key=world'
  let headers = {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
  const init = {
    method: 'POST',
    headers: headers,
    body: content
  }
  const response = await fetch('https://example.com', init)
  console.log('Got response', response)
  return response
}

设置 cookie

您可以使用 CIS 边缘功能设置cookie。

addEventListener('fetch', event => {
  event.respondWith(fetchAndApply(event.request))
})

async function fetchAndApply(request) {
  let response = await fetch(request)

  const randomStuff = `randomcookie=${Math.random()}; Expires=Wed, 21 Oct 2018 07:28:00 GMT; Path='/';`

  // Make the headers mutable by re-constructing the Response.
  response = new Response(response.body, response)
  response.headers.set('Set-Cookie', randomStuff)

  return response
}

已签名请求

一种名为请求签名的通用 URL 认证方法可以在Edge函数中借助Web Crypto API实现。

在此示例中,CIS 使用基于哈希的消息认证码(HMAC)和 SHA-256 摘要算法,对 URL 的路径以及随附的过期时间戳进行验证。 要成功访存已认证的资源,用户代理程序需要使用查询参数提供正确的路径,到期时间戳记和 HMAC。 如果这三个参数中的任何一个被篡改,那么请求将失败。

过期时间戳的真实性由 HMAC 保证,这意味着如果 HMAC 正确,则可以相信用户提供的过期时间戳是正确的,并且当 URL 过期时。 您还可以确定他们持有的 URL 是否已过期。

验证已签名请求

此示例验证了路径名称以 /verify/ 开头的任何请求 URL 的HMAC。

为方便调试,如果 URL 或HMAC无效,或者 URL 过期,则此边缘函数将返回403消息。 在实际应用中,你可能需要返回404。

addEventListener('fetch', event => {
  event.respondWith(verifyAndFetch(event.request))
})

async function verifyAndFetch(request) {
  const url = new URL(request.url)

  // If the path doesn't begin with our protected prefix, just pass the request
  // through.
  if (!url.pathname.startsWith("/verify/")) {
    return fetch(request)
  }

  // Make sure we have the minimum necessary query parameters.
  if (!url.searchParams.has("mac") || !url.searchParams.has("expiry")) {
    return new Response("Missing query parameter", { status: 403 })
  }

  // We'll need some super-secret data to use as a symmetric key.
  const encoder = new TextEncoder()
  const secretKeyData = encoder.encode("my secret symmetric key")
  const key = await crypto.subtle.importKey(
    "raw", secretKeyData,
    { name: "HMAC", hash: "SHA-256" },
    false, [ "verify" ]
  )

  // Extract the query parameters we need and run the HMAC algorithm on the
  // parts of the request we're authenticating: the path and the expiration
  // timestamp.
  const expiry = Number(url.searchParams.get("expiry"))
  const dataToAuthenticate = url.pathname + expiry

  // The received MAC is Base64-encoded, so we have to go to some trouble to
  // get it into a buffer type that crypto.subtle.verify() can read.
  const receivedMacBase64 = url.searchParams.get("mac")
  const receivedMac = byteStringToUint8Array(atob(receivedMacBase64))

  // Use crypto.subtle.verify() to guard against timing attacks. Since HMACs use
  // symmetric keys, we could implement this by calling crypto.subtle.sign() and
  // then doing a string comparison -- this is insecure, as string comparisons
  // bail out on the first mismatch, which leaks information to potential
  // attackers.
  const verified = await crypto.subtle.verify(
    "HMAC", key,
    receivedMac,
    encoder.encode(dataToAuthenticate)
  )

  if (!verified) {
    const body = "Invalid MAC"
    return new Response(body, { status: 403 })
  }

  if (Date.now() > expiry) {
    const body = `URL expired at ${new Date(expiry)}`
    return new Response(body, { status: 403 })
  }

  // We've verified the MAC and expiration time; we're good to pass the request
  // through.
  return fetch(request)
}

// Convert a ByteString (a string whose code units are all in the range
// [0, 255]), to a Uint8Array. If you pass in a string with code units larger
// than 255, their values overflow!
function byteStringToUint8Array(byteString) {
  const ui = new Uint8Array(byteString.length)
  for (let i = 0; i < byteString.length; ++i) {
    ui[i] = byteString.charCodeAt(i)
  }
  return ui
}

生成已签名请求

通常,已签名的请求会以某种频带外方式 (例如,电子邮件) 传递给您,或者您自己生成一个请求 (如果您具有对称密钥)。 您还可以在 Edge 函数中生成已签名的请求。

对于任何以 /generate/ 开头的请求 URL,CIS 会用 /verify/ 替换 /generate/,并在生成的路径上签名,同时用时间戳标记路径,最后在响应正文中返回完整的签名 URL。

addEventListener('fetch', event => {
  const url = new URL(event.request.url)
  const prefix = "/generate/"
  if (url.pathname.startsWith(prefix)) {
    // Replace the "/generate/" path prefix with "/verify/", which we
    // use in the first example to recognize authenticated paths.
    url.pathname = `/verify/${url.pathname.slice(prefix.length)}`
    event.respondWith(generateSignedUrl(url))
  } else {
    event.respondWith(fetch(event.request))
  }
})

async function generateSignedUrl(url) {
  // We'll need some super-secret data to use as a symmetric key.
  const encoder = new TextEncoder()
  const secretKeyData = encoder.encode("my secret symmetric key")
  const key = await crypto.subtle.importKey(
    "raw", secretKeyData,
    { name: "HMAC", hash: "SHA-256" },
    false, [ "sign" ]
  )

  // Signed requests expire after one minute. Note that you could choose
  // expiration durations dynamically, depending on, e.g. the path or a query
  // parameter.
  const expirationMs = 60000
  const expiry = Date.now() + expirationMs
  const dataToAuthenticate = url.pathname + expiry

  const mac = await crypto.subtle.sign(
    "HMAC", key,
    encoder.encode(dataToAuthenticate)
  )

  // `mac` is an ArrayBuffer, so we need to jump through a couple hoops to get
  // it into a ByteString, then a Base64-encoded string.
  const base64Mac = btoa(String.fromCharCode(...new Uint8Array(mac)))

  url.searchParams.set("mac", base64Mac)
  url.searchParams.set("expiry", expiry)

  return new Response(url)
}

以流式方法传递响应

在向 event.respondWith() 发送响应之前,边缘函数脚本无需准备整个响应主体。 通过使用 TransformStream,,您可以在发送响应的前置信息(例如,HTTP 状态行和标题)后,流式传输响应正文。 这种精简有助于 CIS 将访问者首次访问所需的时间以及Edge功能脚本中必须进行的缓冲量降到最低。

如果必须处理或变换大于 Edge 函数内存限制的响应主体,那么最大程度地减少缓冲尤为重要。 在这些情况下,流式方法是唯一可行的实现策略。

缺省情况下,CIS Edge Function 服务已尽可能以流式方式进行传递。 仅当您希望以某种方式修改响应主体,同时保持流式方法行为时,才需要这些 API。 如果 Edge 函数脚本将子请求响应逐字传递回客户机,而不读取其主体,那么主体处理已处于最佳状态。

流式传递

开始使用以下最小传递示例。

addEventListener("fetch", event => {
  event.respondWith(fetchAndStream(event.request))
})

async function fetchAndStream(request) {
  // Fetch from origin server.
  let response = await fetch(request)

  // Create an identity TransformStream (a.k.a. a pipe).
  // The readable side becomes our new response body.
  let { readable, writable } = new TransformStream()

  // Start pumping the body. NOTE: No await!
  streamBody(response.body, writable)

  // ... and deliver our Response while that's running.
  return new Response(readable, response)
}

async function streamBody(readable, writable) {
  let reader = readable.getReader()
  let writer = writable.getWriter()

  while (true) {
    const { done, value } = await reader.read()
    if (done) break
    // Optionally transform value's bytes here.
    await writer.write(value)
  }

  await writer.close()
}

需要注意的一些重要细节:

  • 虽然 streamBody() 是异步函数,但您不希望对其调用 await,以便它不会阻止调用 fetchAndStream() 函数的进度。 此函数在其具有未完成的 reader.read()writer.write() 操作的时间段内继续异步运行。
  • 背压: await 在调用写操作之前执行读操作。 同样,在执行下一次读取操作之前,请 await 写入操作。 遵循此模式会将反压传播到源。
  • 完成:最后请致电 writer.close(),这表示您已完成此响应正文的编写,并通知Edge功能运行时。 调用后,streamBody() 将终止-如果此行为不理想,请将其返回的 promise 传递给 FetchEvent.waitUntil()。 如果您的脚本从未调用 writer.close(),则运行时显示的正文会截断,但可能仍能按预期运行。

聚集和以流式方法传递多个请求

这个用例与聚合多个请求的方案类似,但这次你会在验证每个子请求成功后立即开始编写响应——无需等待响应正文。

addEventListener('fetch', event => {
    event.respondWith(fetchAndApply(event.request))
})

/**
 * Make multiple requests,
 * aggregate the responses and
 * stream it back as a single response.
 */
async function fetchAndApply(request) {
  const requestInit = {
    headers: { "Authorization": "XXXXXX" }
  }
  const fetches = [
    "https://api.coinbase.com/v2/prices/BTC-USD/spot",
    "https://api.coinbase.com/v2/prices/ETH-USD/spot",
    "https://api.coinbase.com/v2/prices/LTC-USD/spot"
  ].map(url => fetch(url, requestInit))

  // Wait for each fetch() to complete.
  let responses = await Promise.all(fetches)

  // Make sure every subrequest succeeded.
  if (!responses.every(r => r.ok)) {
    return new Response(null, { status: 502 })
  }

  // Create a pipe and stream the response bodies out
  // as a JSON array.
  let { readable, writable } = new TransformStream()
  streamJsonBodies(responses.map(r => r.body), writable)

  return new Response(readable)
}

async function streamJsonBodies(bodies, writable) {
  // We're presuming these bodies are JSON, so we
  // concatenate them into a JSON array. Since we're
  // streaming, we can't use JSON.stringify(), but must
  // instead manually write an initial '[' before the
  // bodies, interpolate ',' between them, and write a
  // terminal ']' after them.

  let writer = writable.getWriter()
  let encoder = new TextEncoder()

  await writer.write(encoder.encode("[\n"))

  for (let i = 0; i < bodies.length; ++i) {
    if (i > 0) {
      await writer.write(encoder.encode(",\n"))
    }
    writer.releaseLock()
    await bodies[i].pipeTo(writable, { preventClose: true })
    writer = writable.getWriter()
  }

  await writer.write(encoder.encode("]"))

  await writer.close()
}

运行时期望在 TransformStream 的可读端接收 TypedArray。 因此,您从不将字符串传递到 writer.write(),仅传递到 Uint 8Arrays。 如果需要编写字符串,请使用 TextEncoder。

具有 Edge 函数的定制负载均衡器

负载均衡可帮助您保持所托管 Web 站点的可伸缩性和可靠性。 您可以使用 Edge 函数来创建定制负载均衡器,这些负载均衡器旨在满足您的特定需求。

const US_HOSTS = [
  "0.us.example.com",
  "1.us.example.com",
  "2.us.example.com"
];

const IN_HOSTS = [
  "0.in.example.com",
  "1.in.example.com",
  "2.in.example.com"
];

var COUNTRIES_MAP = {
  IN: IN_HOSTS,
  PK: IN_HOSTS,
  BD: IN_HOSTS,
  SL: IN_HOSTS,
  NL: IN_HOSTS
}
addEventListener('fetch', event => {
  var url = new URL(event.request.url);

  var countryCode = event.request.headers.get('CF-IPCountry');
  var hostnames = US_HOSTS;
  if (COUNTRIES_MAP[countryCode]) {
    hostnames = COUNTRIES_MAP[countryCode];
  }
  // Randomly pick the next host
  var primary = hostnames[getRandomInt(hostnames.length)];

  var primaryUrl = new URL(event.request.url);
  primaryUrl.hostname = hostnames[primary];

  // Fallback if there is no response within timeout
  var timeoutId = setTimeout(function() {
    var backup;
    do {
        // Naive solution to pick a backup host
        backup = getRandomInt(hostnames.length);
    } while(backup === primary);

    var backupUrl = new URL(event.request.url);
    backupUrl.hostname = hostnames[backup];

    event.respondWith(fetch(backupUrl));
  }, 2000 /* 2 seconds */);

  fetch(primaryUrl)
    .then(function(response) {
        clearTimeout(timeoutId);
        event.respondWith(response);
    });
});

function getRandomInt(max) {
  return Math.floor(Math.random() * max);
}

使用访存进行高速缓存

通过在访存请求中设置 TTL,定制高速缓存密钥和高速缓存头,确定如何对资源进行高速缓存。

async function handleRequest(request) {
  const url = new URL(request.url)

  // Only use the path for the cache key, removing query strings
  // and always store using HTTPS, for example, https://www.example.com/file-uri-here
  const someCustomKey = `https://${url.hostname}${url.pathname}`

  let response = await fetch(request, {
    cf: {
      // Always cache this fetch regardless of content type
      // for a max of 5 seconds before revalidating the resource
      cacheTtl: 5,
      cacheEverything: true,
      //Enterprise only feature, see Cache API for other plans
      cacheKey: someCustomKey,
      },
    })
    // Reconstruct the Response object to make its headers mutable.
    response = new Response(response.body, response)

    //Set cache control headers to cache on browser for 25 minutes
    response.headers.set("Cache-Control", "max-age=1500")
    return response
}

addEventListener("fetch", event => {
  return event.respondWith(handleRequest(event.request))
})

高速缓存 HTML 资源

// Force CIS to cache an asset
fetch(event.request, { cf: { cacheEverything: true } })

将高速缓存级别设置为“高速缓存所有内容”将覆盖资产的缺省“可高速缓存性”。 对于 TTL,CIS 仍依赖于源设置的头。

定制高速缓存密钥

此功能仅适用于企业客户。

请求的高速缓存键用于确定两个请求是否“相同”以进行高速缓存。 如果请求具有与先前某个请求相同的高速缓存密钥,那么我们可以为这两个请求提供相同的高速缓存响应。

// Set cache key for this request to "some-string".
fetch(event.request, { cf: { cacheKey: "some-string" } })

CIS 根据请求的 计算缓存密钥,但出于缓存目的,您可能希望将不同的URL视为相同的URL。URL 例如,如果您的网站内容同时托管在 Amazon S3 和 Google Cloud Storage (您在两个地方都有相同的内容),然后您使用边缘函数在两者之间随机平衡。 但是,您不希望高速缓存内容的两个副本。 您可以使用自定义缓存键,根据原始请求 URL 而不是子请求 URL 进行缓存。

addEventListener("fetch", (event) => {
  let url = new URL(event.request.url)
  if (Math.random() < 0.5) {
    url.hostname = "example.s3.amazonaws.com"
  }
  else {
    url.hostname = "example.storage.googleapis.com"
  }

  let request = new Request(url, event.request)
  event.respondWith(
    fetch(request, {
      cf: { cacheKey: event.request.url },
    })
  )
})

请记住,代表不同区域运行的边缘功能不会影响彼此的缓存。 仅当您在自己的区域中发出请求 (在先前的示例中,event.request.url 是存储的密钥) 或向不在 CIS上的主机发出请求时,才能覆盖高速缓存密钥。 当您向另一个 CIS 区域(例如,属于不同 CIS 客户的区域)提出请求时,该区域将完全控制其内容在 CIS 中的缓存方式;您无法覆盖它。

基于源响应代码的覆盖

此功能仅适用于企业客户。

// Force response to be cached for 86400 seconds for 200 status
// codes, 1 second for 404, and do not cache 500 errors.
fetch(request, {
  cf: { cacheTtlByStatus: { "200-299": 86400, 404: 1, "500-599": 0 } },
})

此选项是 cacheTtl 功能部件的版本,该功能部件根据响应的状态码选择 TTL,并且不会自动设置 cacheEverything: true。 如果对此请求的响应具有匹配的状态码,那么 CIS 将在指示的时间进行高速缓存,并覆盖由源发送的高速缓存伪指令。

TTL 解释

以下 TTL 值由 CIS解释。

  • 正值: 指示 CIS 应该为其高速缓存资产的时间长度 (以秒计)。
  • 0: 资产已高速缓存但立即到期 (每次从源重新验证)。
  • -1 或任何负值: 指示 CIS 完全不高速缓存。

高速缓存 API

使用 CIS 高速缓存 API 进行高速缓存。 此示例还可以对 POST 请求进行高速缓存。

const someOtherHostname = "my.herokuapp.com"

async function handleRequest(event) {
  const request = event.request
  const cacheUrl = new URL(request.url)

  // Hostname for a different zone
  cacheUrl.hostname = someOtherHostname

  const cacheKey = new Request(cacheUrl.toString(), request)
  const cache = caches.default

  // Get this request from this zone's cache
  let response = await cache.match(cacheKey)

  if (!response) {
    //If not in cache, get it from origin
    response = await fetch(request)

    // Must use Response constructor to inherit all of response's fields
    response = new Response(response.body, response)

    // Cache API respects Cache-Control headers. Setting max-age to 10
    // will limit the response to be in cache for 10 seconds max
    response.headers.append("Cache-Control", "max-age=10")

    // Store the fetched response as cacheKey
    // Use waitUntil so computational expensive tasks don"t delay the response
    event.waitUntil(cache.put(cacheKey, response.clone()))
  }
  return response
}

async function sha256(message) {
  // encode as UTF-8
  const msgBuffer = new TextEncoder().encode(message)

  // hash the message
  const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer)

  // convert ArrayBuffer to Array
  const hashArray = Array.from(new Uint8Array(hashBuffer))

  // convert bytes to hex string
  const hashHex = hashArray.map(b => ("00" + b.toString(16)).slice(-2)).join("")
  return hashHex
}

async function handlePostRequest(event) {
  const request = event.request
  const body = await request.clone().text()
  const hash = await sha256(body)
  const cacheUrl = new URL(request.url)

  // Store the URL in cache by prepending the body's hash
  cacheUrl.pathname = "/posts" + cacheUrl.pathname + hash

  // Convert to a GET to be able to cache
  const cacheKey = new Request(cacheUrl.toString(), {
    headers: request.headers,
    method: "GET",
  })

  const cache = caches.default

  //Find the cache key in the cache
  let response = await cache.match(cacheKey)

  // Otherwise, fetch response to POST request from origin
  if (!response) {
    response = await fetch(request)
    event.waitUntil(cache.put(cacheKey, response.clone()))
  }
  return response
}

addEventListener("fetch", event => {
  try {
    const request = event.request
    if (request.method.toUpperCase() === "POST")
      return event.respondWith(handlePostRequest(event))
    return event.respondWith(handleRequest(event))
  } catch (e) {
    return event.respondWith(new Response("Error thrown " + e.message))
  }
})