Skip to main content
โšก Calmops

Edge Computing: Cloudflare Workers, AWS Lambda@Edge

Introduction

Edge computing brings computation closer to users. With latencies under 10ms globally, edge functions enable real-time personalization, A/B testing, and security processing at the network edge.

Key Statistics:

  • Edge functions reduce latency by 60-80%
  • Cloudflare processes requests in 50ms globally
  • Edge computing market: $15B by 2027
  • 40% of web traffic goes through edge functions

Edge Architecture

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    Edge Computing Architecture                         โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                  โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚
โ”‚  โ”‚                    Global Edge Network                       โ”‚โ”‚
โ”‚  โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚โ”‚
โ”‚  โ”‚  โ”‚  US-E  โ”‚ โ”‚  US-W  โ”‚ โ”‚  EU-C  โ”‚ โ”‚  ASIA  โ”‚ โ”‚  SA    โ”‚ โ”‚โ”‚
โ”‚  โ”‚  โ”‚ (NYC)  โ”‚ โ”‚ (LAX)  โ”‚ โ”‚ (FRA)  โ”‚ โ”‚ (SIN)  โ”‚ โ”‚ (GRU)  โ”‚ โ”‚โ”‚
โ”‚  โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚
โ”‚                              โ”‚                                    โ”‚
โ”‚         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”             โ”‚
โ”‚         โ–ผ                    โ–ผ                    โ–ผ             โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚
โ”‚  โ”‚  A/B Test  โ”‚      โ”‚ Auth/JWT  โ”‚      โ”‚ Rate Limit โ”‚       โ”‚
โ”‚  โ”‚  Personalizeโ”‚      โ”‚ Transform โ”‚      โ”‚  Security  โ”‚       โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜      โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚
โ”‚                                                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Cloudflare Workers

Getting Started

// index.js - Cloudflare Worker
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    
    // Handle different routes
    if (url.pathname === '/api/data') {
      return handleApiRequest(request, env);
    }
    
    if (url.pathname.startsWith('/user/')) {
      return handleUserRequest(request, env, url.pathname);
    }
    
    // Default: fetch from origin
    return fetch(request);
  }
};

async function handleApiRequest(request, env) {
  // Parse request
  const data = await request.json();
  
  // Transform response
  const response = await fetch('https://api.internal.com/data', {
    method: 'POST',
    body: JSON.stringify(data),
    headers: {
      'Authorization': `Bearer ${env.API_SECRET}`
    }
  });
  
  const result = await response.json();
  
  // Add edge-specific headers
  return new Response(JSON.stringify(result), {
    headers: {
      'Content-Type': 'application/json',
      'X-Edge-Location': 'global',
      'Cache-Control': 'public, max-age=60'
    }
  });
}

async function handleUserRequest(request, env, path) {
  const userId = path.split('/')[2];
  
  // Fetch from KV store
  const user = await env.USERS.get(userId);
  
  if (!user) {
    return new Response('User not found', { status: 404 });
  }
  
  return new Response(user, {
    headers: { 'Content-Type': 'application/json' }
  });
}

Durable Objects

// durable-object.js
export class UserSession {
  constructor(state, env) {
    this.state = state;
    this.env = env;
  }
  
  async fetch(request) {
    const url = new URL(request.url);
    
    // Get or create session
    let session = await this.state.get('session');
    
    if (!session) {
      session = {
        id: crypto.randomUUID(),
        created: Date.now(),
        data: {}
      };
    }
    
    if (url.pathname === '/get') {
      return new Response(JSON.stringify(session), {
        headers: { 'Content-Type': 'application/json' }
      });
    }
    
    if (url.pathname === '/set') {
      const updates = await request.json();
      session.data = { ...session.data, ...updates };
      session.updated = Date.now();
      await this.state.put('session', session);
      
      return new Response(JSON.stringify({ success: true }));
    }
    
    return new Response('Not found', { status: 404 });
  }
}

AWS Lambda@Edge

Function Configuration

// viewer-request.js - Lambda@Edge
exports.handler = async (event, context) => {
  const request = event.Records[0].cf.request;
  
  // Add security headers
  request.headers['strict-transport-security'] = [{
    key: 'Strict-Transport-Security',
    value: 'max-age=31536000; includeSubDomains'
  }];
  
  request.headers['x-content-type-options'] = [{
    key: 'X-Content-Type-Options',
    value: 'nosniff'
  }];
  
  request.headers['x-frame-options'] = [{
    key: 'X-Frame-Options',
    value: 'DENY'
  }];
  
  return request;
};

