Casi di utilizzo delle funzioni edge

I seguenti casi di utilizzo vengono forniti esclusivamente come esempi e non sono destinati alla duplicazione esatta nel proprio ambiente.

Prestare attenzione quando si esegue il test di uno qualsiasi dei seguenti codici perché potrebbe causare un'interruzione del servizio.

Test A/B

Puoi creare una funzione edge CIS per controllare i test 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
  }
}

Aggiunta di un'intestazione della risposta

Per modificare le intestazioni della risposta, creare prima una copia della risposta in modo da renderla modificabile. Quindi, si può usare l'interfaccia Intestazioni per aggiungere, modificare o rimuovere le intestazioni.

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

Aggregazione di più richieste

Questo esempio effettua più richieste a diversi endpoint API, aggrega le risposte e le invia come un'unica risposta.

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

Instradamento condizionale

Il modo più semplice per fornire contenuti diversi in base al dispositivo utilizzato è riscrivere l'indirizzo URL della richiesta in base alla condizione che interessa. Vedi i seguenti esempi.

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

Intestazioni personalizzate

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

Risposte senza origine

Puoi restituire le risposte direttamente dall'edge. Non c'è bisogno di inviare una richiesta alla tua origine.

Ignora le richieste HTTP POST e PUT

Ignora le richieste HTTP POST e PUT. Questo frammento consente a tutte le altre richieste di passare attraverso 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)
}

Blocca uno spider o un crawler

Proteggi la tua origine da spider o crawler non desiderati. In questo caso, se l'agent utente (user-agent) è “annoying-robot”, la funzione edge restituisce la risposta invece di inviare la richiesta all'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)
}

Impedisci il collegamento di uno specifico IP

Indirizzi IP blocklist. Questo frammento di codice impedisce a uno specifico IP (in questo caso 225.0.0.1) di connettersi all'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)
}

Richieste Post

Lettura del contenuto di una richiesta POST di 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')
  }
}

Creazione di una richiesta HTTP POST da una funzione 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
}

Impostazione di un cookie

È possibile impostare i cookie utilizzando le funzioni di 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
}

Richieste firmate

Un metodo di autenticazione comune di URL, noto come firma delle richieste, può essere implementato in una funzione Edge con l'aiuto di Web Crypto API.

Nell'esempio qui presentato, CIS autentica il percorso di un URL insieme al relativo timestamp di scadenza utilizzando un Hash-based Message Authentication Code (HMAC) con un algoritmo di digest SHA-256. Per recuperare correttamente una risorsa autenticata, l'agent utente deve fornire il percorso corretto, la data/ora di scadenza e HMAC utilizzando i parametri di query. Se uno di questi tre parametri viene alterato, la richiesta ha esito negativo.

L'autenticità del timestamp di scadenza è coperta dall'HMAC, il che significa che puoi fare affidamento sulla correttezza del timestamp fornito dall'utente se l'HMAC è corretto e quando l' URL a scade. È inoltre possibile determinare se un certificato di ispezione ( URL ) in loro possesso è scaduto.

Verifica delle richieste firmate

Questo esempio verifica l'HMAC per qualsiasi richiesta URL in cui il nome del percorso inizia con /verify/.

Per comodità di debug, questa funzione Edge restituisce un messaggio 403 se URL o HMAC non sono validi o se URL è scaduto. Si potrebbe voler restituire 404 in un'implementazione reale.

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
}

Generazione delle richieste firmate

In genere, le richieste firmate vengono consegnate in modo fuori banda, come ad esempio un'email, oppure ne viene generata una da soli, se si dispone della chiave simmetrica. È inoltre possibile generare le richieste firmate in una funzione Edge.

Per qualsiasi richiesta URL che inizia con /generate/, CIS sostituisce /generate/ con /verify/, firma il percorso risultante con il suo timestamp e restituisce l'intero percorso firmato URL nel corpo della risposta.

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

Risposte in streaming

Uno script di funzione Edge non ha bisogno di preparare l'intero corpo della risposta prima di fornire una risposta a event.respondWith(). Utilizzando un TransformStream, è possibile trasmettere in streaming un corpo di risposta dopo aver inviato l'intestazione della risposta (ad esempio, la riga di stato e le intestazioni di HTTP ). Questa ottimizzazione aiuta l' CIS e a ridurre al minimo il tempo di attesa del visitatore per il primo byte e la quantità di buffering che deve essere eseguita nello script della funzione Edge.

La riduzione del buffer dei dati è particolarmente importante se devi elaborare o trasformare corpi di risposte che superano il limite di memoria della funzione edge. In questi casi, lo streaming è l'unica strategia di implementazione praticabile.

Il servizio della funzione edge CIS utilizza già lo streaming ovunque possibile, per impostazione predefinita. Queste API sono necessarie solo se si desidera modificare il corpo della risposta in qualche modo, mentre si mantiene il comportamento del flusso. Se lo script della funzione Edge passa le risposte della richiesta secondaria al client alla lettera, senza leggerne il corpo, la gestione del corpo è già ottimale.

Pass-Through in streaming

