Cas d'utilisation de fonction Edge

Les cas d'utilisation suivants sont fournis uniquement à titre d'exemples et ne sont pas destinés à une duplication exacte dans votre environnement.

Soyez prudent lorsque vous testez l'un des codes suivants, car il pourrait entraîner une interruption du service.

Test A/B

Vous pouvez créer une fonction Edge CIS pour contrôler les tests 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
  }
}

Ajout d'un en-tête de réponse

Pour modifier les en-têtes de réponse, faites d'abord une copie de la réponse afin de la rendre modifiable. Vous pouvez ensuite utiliser l'interface des en-têtes pour ajouter, modifier ou supprimer des en-têtes.

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

Agrégation de plusieurs demandes

Cet exemple effectue plusieurs demandes auprès de différents points d'extrémité de l'API, regroupe les réponses et les renvoie sous la forme d'une réponse unique.

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

Routage conditionnel

Le moyen le plus simple de fournir un contenu différent en fonction de l'appareil utilisé est de réécrire le site URL de la requête en fonction de la condition qui vous intéresse. Voir les exemples suivants.

Type de dispositif

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

En-têtes personnalisés

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

Réponses sans origine

Vous pouvez renvoyer les réponses directement à partir du bord (Edge). Il n'est pas nécessaire d'envoyer une demande à votre origine.

Ignorer les demandes HTTP POST et PUT

Ignorez les demandes HTTP POST et PUT. Ce fragment de code permet à toutes les autres demandes de traverser à l'origine.

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

Refuser un moteur de balayage ou un moteur d'exploration

Protégez votre origine des moteurs de balayage ou d'exploration indésirables. Dans ce cas, si l'agent utilisateur est un "annoying_robot", la fonction Edge renvoie la réponse au lieu d'envoyer la demande à l'origine.

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

Empêcher une adresse IP spécifique de se connecter

Ajoutez des adresses IP à la liste rouge. Ce bout de code empêche une IP spécifique (dans ce cas 225.0.0.1) de se connecter à l'origine.

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

Demandes POST

Lecture du contenu d'une demande POST HTTP :

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

Création d'une requête POST HTTP à partir d'une fonction Edge :

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
}

Définition d'un cookie

Vous pouvez définir des cookies en utilisant les fonctions 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
}

Demandes signées

Une méthode d'authentification d'URL commune appelée signature de requête peut être implémentée dans une fonction Edge à l'aide de l'API Crypto Web.

Dans l'exemple présenté ici, CIS authentifie le chemin d'accès à URL ainsi que l'horodatage qui l'accompagne en utilisant un code d'authentification des messages basé sur le hachage (HMAC) avec un algorithme de condensé SHA-256. Pour extraire une ressource authentifiée, l'agent utilisateur doit fournir le chemin d'accès, l'horodatage d'expiration et le code HMAC corrects à l'aide des paramètres de requête. Si l'un de ces trois paramètres est falsifié, la demande échoue.

L'authenticité de l'horodatage d'expiration est couverte par le HMAC, ce qui signifie que vous pouvez être sûr que l'horodatage fourni par l'utilisateur est correct si le HMAC l'est, et lorsque l' URL e expire. Vous pouvez également déterminer si un certificat de conformité ( URL ) en leur possession est expiré.

Vérification des demandes signées

Cet exemple vérifie le HMAC pour toute requête URL dont le nom de chemin commence par /verify/.

Pour faciliter le débogage, cette fonction Edge renvoie un message 403 si le URL ou le HMAC n'est pas valide, ou si le URL a expiré. Vous souhaiterez peut-être renvoyer le 404 dans une implémentation réelle.

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
}

Génération de demandes signées

En règle générale, les demandes signées vous sont remises d'une manière extrabudgétaire, par exemple par courrier électronique, ou vous en générez une vous-même si vous disposez de la clé symétrique. Vous pouvez également générer les demandes signées dans une fonction Edge.

Pour toute demande URL qui commence par /generate/, CIS remplace /generate/ par /verify/, signe le chemin résultant avec son horodatage, et renvoie la version complète et signée de URL dans le corps de la réponse.

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

Réponses en continu

Un script de fonction Edge n'a pas besoin de préparer l'ensemble de son corps de réponse avant d'envoyer une réponse à event.respondWith(). En utilisant un TransformStream,, vous pouvez diffuser un corps de réponse après avoir envoyé l'en-tête de la réponse (par exemple, la ligne d'état et les en-têtes d' HTTP ). Cette rationalisation aide l' CIS, à minimiser le temps de réponse du visiteur et la quantité de mise en mémoire tampon qui doit être effectuée dans le script de la fonction Edge.

Réduire la mise en mémoire tampon est particulièrement important si vous devez traiter ou transformer des corps de réponse supérieurs à la limite de mémoire de la fonction Edge. Dans ces cas, la diffusion en continu est la seule stratégie de mise en oeuvre réalisable.

Le service CIS Edge Function est déjà diffusé par défaut dans la mesure du possible. Ces API ne sont nécessaires que si vous souhaitez modifier le corps de la réponse d'une manière ou d'une autre, tout en conservant le comportement de diffusion en flux. Si votre script de fonction Edge renvoie les réponses de sous-demande au client textuellement, sans lire leur corps, la gestion du corps est déjà optimale.