// origin-request.js
exports.handler = async (event, context) => {
  const request = event.Records[0].cf.request;
  
  // Add authentication
  const authHeader = request.headers['authorization'];
  
  if (!authHeader && !request.uri.startsWith('/public/')) {
    return {
      status: '401',
      statusDescription: 'Unauthorized',
      headers: {
        'www-authenticate': [{
          key: 'WWW-Authenticate',
          value: 'Bearer realm="protected"'
        }]
      }
    };
  }
  
  // Transform request
  request.uri = request.uri.replace('/api/', '/api/v1/');
  
  return request;
};

// origin-response.js
exports.handler = async (event, context) => {
  const response = event.Records[0].cf.response;
  
  // Add caching headers
  response.headers['cache-control'] = [{
    key: 'Cache-Control',
    value: 'public, max-age=3600, s-maxage=86400'
  }];
  
  // Add custom header
  response.headers['x-edge-function'] = [{
    key: 'X-Edge-Function',
    value: 'lambda@edge'
  }];
  
  return response;
};

CloudFront Trigger Setup

# Terraform Lambda@Edge
resource "aws_lambda_function" "edge_function" {
  filename         = "function.zip"
  function_name    = "edge-function"
  role           = aws_iam_role.edge_role.arn
  handler         = "index.handler"
  runtime         = "nodejs20.x"
  timeout         = 30
  
  lifecycle {
    create_before_destroy = true
  }
}

resource "aws_cloudfront_distribution" "cdn" {
  origin {
    domain_name = "origin.example.com"
    origin_id   = "custom-origin"
  }
  
  default_cache_behavior {
    allowed_methods        = ["GET", "HEAD", "OPTIONS", "PUT", "POST", "PATCH", "DELETE"]
    cached_methods         = ["GET", "HEAD"]
    target_origin_id       = "custom-origin"
    viewer_protocol_policy = "redirect-to-https"
    
    lambda_function_association {
      event_type = "viewer-request"
      lambda_arn = aws_lambda_function.edge_function.arn
      include_body = false
    }
    
    lambda_function_association {
      event_type = "origin-request"
      lambda_arn = aws_lambda_function.edge_function.arn
      include_body = false
    }
  }
}

Edge Patterns

A/B Testing

// A/B testing at edge
export default {
  async fetch(request, env, ctx) {
    // Check for existing bucket assignment
    let bucket = request.headers.get('x-ab-bucket');
    
    // Assign bucket if not exists
    if (!bucket) {
      bucket = Math.random() < 0.5 ? 'a' : 'b';
    }
    
    // Fetch origin with bucket
    const response = await fetch(request.url, {
      headers: {
        'X-AB-Bucket': bucket,
        ...request.headers
      }
    });
    
    // Set cookie if not present
    if (!request.headers.get('cookie')?.includes('ab_bucket')) {
      const modifiedResponse = new Response(response.body, response);
      modifiedResponse.headers.set(
        'Set-Cookie', 
        `ab_bucket=${bucket}; Path=/; Max-Age=2592000; SameSite=Lax`
      );
      
      modifiedResponse.headers.set('X-Variant', bucket);
      return modifiedResponse;
    }
    
    return response;
  }
};

Rate Limiting

// Rate limiting with KV store
export default {
  async fetch(request, env, ctx) {
    const ip = request.headers.get('CF-Connecting-IP');
    const limit = 100; // requests per minute
    const window = 60;
    
    const key = `rate:${ip}`;
    const current = await env.RATE_LIMIT.get(key, 'json');
    
    const now = Date.now();
    
    if (current && current.count >= limit) {
      // Check if window expired
      if (now - current.window_start < window * 1000) {
        return new Response('Too Many Requests', {
          status: 429,
          headers: {
            'Retry-After': String(window),
            'X-RateLimit-Remaining': '0'
          }
        });
      }
    }
    
    // Update counter
    const newCount = {
      count: (current?.count || 0) + 1,
      window_start: now
    };
    
    ctx.waitUntil(env.RATE_LIMIT.put(key, JSON.stringify(newCount)));
    
    const response = await fetch(request);
    response.headers.set('X-RateLimit-Remaining', String(limit - newCount.count));
    
    return response;
  }
};

External Resources


Comments