Casos de prácticos de Edge functions

Los casos de uso siguientes se proporcionan únicamente como ejemplos y no están pensados para su duplicación exacta en el entorno.

Tenga cuidado cuando pruebe cualquiera de los siguientes códigos, ya que podría causar una interrupción del servicio.

Pruebas A/B

Puede crear una CIS Edge Function para controlar las pruebas 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
  }
}

Añadir una cabecera de respuesta

Para modificar las cabeceras de respuesta, primero haga una copia de la respuesta para que pueda hacerlo mutable. A continuación, puede utilizar la interfaz Cabeceras para añadir, modificar o eliminar cabeceras.

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;
}

Agregar varias solicitudes

Este ejemplo realiza varias solicitudes a diferentes puntos finales de la API, agrega las respuestas y las devuelve como una única respuesta.

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)
}

Direccionamiento condicional

La forma más sencilla de ofrecer contenidos diferentes en función del dispositivo utilizado es reescribir la dirección URL de la solicitud en función de la condición que le interese. Consulte los ejemplos siguientes.

Tipo de dispositivo

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)
}

Cabeceras personalizadas

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)
}

Respuestas sin origen

Puede devolver respuestas directamente desde el servidor edge. No es necesario enviar una solicitud a su origen.

Ignorar las solicitudes HTTP POST y PUT

Ignorar las solicitudes HTTP POST y PUT. Este fragmento de código permite que todas las demás solicitudes pasen a través del origen.

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)
}

Denegar una araña o un rastreador

Proteja su origen de las arañas o rastreadores no deseados. En este caso, si el agente de usuario es “annoying-robot”, la Edge Function devuelve la respuesta en lugar de enviar la solicitud a su origen.

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)
}

Impedir que se conecte una IP específica

Direcciones IP de la lista de elementos bloqueados. Este fragmento de código impide que una IP específica (en este caso 225.0.0.1) se conecte al origen.

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)
}

Solicitudes POST

Leer el contenido de una solicitud 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')
  }
}

Crear una solicitud HTTP POST an partir de una Edge Function:

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
}

Establecer una cookie

Puede establecer cookies utilizando las funciones de CIS Edge.

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
}

Solicitudes firmadas

Se puede implementar un método frecuente de autenticación de URL conocido como firma de solicitud en una Edge Function con la API de Web Crypto.

En el ejemplo que se presenta aquí, CIS autentica la ruta de un URL junto con una marca de tiempo de caducidad adjunta utilizando un Código de Autenticación de Mensaje Basado en Hash (HMAC) con un algoritmo de compendio SHA-256. Para captar correctamente un recurso autenticado, el agente de usuario debe proporcionar la vía de acceso correcta, la indicación de fecha y hora de caducidad y HMAC utilizando los parámetros de consulta. Si alguno de estos tres parámetros se manipulan indebidamente, la solicitud falla.

La autenticidad de la marca de tiempo de caducidad está cubierta por el HMAC, lo que significa que puede confiar en que la marca de tiempo proporcionada por el usuario es correcta si el HMAC lo es, y cuando caduque el URL. También puede determinar si un certificado de autenticidad ( URL ) que está en su poder ha caducado.

Verificación de solicitudes firmadas

Este ejemplo verifica el HMAC para cualquier solicitud URL cuyo nombre de ruta empiece por /verify/.

Para facilitar la depuración, esta función de Edge devuelve un mensaje 403 si URL o HMAC no son válidos, o si URL ha caducado. Puede que desee devolver 404 en una implementación real.

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
}

Generación de solicitudes firmadas

Normalmente, las solicitudes firmadas se le entregan de alguna manera fuera de banda, como un correo electrónico, o usted mismo genera una si dispone de la clave simétrica. También puede generar las solicitudes firmadas en una función Edge.

Para cualquier solicitud URL que empiece por /generate/, CIS sustituye /generate/ por /verify/, firma la ruta resultante con su marca de tiempo y devuelve la URL completa y firmada en el cuerpo de la respuesta.

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)
}

Respuestas en streaming

Un script de función Edge no necesita preparar todo su cuerpo de respuesta antes de entregar una Respuesta a event.respondWith(). Al utilizar un TransformStream,, puede transmitir un cuerpo de respuesta después de enviar el asunto de la respuesta (por ejemplo, la línea de estado y los encabezados de HTTP ). Esta optimización ayuda a CIS a minimizar el tiempo de respuesta de los visitantes y la cantidad de almacenamiento en búfer que debe realizarse en el script de la función Edge.

Es especialmente importante minimizar el almacenamiento intermedio si procesa o transforma cuerpos de respuesta más grandes que el límite de memoria de la Edge Function. En estos casos, el streaming es la única estrategia de implementación viable.

El servicio CIS Edge Function ya envía por streaming de forma predeterminada siempre que es posible. Estas API sólo son necesarias si desea modificar el cuerpo de respuesta de algún modo, mientras mantiene el comportamiento de la modalidad continua. Si el script de la función Edge devuelve las respuestas de subsolicitud al cliente textualmente, sin leer sus cuerpos, el manejo del cuerpo ya es óptimo.