Passe-système en continu

Commencez avec l'exemple de passe-système minimal suivant.

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

D'importants détails doivent être pris en compte :

  • Bien que streamBody() soit une fonction asynchrone, vous ne voulez pas appeler await sur cette fonction afin qu'elle ne bloque pas la progression de l'appel à la fonction fetchAndStream(). La fonction continue de s'exécuter de manière asynchrone pendant la période au cours de laquelle elle a une opération reader.read() ou writer.write() en attente.
  • Contre-pression: await opération de lecture avant d'appeler l'opération d'écriture. De même, await l'opération d'écriture avant d'appeler l'opération de lecture suivante. Ce masque propage la contre-pression à l'origine.
  • Achèvement : appeler writer.close() à la fin, ce qui signale à l'exécution de la fonction Edge que vous avez fini d'écrire ce corps de réponse. Après avoir été appelé, streamBody() se termine - si ce comportement n'est pas souhaitable, transmettez la promesse renvoyée à FetchEvent.waitUntil(). Si votre script n'appelle jamais writer.close(), le corps apparaît tronqué au moment de l'exécution, mais il peut continuer de fonctionner comme prévu.

Agrégation et diffusion de plusieurs demandes

Ce cas d'utilisation est similaire à la recette de l'agrégation de plusieurs requêtes, mais cette fois, vous commencez à écrire la réponse dès que vous avez vérifié que chaque sous-requête a abouti - il n'est pas nécessaire d'attendre les corps de réponse.

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

Le moteur d'exécution s'attend à recevoir TypedArrays du côté lisible du TransformStream. Par conséquent, vous ne transmettez jamais une chaîne à writer.write(), uniquement Uint 8Arrays. Si vous devez écrire une chaîne, utilisez un TextEncoder.

Equilibreur de charge personnalisé avec fonctions Edge

L'équilibrage de charge vous aide à maintenir l'évolutivité et la fiabilité des sites web que vous hébergez. Vous pouvez utiliser les fonctions Edge pour créer des répartiteurs de charge personnalisés, conçus pour répondre à vos besoins spécifiques.

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

Mise en cache à l'aide de la fonction d'extraction

Déterminez comment mettre en cache une ressource en définissant des durées de vie (TTL), des clés de mémoire cache personnalisées et des en-têtes de cache dans une demande d'extraction.

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

Mise en cache des ressources HTML

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

La définition du paramètre "Cache Everything" comme niveau de cache remplace le niveau de mise en cache par défaut de l'actif. Pour la durée de vie (TTL), CIS utilise toujours les en-têtes définis par l'origine.

Clés de mémoire cache personnalisées

Cette fonction ne peut être utilisée que par des clients d'entreprise.

La clé de cache d'une demande est ce qui détermine si deux demandes sont "identiques" à des fins de mise en cache. Si une demande possède la même clé de mémoire cache qu'une demande précédente, la même réponse mise en cache peut être transmise pour les deux.

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

CIS calcule la clé de cache d'une requête en se basant sur l'adresse URL, mais il se peut que vous souhaitiez que des URL différentes soient traitées comme si elles étaient identiques à des fins de mise en cache. Par exemple, si le contenu de votre site web est hébergé à la fois sur Amazon S3 et Google Cloud Storage (vous avez le même contenu aux deux endroits), et que vous utilisez ensuite une fonction d'arête pour équilibrer aléatoirement les deux. Cependant, vous ne voulez pas mettre en cache deux copies de votre contenu. Vous pouvez utiliser des clés de cache personnalisées pour mettre en cache la demande originale URL plutôt que la sous-demande 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 },
    })
  )
})

N'oubliez pas que les fonctions de bordure qui opèrent pour le compte de différentes zones ne peuvent pas affecter le cache de l'autre. Vous ne pouvez remplacer les clés de cache que lorsque vous effectuez des requêtes dans votre propre zone (dans l'exemple précédent, event.request.url était la clé stockée), ou des requêtes vers des hôtes qui ne se trouvent pas sur CIS. Lorsque vous faites une requête vers une autre zone d' CIS s (par exemple, une zone qui appartient à un autre client d' CIS ), cette zone contrôle entièrement la façon dont son propre contenu est mis en cache dans CIS; vous ne pouvez pas la remplacer.

Remplacement en fonction du code de réponse d'origine

Cette fonction ne peut être utilisée que par des clients d'entreprise.

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

Cette option est une version de la fonction cacheTtl qui choisit une durée de vie (TTL) en fonction du code d'état de la réponse et ne définit pas automatiquement cacheEverything: true. Si la réponse à cette demande possède un code d'état concordant, CIS effectue la mise en cache pendant la durée indiquée et remplace les directives de cache envoyées par l'origine.

Interprétation de la durée de vie (TTL)

Les valeurs TTL suivantes sont interprétées par CIS.

  • Valeurs positives : Indiquez, en secondes, pendant combien de tempsCIS doit mettre en cache l'actif.
  • 0 : L'actif est mis en cache mais arrive à expiration immédiatement (revalidation à partir de l'origine à chaque fois).
  • -1 ou toute autre valeur négative : Demande à CIS de ne pas effectuer de mise en cache.

API de cache

Mise en cache à l'aide de l'API de cache CIS. Cet exemple permet également de mettre en cache des demandes 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))
  }
})