Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[John Graham-Cumming] Coding on the Edge

[John Graham-Cumming] Coding on the Edge

Presentation from GDG DevFest Ukraine 2018 - the biggest community-driven Google tech conference in the CEE.

Learn more at: https://devfest.gdg.org.ua

__

The popularity of Edge Computing platforms like Cloudflare Workers and Lambda@Edge means there's now a third place to run code: at the edge. Applications are liberated from having to run on device or on server and can now live inside the Internet. In this talk I'll look at the architecture and performance of Edge Computing and show real-world examples of apps running on the Edge.

Google Developers Group Lviv

October 12, 2018
Tweet

More Decks by Google Developers Group Lviv

Other Decks in Technology

Transcript

  1. Minicomputer Commodity Server Virtual Machine Container Event Units of Compute

    Granularity 10x 10x 10x 500 req/sec… 86400 sec/day… 30 days/month...
  2. Minicomputer Commodity Server Virtual Machine Container Event Units of Compute

    Granularity 10x 10x 10x 1,000,000,000x 500 req/sec… 86400 sec/day… 30 days/month...
  3. But… First-gen serverless is just containers. Serverless Container "There is

    no cloud" design by Chris Watterston (chriswatterston.com)
  4. 18 62 Major Cloud Provider #1 32 Major Cloud Provider

    #2 4 Major Cloud Provider #3 Cloudflare Workers 154
  5. New Server(less) Technology Required • Instantaneous startup for one-off invocation.

    • Thousands of simultaneous tenants. • Secure sandboxing.
  6. =

  7. Central DB User Cache App User DB User Cache App

    User DB User Cache App User DB
  8. User Cache App User DB User Cache App User DB

    User Cache App User DB Central DB
  9. // Race GCS and B2 let gcsReq = new Request('https://storage.googleapis.com/bucketName'

    + url.pathname, event.request) let b2Req = new Request(getB2Url(request) + '/bucketName' + url.pathname, event.request); // Fetch from GCS and B2 with Cloudflare caching enabled let gcsPromise = fetch(gcsReq, cfSettings); let b2Promise = fetch(b2Req, cfSettings); let response = await Promise.race([gcsPromise, b2Promise]); if (response.ok) { return response; } // If the winner was bad, find the one that is good (if any) response = await gcsPromise; if (response.ok) { return response; }
  10. Send logs to StatHat // Fetch from GCS and B2

    with caching let reqStartTime = Date.now(); let gcsPromise = fetch(gcsReq, cfSettings); let b2Promise = fetch(b2Req, cfSettings); let response = await Promise.race([gcsPromise, b2Promise]); if (response.ok) { event.waitUntil(logResponse(event, response, (Date.now() - reqStartTime))); return response; }
  11. Spotting a Canary if(response.headers.get('Content-Type').includes('text')){ let body = await response.text() if(body.includes('SHHHTHISISASECRET'))

    { response = new Response('Blocked.', {status: 403, headers: new Headers({'Private-block': true})}) return response } return new Response(body, {status: response.status, headers: response.headers}) }
  12. Call PagerDuty from the Worker async function createPagerDutyIncident(event) { let

    body = `{ "incident": { "type": "incident", "title": "Potential data breach", "service": { "id": "${PD_SERVICE_ID}", "type": "service_reference" }, } }` let PDInit = { method: 'POST', headers: new Headers({ "Content-Type": "application/json", "Accept": "application/vnd.pagerduty+json;version=2", "From": `${PD_FROM}`, "Authorization": `Token token=${PD_API_KEY}` }), body: body } event.waitUntil(fetch('https://api.pagerduty.com/inc idents', PDInit)) }
  13. Redirect to country-specific page… if it exists function fetchAndApply(request) {

    const country = request.headers.get('Cf-Ipcountry').toLowerCase() let url = new URL(request.url) const target_url = 'https://' + url.hostname + '/' + country const target_url_response = await fetch(target_url) if(target_url_response.status === 200) { return new Response('', { status: 302, headers: { 'Location': target_url } }) } else { return response } }
  14. Dynamically include external content <html lang="en"> <head> <meta charset="UTF-8"> <title>ESI

    Demonstration</title> <script src="/cdn-cgi/apps/head/FRz8JyVlvChltLiN7yAq762_k_A.js"></script></head> <body> <h1>Cloudflare Edge-Side-Include demonstration</h1> <h3>How to ESI with Cloudflare</h3> <p>ESI (or Edge-Side-Include consist in fetching a globally static page with some dynamic fragment, this can be done directly from our EDGE with the workers, where the HTML page would be like that one:</p> <p>Dynamic portion coming from https://httpbin.org/headers:</p> <esi:include src="https://httpbin.org/headers" onerror="continue" /> </body> </html>
  15. Stream content and replace esi: async function fetchAndStream(request) { let

    response = await fetch(request) let contentType = response.headers.get('content-type') if (!contentType || !contentType.startsWith("text/")) { return response } let { readable, writable } = new TransformStream() let newResponse = new Response(readable, response) newResponse.headers.set('cache-control', 'max-age=0') streamTransformBody(response.body, writable) return newResponse }
  16. Find the esi:include and make sub-request async function handleTemplate(encoder, templateKey)

    { const linkRegex = /(esi:include.*src="(.*?)".*\/)/gm let result = linkRegex.exec(templateKey); let esi if (!result) { return encoder.encode(`<${templateKey}>`); } if (result[2]) { esi = await subRequests(result[2]); } return encoder.encode( `${esi}` ); }
  17. Get content via a sub-request async function subRequests(target){ const init

    = { method: 'GET', headers: { 'user-agent': 'cloudflare' } } let response = await fetch(target, init) let text = await response.text() return text }
  18. Verify JWT on the edge function handleFetch(request) { if (!(await

    isValidJwt(request))) { return new Response('Invalid JWT', { status: 403 }) } const gsBaseUrl = createGoogleStorageUrl(request); const gsHeaders = new Headers(); gsHeaders.set('Date', new Date().toUTCString()); // Required by Google for HMAC signed URLs const signature = await hmacSignature(gsBaseUrl, gsHeaders); gsHeaders.set('Authorization', 'AWS ' + HMAC_KEY + ':' + signature); return fetch(new Request(gsBaseUrl, {headers: gsHeaders})) }
  19. JWT Handling async function isValidJwt(request) { const encodedToken = getJwt(request);

    if (encodedToken === null) { return false } const token = decodeJwt(encodedToken); return isValidJwtSignature(token) } function getJwt(request) { const authHeader = request.headers.get('Authorization'); if (!authHeader || authHeader.substring(0, 6) !== 'Bearer') { return null } return authHeader.substring(6).trim() }