IBM Cloud Docs
Edge Functions 유스 케이스

Edge Functions 유스 케이스

다음 사용 사례는 예시로만 제공되며 사용자 환경에서 정확히 복제하기 위한 것이 아닙니다.

다음 코드를 테스트할 때는 서비스 중단을 일으킬 수 있으므로 주의하세요.

A/B 테스트

CIS Edge Function을 작성하여 A/B 테스트를 제어할 수 있습니다.

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

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

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

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

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

  const response = await fetch(modifiedRequest)

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

응답 헤더 추가

응답 헤더를 수정하려면 먼저 변경 가능하도록 응답의 사본을 작성하십시오. 그런 다음 헤더 인터페이스를 사용하여 헤더를 추가, 변경 또는 제거할 수 있습니다.

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

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

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

다중 요청 집계

이 예는 서로 다른 API 엔드포인트에 여러 번 요청하고 응답을 집계한 다음 단일 응답으로 다시 전송합니다.

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

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

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

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

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

조건부 라우팅

사용 중인 디바이스에 따라 다른 콘텐츠를 제공하는 가장 쉬운 방법은 관심 있는 조건에 따라 요청의 URL 을 다시 작성하는 것입니다. 다음 예제를 참조하십시오.

디바이스 유형

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

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

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

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

사용자 정의 헤더

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

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

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

오리진 없는 응답

에지에서 직접 응답을 리턴할 수 있습니다. 오리진에 요청을 보낼 필요가 없습니다.

POST 및 PUT HTTP 요청 무시

POST 및 PUT HTTP 요청을 무시합니다. 이 스니펫에서는 다른 모든 요청이 오리진을 거칠 수 있습니다.

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

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

  return fetch(request)
}

스파이더 또는 크롤러 거부

원하지 않는 스파이더나 크롤러로부터 오리진을 보호합니다. 이 경우 사용자 에이전트가 “성가신 로봇”이면 Edge Function이 요청을 오리진으로 보내는 대신 응답을 리턴합니다.

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

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

  return fetch(request)
}

특정 IP가 연결되지 않도록 방지

IP 주소를 블랙리스트에 추가합니다. 이 코드 조각은 특정 IP(이 경우 225.0.0.1)가 오리진에 연결되는 것을 방지합니다.

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

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

  return fetch(request)
}

Post 요청

HTTP POST 요청에서 컨텐츠 읽기:

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

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

Edge Function에서 HTTP POST 요청 작성:

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

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

쿠키 설정

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
}

서명된 요청

요청 서명으로 알려진 일반 URL 인증 메소드를 Web Crypto API를 사용하여 Edge Function에서 구현할 수 있습니다.

여기에 제시된 예제에서 CIS 는 SHA-256 다이제스트 알고리즘과 함께 해시 기반 메시지 인증 코드(HMAC)를 사용하여 URL 의 경로와 함께 만료 타임스탬프를 인증합니다. 인증된 자원을 페치하려면 사용자 에이전트가 조회 매개변수를 사용하여 올바른 경로, 만기 시간소인 및 HMAC를 제공해야 합니다. 이러한 세 매개변수 중 하나가 변경되면 요청이 실패합니다.

만료 시간 스탬프의 진위 여부는 HMAC에 의해 보장됩니다. 즉, HMAC가 정확하고, URL 가 만료되면 사용자가 제공한 시간 스탬프가 정확하다고 믿을 수 있습니다. 또한 그들이 소유한 URL 가 만료되었는지 여부를 확인할 수 있습니다.

서명된 요청 확인

이 예에서는 경로 이름이 /verify/ 으로 시작하는 모든 요청 URL 에 대한 HMAC을 확인합니다.

디버깅 편의를 위해 이 Edge 함수는 URL 또는 HMAC이 유효하지 않거나 URL 주소가 만료된 경우 403 메시지를 반환합니다. 실제 구현에서는 404를 리턴할 수 있습니다.

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

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

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

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

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

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

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

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

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

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

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

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