Paso a través de streaming

Empiece con el siguiente ejemplo de paso a través mínimo.

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()
}

Algunos detalles importantes a tener en cuenta:

  • Aunque streamBody() es una función asíncrona, no se desea llamar a await para que no bloquee el progreso de la llamada a la función fetchAndStream(). La función continúa ejecutándose de forma asíncrona durante el periodo en el que tiene una operación reader.read() o writer.write() pendiente.
  • Contrapresión: await la operación de lectura antes de llamar a la operación de grabación. Del mismo modo, await la operación de escritura antes de llamar a la siguiente operación de lectura. Al seguir este patrón, se propaga la resistencia hacia el origen.
  • Finalización: llame a writer.close() al final, lo que indica al tiempo de ejecución de la función Edge que ha terminado de escribir este cuerpo de respuesta. Después de ser llamado, streamBody() termina - si este comportamiento no es deseable, pase su promesa devuelta a FetchEvent.waitUntil(). Si el script nunca llama a writer.close(), el cuerpo aparece truncado en base al tiempo de ejecución, aunque puede seguir funcionando normalmente.

Agregar varias solicitudes

Este caso de uso es similar a la receta de agregar múltiples peticiones, pero esta vez se empieza a escribir la respuesta tan pronto como se verifica que cada subpetición ha tenido éxito - no hay necesidad de esperar a los cuerpos de respuesta.

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()
}

El tiempo de ejecución espera recibir TypedArrays en el lado legible de TransformStream. Por lo tanto, nunca pasa una serie a writer.write(), sólo Uint 8Arrays. Si tiene que escribir una serie, utilice un TextEncoder.

Equilibrador de carga personalizado con Edge Functions

El equilibrio de carga le ayuda a mantener la escalabilidad y fiabilidad de los sitios web que aloja. Puede utilizar las funciones Edge para crear equilibradores de carga personalizados diseñados para satisfacer sus necesidades específicas.

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);
}

Almacenamiento en memoria caché utilizando la captación

Determine cómo almacenar en memoria caché un recurso estableciendo los TTL, las claves de memoria caché personalizadas y las cabeceras de memoria caché en una solicitud de captación.

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))
})

Almacenamiento en memoria caché de recursos HTML

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

Establecer el nivel de memoria caché en Almacenar todo en la memoria caché altera temporalmente la "cacheabilidad" predeterminada del activo. Para TTL, CIS sigue dependiendo de las cabeceras establecidas por el origen.

Claves de memoria caché personalizadas

Esta característica solo está disponible en los clientes del plan de empresa.

La clave de caché de una solicitud es lo que determina si dos solicitudes son "iguales" a efectos de caché. Si una solicitud tiene la misma clave de memoria caché que una solicitud anterior, se puede ofrecer la misma respuesta en memoria caché para ambas.

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

CIS calcula la clave de caché para una petición basándose en la dirección URL de la petición, pero es posible que desee que diferentes URL se traten como si fueran la misma a efectos de caché. Por ejemplo, si el contenido de su sitio web está alojado tanto en Amazon S3 como en Google Cloud Storage (tiene el mismo contenido en ambos sitios), y luego utiliza una función de borde para equilibrar aleatoriamente entre los dos. Sin embargo, no querrá almacenar en caché dos copias de su contenido. Puede utilizar claves de caché personalizadas para almacenar en caché en función de la solicitud original URL en lugar de la subpetición 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 },
    })
  )
})

Recuerda que las funciones de borde que operan en nombre de zonas diferentes no pueden afectar a la caché de las demás. Puede anular las claves de caché sólo cuando realice peticiones dentro de su propia zona (en el ejemplo anterior event.request.url era la clave almacenada), o peticiones a hosts que no estén en CIS. Cuando realiza una solicitud a otra zona de CIS (por ejemplo, una zona que pertenece a un cliente diferente de CIS ), esa zona controla completamente cómo se almacena en caché su propio contenido dentro de CIS; no puede anularlo.

Alteración temporal basada en código de respuesta de origen

Esta característica solo está disponible en los clientes del plan de empresa.

// 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 } },
})

Esta opción es una versión de la característica cacheTtl que elige un TTL basado en el código de estado de la respuesta y no establece automáticamente cacheEverything: true. Si la respuesta a esta solicitud tiene un código de estado que coincide, CIS almacena en memoria caché durante el tiempo indicado y altera temporalmente las directivas de memoria caché enviadas por el origen.

Interpretación TTL

Los siguientes valores TTL son interpretados por CIS.

  • Valores positivos: indique en segundos durante cuánto tiempo CIS debe almacenar en memoria caché el activo.
  • 0: el activo se almacena en memoria caché caduca inmediatamente (revalidarse desde el origen cada vez).
  • -1 o cualquier valor negativo: indica a CIS que no se almacene en memoria caché en absoluto.

API de memoria caché

Almacene en memoria caché utilizando la API de memoria caché CIS. Este ejemplo también almacena en memoria caché solicitudes 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))
  }
})