Inizia con il seguente esempio di pass-through minimo.

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

Alcuni dettagli importanti da notare:

  • Sebbene streamBody() sia una funzione asincrona, non si desidera richiamare await su di essa in modo che non blocchi l'avanzamento della chiamata della funzione fetchAndStream(). La funzione continua ad essere eseguita in modo asincrono per il periodo in cui ha un'operazione reader.read() o writer.write() in sospeso.
  • Backpressure: await l'operazione di lettura prima di richiamare l'operazione di scrittura. Allo stesso modo, await l'operazione di scrittura prima di chiamare l'operazione di lettura successiva. Seguendo questo modello, si propaga la contropressione all'origine.
  • Completamento: chiamare writer.close() alla fine, per segnalare al runtime della funzione Edge che la scrittura del corpo della risposta è terminata. Dopo essere stato richiamato, streamBody() termina - se questo comportamento non è desiderabile, passa la sua promessa restituita a FetchEvent.waitUntil(). Se lo script non chiama mai writer.close(), il corpo appare troncato al runtime, anche se potrebbe continuare a funzionare come previsto.

Aggrega e invia in streaming più richieste

Questo caso d'uso è simile alla ricetta dell'aggregazione di più richieste, ma questa volta si inizia a scrivere la risposta non appena si verifica che ogni sotto-richiesta è andata a buon fine, senza dover aspettare i corpi delle risposte.

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

Il runtime prevede di ricevere TypedArrays sul lato leggibile di TransformStream. Pertanto, non si passa mai una stringa a writer.write(), solo Uint 8Arrays. Se devi scrivere una stringa, utilizza un TextEncoder.

Programma di bilanciamento del carico personalizzato con funzioni Edge

Il bilanciamento del carico consente di mantenere la scalabilità e l'affidabilità dei siti Web ospitati. Puoi utilizzare le funzioni Edge per creare programmi di bilanciamento del carico personalizzati progettati per soddisfare le tue esigenze specifiche.

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

Memorizzazione nella cache utilizzando il recupero

Determinare come memorizzare nella cache una risorsa impostando TTL, chiavi cache personalizzate e intestazioni cache in una richiesta di richiamo.

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

Memorizzazione nella cache delle risorse HTML

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

L'impostazione del livello cache su Cache Everything sovrascrive la "memorizzazione nella cache" predefinita dell'asset. Per TTL, CIS si basa ancora su intestazioni configurate dall'origine.

Chiavi cache personalizzate

Questa funzione è disponibile solo per i clienti aziendali.

La chiave della cache di una richiesta è ciò che determina se due richieste sono "le stesse" per scopi di memorizzazione nella cache. Se una richiesta ha la stessa chiave di cache di una richiesta precedente, possiamo fornire la stessa risposta memorizzata nella cache per entrambi.

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

CIS calcola la chiave della cache per una richiesta in base all' URL, ma potresti volere che URL diversi vengano trattati come se fossero gli stessi ai fini della memorizzazione nella cache. Ad esempio, se il contenuto del tuo sito web è ospitato sia su Amazon S3 che su Google Cloud Storage (hai lo stesso contenuto in entrambi i siti) e poi utilizzi una funzione edge per bilanciare casualmente tra i due. Tuttavia, non si desidera memorizzare nella cache due copie del contenuto. È possibile utilizzare chiavi di cache personalizzate per memorizzare nella cache in base alla richiesta originale URL anziché alla sotto-richiesta 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 },
    })
  )
})

Ricorda, le funzioni di bordo che operano per conto di zone diverse non possono influenzare la cache l'una dell'altra. Puoi sovrascrivere le chiavi della cache solo quando effettui richieste all'interno della tua zona (nell'esempio precedente event.request.url era la chiave memorizzata) o richieste agli host che non sono su CIS. Quando si effettua una richiesta a un'altra zona dell' CIS, ad esempio una zona che appartiene a un cliente dell' CIS, quella zona controlla completamente il modo in cui i propri contenuti vengono memorizzati nella cache all'interno dell' CIS; non è possibile ignorarla.

Sovrascrivi in base al codice di risposta di origine

Questa funzione è disponibile solo per i clienti Enterprise.

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

Questa opzione è una versione della funzionalità cacheTtl che sceglie un TTL basato sul codice di stato della risposta e non imposta automaticamente cacheEverything: true. Se la risposta a questa richiesta ha un codice di stato che corrisponde, CIS memorizza nella cache per l'ora indicata e sovrascrive le direttive della cache inviate dall'origine.

Interpretazione TTL

I seguenti valori TTL sono interpretati da CIS.

  • Valori positivi: indicare in secondi per quanto tempo CIS deve memorizzare nella cache l'asset.
  • 0: l'asset viene memorizzato nella cache ma scade immediatamente (riconvalida dall'origine ogni volta).
  • -1 o qualsiasi valore negativo: indica a CIS di non memorizzare nella cache.

API cache

Memorizza nella cache utilizzando l'API della cache CIS. Questo esempio può anche memorizzare nella cache le richieste 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))
  }
})