서명된 요청 생성

일반적으로 서명된 요청은 이메일과 같은 대역 외 방식으로 전달되거나 대칭 키가 있는 경우 사용자가 직접 생성합니다. Edge 함수에서 서명된 요청을 생성할 수도 있습니다.

/generate/ 으로 시작하는 요청 URL 의 경우 CIS 은 /generate//verify/ 으로 바꾸고 결과 경로에 타임스탬프를 서명하고 응답 본문에서 서명된 전체 URL 을 반환합니다.

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

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

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

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

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

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

  return new Response(url)
}

응답 스트리밍

Edge 함수 스크립트는 event.respondWith() 에 응답을 전달하기 전에 전체 응답 본문을 준비할 필요가 없습니다. TransformStream, 를 사용하면 응답의 프론트 머신(예: HTTP 상태 표시줄 및 헤더)을 보낸 후 응답 본문을 스트리밍할 수 있습니다. 이러한 간소화는 CIS 가 방문자의 첫 번째 바이트에 도달하는 시간과 Edge 기능 스크립트에서 수행해야 하는 버퍼링 양을 최소화하는 데 도움이 됩니다.

Edge Function의 메모리 한계보다 큰 응답 본문을 처리하거나 변환해야 하는 경우 버퍼링을 최소화하는 것이 특히 중요합니다. 이러한 경우 스트리밍은 실현 가능한 유일한 구현 전략입니다.

CIS Edge Function 서비스는 가능한 경우 기본적으로 스트리밍합니다. 이러한 API는 스트리밍 동작을 유지보수하는 동안 어떤 방식으로든 응답 본문을 수정하려는 경우에만 필요합니다. 에지 함수 스크립트가 본문을 읽지 않고 하위 요청 응답을 다시 클라이언트 verbatim으로 전달하는 경우, 본문 처리가 이미 최적입니다.

패스 스루 스트리밍

다음과 같은 최소 패스 스루 예제로 시작하십시오.

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

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

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

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

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

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

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

  await writer.close()
}

주목해야 할 몇 가지 중요한 세부사항은 다음과 같습니다.

  • streamBody() 함수는 비동기 함수이지만 fetchAndStream() 함수 호출의 진행을 차단하지 않도록 await 함수를 호출하지 않으려 합니다. 함수는 미해결 reader.read() 또는 writer.write() 조작이 있는 기간 동안 비동기적으로 계속 실행됩니다.
  • 배압: await 쓰기 조작을 호출하기 전에 읽기 조작을 수행하십시오. 마찬가지로, 다음 읽기 작업을 호출하기 전에 쓰기 작업을 await 합니다. 이 패턴을 따르면 오리진으로 역압(backpressure)이 전파됩니다.
  • 완료: 마지막에 writer.close() 을 호출하여 이 응답 본문 작성을 완료했음을 Edge 함수 런타임에 알립니다. 호출된 후 streamBody() 은 종료됩니다. 이 동작이 바람직하지 않은 경우 반환된 약속을 FetchEvent.waitUntil() 으로 전달합니다. 스크립트가 writer.close()를 호출하지 않으면 계속 의도대로 작동할 수는 있지만 본문이 런타임에 잘린 것처럼 보입니다.

다중 요청 집계 및 스트리밍

이 사용 사례는 여러 요청 집계하기 레시피와 유사하지만 이번에는 모든 하위 요청이 성공했는지 확인하는 즉시 응답을 작성하기 시작하므로 응답 본문을 기다릴 필요가 없습니다.

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

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

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

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

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

  return new Response(readable)
}

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

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

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

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

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

  await writer.close()
}

런타임은 읽기 가능한 TransformStream에서 TypedArrays를 수신해야 합니다. 따라서 문자열을 writer.write() 에 전달하지 않고 Uint 8Arrays만 전달합니다. 문자열을 작성해야 하는 경우에는 TextEncoder를 사용하십시오.

