Anwendungsfälle für Edge-Funktionen
Die folgenden Anwendungsfälle dienen lediglich als Beispiele und sind nicht für eine exakte Duplizierung in Ihrer Umgebung vorgesehen.
Seien Sie vorsichtig, wenn Sie einen der folgenden Codes testen, da dies zu einer Unterbrechung des Dienstes führen kann.
A/B-Test
Sie können eine CIS Edge-Funktion zum Steuern von A/B-Tests erstellen.
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
}
}
Antwortheader hinzufügen
Um die Antwortheader zu ändern, erstellen Sie zuerst eine Kopie der Antwort, damit Sie sie veränderbar machen können. Anschließend können Sie die Schnittstelle Kopfzeilen verwenden, um Kopfzeilen hinzuzufügen, zu ändern oder zu entfernen.
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;
}
Mehrere Anforderungen zusammenfassen
In diesem Beispiel werden mehrere Anfragen an verschiedene API-Endpunkte gestellt, die Antworten zusammengefasst und als eine einzige Antwort zurückgesendet.
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)
}
Bedingtes Routing
Die einfachste Möglichkeit, je nach verwendetem Gerät unterschiedliche Inhalte zu liefern, besteht darin, die URL der Anfrage je nach der gewünschten Bedingung umzuschreiben. Siehe die nachfolgenden Beispiele.
Gerätetyp
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)
}
Angepasste Header
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)
}
Hot Link-Schutz
Sie können CIS-Edge-Funktionen verwenden, um Ihre Hot Links in Ihren Webeigenschaften zu schützen.
addEventListener('fetch', event => {
event.respondWith(fetchAndApply(event.request))
})
/**
* If the browser is requesting an image and
* the referer does not match your host
* we redirect the request to your page
*/
async function fetchAndApply(request) {
// Fetch the response.
let response = await fetch(request)
// If it's an image, engage hotlink protection based on the
// Referer header.
let referer = request.headers.get('Referer')
let contentType = response.headers.get('Content-Type') || ''
if (referer && contentType.startsWith('image/')) {
// It's an image and there's a Referer. Verify that the
// hostnames match.
if (new URL(referer).hostname !==
new URL(request.url).hostname) {
// Hosts don't match. This is a hotlink. Redirect the
// user to our homepage.
return new Response('', {
status: 302,
headers: {
'Location': '/'
}
})
}
}
// Everything is fine, return the response normally.
return response
}
Ursprungslose Antworten
Sie können Antworten direkt vom Edge zurückgeben. Sie müssen keine Anforderung an Ihren Ursprung senden.
HTTP-Anforderungen POST und PUT ignorieren
Ignorieren Sie die HTTP-Anforderungen POST und PUT. In diesem Snippet werden alle anderen Anforderungen zum Ursprung durchgelassen.
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 oder Crawler zurückweisen
Schützen Sie Ihren Ursprung vor unerwünschten Spider- oder Crawler-Elementen. Wenn der Benutzeragent in diesem Fall 'annoying-robot' ist, gibt die Edge-Funktion die Antwort zurück, anstatt die Anforderung an den Ursprung zu senden.
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)
}
Verbindungsaufbau über eine bestimmte IP verhindern
IP-Adressen in Blacklist eintragen. Dieser Codeschnipsel verhindert, dass eine bestimmte IP (in diesem Fall 225.0.0.1) eine Verbindung zum Ursprung herstellt.
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-Anforderungen
Inhalt aus einer HTTP-Anforderung POST lesen:
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-Anforderung POST aus einer Edge-Funktion erstellen:
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
}
Signierte Anforderungen
Eine gängige URL-Authentifizierungsmethode, die als 'signierte Anforderung' bezeichnet wird, kann mithilfe der Web-Crypto-API in einer Edge-Funktion implementiert werden.
In dem hier vorgestellten Beispiel authentifiziert CIS den Pfad einer URL zusammen mit einem begleitenden Verfallszeitstempel unter Verwendung eines Hash-basierten Message Authentication Code (HMAC) mit einem SHA-256 Digest-Algorithmus. Damit eine authentifizierte Ressource erfolgreich abgerufen werden kann, muss der Benutzeragent den richtigen Pfad, die Zeitmarke für den Ablauf und HMAC mithilfe von Abfrageparametern angeben. Wenn einer dieser drei Parameter manipuliert wird, schlägt die Anforderung fehl.
Die Authentizität des Ablaufzeitstempels wird durch den HMAC abgedeckt, was bedeutet, dass Sie sich darauf verlassen können, dass der vom Benutzer angegebene Zeitstempel korrekt ist, wenn der HMAC korrekt ist und wenn URL abläuft. Sie können auch feststellen, ob eine in ihrem Besitz befindliche URL abgelaufen ist.
Signierte Anforderungen verifizieren
In diesem Beispiel wird der HMAC für jede Anfrage URL überprüft, deren Pfadname mit /verify/ beginnt.
Zur Erleichterung der Fehlersuche gibt diese Edge-Funktion eine 403-Meldung zurück, wenn URL oder HMAC ungültig sind oder die URL abgelaufen ist. In einer tatsächlichen Implementierung kann es sinnvoll sein, den Code 404 zurückzugeben.
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
}
Signierte Anforderungen generieren
Normalerweise werden Ihnen signierte Anfragen auf eine andere Art und Weise zugestellt, z. B. per E-Mail, oder Sie generieren sie selbst, wenn Sie den symmetrischen Schlüssel besitzen. Sie können die signierten Anforderungen auch in einer Edge-Funktion erzeugen.
Bei jeder Anfrage URL, die mit /generate/ beginnt, ersetzt CIS /generate/ durch /verify/, signiert den resultierenden Pfad mit seinem Zeitstempel und gibt den vollständigen, signierten URL im Antwortkörper
zurück.
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)
}
Antworten streamen
Ein Edge-Funktionsskript muss nicht seinen gesamten Antwortkörper vorbereiten, bevor es eine Antwort an event.respondWith() liefert. Wenn Sie eine TransformStream, verwenden, können Sie nach dem Senden des Antwort-Vorspanns (z.
B. HTTP-Statuszeile und -Kopfzeilen) einen Antworttext streamen. Diese Optimierung hilft CIS, die Zeit bis zum ersten Byte für den Besucher und die Menge an Pufferung, die im Edge-Funktionsskript durchgeführt werden muss, zu minimieren.
Das Minimieren der Pufferung ist besonders wichtig, wenn Antworthauptteile verarbeitet oder transformiert werden müssen, die die Speicherbegrenzung der Edge-Funktion überschreiten. In solchen Fällen ist das Streaming die einzige durchführbare Implementierungsstrategie.
Der CIS-Edge-Funktionsservice greift standardmäßig auf das Streaming zurück, sofern möglich. Diese APIs sind nur erforderlich, wenn Sie den Antworthauptteil ändern möchten, während Sie das Streamingverhalten beibehalten. Wenn Ihr Edge-Funktionsscript Antworten auf Unteranforderungen zurück an den Client wortgetreu übergibt, ohne deren Körper zu lesen, ist die Handhabung des Körpers bereits optimal.
Streaming-Durchgriff
Beginnen Sie mit dem folgenden minimalen Durchgriffsbeispiel.
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()
}
Beachten Sie die folgenden wichtigen Details:
- Obwohl
streamBody()eine asynchrone Funktion ist, sollten Sieawaitnicht aufrufen, damit der Aufruf der FunktionfetchAndStream()nicht blockiert wird. Die Funktion wird für den Zeitraum, in dem sie eine ausstehendereader.read()-oderwriter.write()-Operation hat, weiterhin asynchron ausgeführt. - Rückstau:
awaitDie Leseoperation, bevor Sie die Schreiboperation aufrufen. Gleichermaßen sollten Sie den Schreibvorgang unterawaitdurchführen, bevor Sie den nächsten Lesevorgang aufrufen. Durch dieses Verarbeitungsmuster wird der Rückstau an den Ursprung weitergegeben. - Fertigstellung: Rufen Sie am Ende
writer.close()auf, um der Edge-Laufzeitfunktion zu signalisieren, dass Sie mit dem Schreiben dieses Antwortkörpers fertig sind. Nach dem Aufruf beendet sichstreamBody()- wenn dieses Verhalten unerwünscht ist, übergeben Sie das zurückgegebene Versprechen anFetchEvent.waitUntil(). Wenn Ihr Script keinen Aufruf anwriter.close()absetzt, wird der Hauptteil von der Laufzeit als abgeschnitten eingestuft, obwohl er möglicherweise wie beabsichtigt funktioniert.
Mehrere Anforderungen zusammenfassen und streamen
Dieser Anwendungsfall ähnelt dem Rezept für das Zusammenfassen mehrerer Anfragen, aber dieses Mal beginnen Sie mit dem Schreiben der Antwort, sobald Sie überprüft haben, dass jede Unteranfrage erfolgreich war - Sie müssen nicht auf die Antwortkörper warten.
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()
}
Während der Laufzeit werden TypedArray-Elemente auf der Leseseite des Elements 'TransformStream' erwartet. Daher wird nie eine Zeichenfolge an writer.write() übergeben, nur Uint 8Arrays. Wenn eine Zeichenfolge geschrieben werden
muss, verwenden Sie ein Element 'TextEncoder'.
Angepasste Lastausgleichsfunktion mit Edge-Funktionen
Der Lastausgleich hilft Ihnen, die Skalierbarkeit und Zuverlässigkeit der von Ihnen gehosteten Websites zu erhalten. Sie können Edge-Funktionen verwenden, um benutzerdefinierte Load Balancer zu erstellen, die auf Ihre speziellen Anforderungen zugeschnitten sind.
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);
}
Mit Abruf zwischenspeichern
Legen Sie fest, wie eine Ressource zwischengespeichert werden soll, indem Sie TTLs, angepasste Cacheschlüssel und Cache-Header in einer Abrufanforderung festlegen.
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-Ressourcen zwischenspeichern
// Force CIS to cache an asset
fetch(event.request, { cf: { cacheEverything: true } })
Das Festlegen der Cachestufe auf "Alles zwischenspeichern" überschreibt die "Speicherbarkeit im Cache" des Assets. Hinsichtlich TTL ist CIS immer noch auf Header angewiesen, die vom Ursprung festgelegt wurden.
Angepasste Cacheschlüssel
Diese Funktion ist nur für Enterprise-Kunden verfügbar.
Der Cache-Schlüssel einer Anfrage bestimmt, ob zwei Anfragen für Caching-Zwecke "gleich" sind. Wenn eine Anforderung denselben Cacheschlüssel aufweist wie eine vorherige Anforderung, kann dieselbe Antwort aus dem Cache für beide Anforderungen bereitgestellt werden.
// Set cache key for this request to "some-string".
fetch(event.request, { cf: { cacheKey: "some-string" } })
CIS berechnet den Cache-Schlüssel für eine Anfrage auf der Grundlage der Anfrage URL, aber Sie möchten vielleicht, dass verschiedene URLs für Caching-Zwecke so behandelt werden, als ob sie gleich wären. Zum Beispiel, wenn der Inhalt Ihrer Website sowohl auf Amazon S3 als auch auf Google Cloud Storage gehostet wird (Sie haben denselben Inhalt an beiden Orten), und Sie dann eine Randfunktion verwenden, um zufällig zwischen den beiden zu balancieren. Sie möchten jedoch nicht zwei Kopien Ihrer Inhalte zwischenspeichern. Sie können benutzerdefinierte Cache-Schlüssel verwenden, um den Cache auf der Grundlage der ursprünglichen Anforderung URL und nicht der Unteranforderung URL einzurichten.
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 },
})
)
})
Denken Sie daran, dass Edge-Funktionen, die für verschiedene Zonen arbeiten, den Cache der jeweils anderen Zone nicht beeinflussen können. Sie können Cache-Schlüssel nur überschreiben, wenn Sie Anfragen innerhalb Ihrer eigenen Zone stellen
(im vorherigen Beispiel war event.request.url der gespeicherte Schlüssel), oder Anfragen an Hosts, die nicht auf CIS sind. Wenn Sie eine Anfrage an eine andere CIS-Zone richten (z. B. eine Zone, die zu einem anderen CIS-Kunden
gehört), steuert diese Zone vollständig, wie ihre eigenen Inhalte in CIS zwischengespeichert werden. Sie können dies nicht überschreiben.
Auf Basis des Ursprungsantwortcodes überschreiben
Diese Funktion ist nur für Enterprise-Kunden verfügbar.
// 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 } },
})
Diese Option ist eine Variante der Funktion cacheTtl, bei der anhand des Statuscodes der Antwort ein TTL-Wert ausgewählt und nicht automatisch cacheEverything: true eingestellt wird. Wenn die Antwort auf diese Anforderung
einen entsprechenden Statuscode aufweist, wird sie von CIS für den angegebenen Zeitraum zwischengespeichert und die vom Ursprung gesendeten Anweisungen werden überschrieben.
TTL-Interpretation
Die folgenden TTL-Werte werden von CIS interpretiert.
- Positive Werte: Geben Sie in Sekunden an, wie lange CIS das Asset zwischenspeichern soll.
0: Das Asset wird zwischengespeichert, läuft jedoch sofort ab (jedes Mal Revalidierung vom Ursprung).-1oder ein beliebiger anderer negativer Wert: Weist CIS an, überhaupt nicht zwischenzuspeichern.
Cache-API
Mithilfe der Cache-API CIS zwischenspeichern. Dieses Beispiel kann auch POST-Anforderungen zwischenspeichern.
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))
}
})