Edge Functions를 사용하는 사용자 정의 로드 밸런서

로드 밸런싱은 호스팅하는 웹사이트의 확장성과 안정성을 유지하는 데 도움이 됩니다. Edge 기능을 사용하여 특정 요구 사항을 해결하도록 설계된 사용자 지정 로드 밸런서를 만들 수 있습니다.

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

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

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

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

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

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

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

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

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

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

페치를 사용하는 캐싱

페치 요청에서 TTL, 사용자 정의 캐시 키 및 캐시 헤더를 설정하여 리소스를 캐시하는 방법을 결정합니다.

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

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

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

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

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

HTML 리소스 캐싱

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

캐시 레벨을 모든 항목 캐시로 설정하면 자산의 기본 "캐시 가능성"이 대체됩니다. TTL의 경우, CIS는 여전히 오리진에서 설정된 헤더에 의존합니다.

사용자 정의 캐시 키

이 기능은 엔터프라이즈 고객만 사용할 수 있습니다.

요청의 캐시 키는 캐싱 목적으로 두 요청이 '동일한지'를 결정하는 요소입니다. 요청의 캐시 키가 이전 요청과 동일할 경우 두 요청에 대해 동일한 캐싱된 응답을 제공할 수 있습니다.

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

CIS 는 요청의 URL 을 기준으로 요청의 캐시 키를 계산하지만 캐싱 목적으로 서로 다른 URL을 동일한 것처럼 취급할 수 있습니다. 예를 들어 웹사이트 콘텐츠가 Amazon S3 와 Google Cloud Storage 에서 호스팅되는 경우(두 곳 모두에 동일한 콘텐츠가 있는 경우) 엣지 기능을 사용하여 두 곳 간의 균형을 무작위로 맞출 수 있습니다. 하지만 콘텐츠의 사본을 두 개 캐시하고 싶지는 않을 것입니다. 사용자 지정 캐시 키를 사용하여 하위 요청 URL 이 아닌 원본 요청 URL 을 기반으로 캐시할 수 있습니다.

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

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

서로 다른 영역을 대신하여 작동하는 엣지 함수는 서로의 캐시에 영향을 줄 수 없다는 점을 기억하세요. 캐시 키는 자신의 영역 내에서 요청(이전 예제에서는 event.request.url 에 저장된 키)을 하거나 CIS 에 없는 호스트에 요청하는 경우에만 재정의할 수 있습니다. 다른 CIS 구역(예를 들어, 다른 CIS 고객의 구역)에 요청을 하면, 그 구역은 CIS 내에서 자체 콘텐츠가 캐시되는 방식을 완전히 제어합니다. 이것을 덮어쓸 수는 없습니다.

오리진 응답 코드를 기반으로 대체

이 기능은 엔터프라이즈 고객만 사용할 수 있습니다.

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

이 옵션은 응답의 상태 코드를 기반으로 TTL을 선택하고 cacheTtl를 자동으로 설정하지 않는 cacheEverything: true 기능의 버전입니다. 이 요청에 대한 응답에 일치하는 상태 코드가 있으면 CIS가 지시된 시간 동안 캐시하고 오리진이 보낸 캐시 지시문을 대체합니다.

TTL 해석

다음 TTL 값은 CIS 에서 해석합니다.

  • 양수: CIS가 자산을 캐시해야 하는 시간을 초 단위로 나타냅니다.
  • 0: 자산이 캐시되지만 즉시 만료됩니다(오리진에서 매번 재 검증).
  • -1 또는 음수: CIS가 캐시하지 않도록 지시합니다.

캐시 API

CIS 캐시 API를 사용하여 캐시하십시오. 이 예제는 POST 요청을 캐시할 수도 있습니다.

const someOtherHostname = "my.herokuapp.com"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  const cache = caches.default

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

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

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