Cloudflare Workers
Cloudflare Workers (Edge Serverless Platform)
Cloudflare의 엣지 컴퓨팅 플랫폼. V8 isolates로 빠른 시작. 전 세계 300+ 데이터센터에서 실행.
Cloudflare Workers (Edge Serverless Platform)
Cloudflare의 엣지 컴퓨팅 플랫폼. V8 isolates로 빠른 시작. 전 세계 300+ 데이터센터에서 실행.
Cloudflare Workers는 Cloudflare의 전 세계 300개 이상의 데이터센터에서 JavaScript/TypeScript 코드를 실행하는 서버리스 엣지 컴퓨팅 플랫폼입니다. 기존 Lambda나 Cloud Functions와 달리 사용자와 가장 가까운 엣지 서버에서 코드가 실행됩니다.
Workers는 Chrome V8 엔진의 isolates 기술을 사용합니다. 컨테이너나 VM 대신 경량화된 isolates를 사용하여 콜드 스타트 없이 0ms 수준의 시작 시간을 제공합니다. 단일 프로세스 내에서 수천 개의 isolates가 동시에 실행될 수 있어 매우 효율적입니다.
주요 특징:
// src/index.ts - 기본 Cloudflare Worker
export interface Env {
API_KEY: string;
CACHE_TTL: string;
}
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
const url = new URL(request.url);
// CORS 헤더 설정
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
};
// Preflight 요청 처리
if (request.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
// 라우팅
if (url.pathname === '/api/hello') {
return new Response(
JSON.stringify({
message: 'Hello from the Edge!',
location: request.cf?.city || 'Unknown',
country: request.cf?.country || 'Unknown',
timestamp: new Date().toISOString(),
}),
{
headers: {
'Content-Type': 'application/json',
...corsHeaders,
},
}
);
}
// 캐시된 응답 반환
if (url.pathname === '/api/cached') {
const cacheKey = new Request(url.toString(), request);
const cache = caches.default;
let response = await cache.match(cacheKey);
if (!response) {
response = new Response(
JSON.stringify({ data: 'Fresh data', time: Date.now() }),
{
headers: {
'Content-Type': 'application/json',
'Cache-Control': `s-maxage=${env.CACHE_TTL}`,
},
}
);
ctx.waitUntil(cache.put(cacheKey, response.clone()));
}
return response;
}
return new Response('Not Found', { status: 404 });
},
};
// src/index.ts - Hono 프레임워크를 사용한 API 라우터
import { Hono } from 'hono';
import { cors } from 'hono/cors';
import { cache } from 'hono/cache';
import { jwt } from 'hono/jwt';
type Bindings = {
JWT_SECRET: string;
DATABASE: D1Database;
RATE_LIMITER: RateLimit;
};
const app = new Hono<{ Bindings: Bindings }>();
// 미들웨어 설정
app.use('/*', cors());
app.use('/api/*', async (c, next) => {
// Rate Limiting
const { success } = await c.env.RATE_LIMITER.limit({
key: c.req.header('CF-Connecting-IP') || 'anonymous',
});
if (!success) {
return c.json({ error: 'Rate limit exceeded' }, 429);
}
await next();
});
// 공개 엔드포인트
app.get('/', (c) => c.json({ status: 'healthy', edge: c.req.raw.cf?.colo }));
// JWT 인증이 필요한 엔드포인트
app.use('/api/protected/*', jwt({ secret: (c) => c.env.JWT_SECRET }));
// D1 데이터베이스 조회
app.get('/api/users', async (c) => {
const { results } = await c.env.DATABASE.prepare(
'SELECT id, name, email FROM users LIMIT 100'
).all();
return c.json(results);
});
// 사용자 생성
app.post('/api/users', async (c) => {
const body = await c.req.json();
const { name, email } = body;
const result = await c.env.DATABASE.prepare(
'INSERT INTO users (name, email) VALUES (?, ?) RETURNING id'
).bind(name, email).first();
return c.json({ id: result?.id, name, email }, 201);
});
// 캐시된 응답 (1시간)
app.get(
'/api/cached-data',
cache({ cacheName: 'api-cache', cacheControl: 'max-age=3600' }),
(c) => c.json({ data: 'This is cached for 1 hour' })
);
export default app;
// src/kv-session.ts - Workers KV를 사용한 세션 관리
export interface Env {
SESSIONS: KVNamespace;
SESSION_TTL: string;
}
interface Session {
userId: string;
email: string;
roles: string[];
createdAt: number;
expiresAt: number;
}
// 세션 생성
export async function createSession(
env: Env,
userId: string,
email: string,
roles: string[]
): Promise<string> {
const sessionId = crypto.randomUUID();
const ttl = parseInt(env.SESSION_TTL) || 3600;
const session: Session = {
userId,
email,
roles,
createdAt: Date.now(),
expiresAt: Date.now() + ttl * 1000,
};
// KV에 세션 저장 (TTL 자동 만료)
await env.SESSIONS.put(
`session:${sessionId}`,
JSON.stringify(session),
{ expirationTtl: ttl }
);
return sessionId;
}
// 세션 조회
export async function getSession(
env: Env,
sessionId: string
): Promise<Session | null> {
const data = await env.SESSIONS.get(`session:${sessionId}`);
if (!data) return null;
const session: Session = JSON.parse(data);
// 만료 확인
if (session.expiresAt < Date.now()) {
await env.SESSIONS.delete(`session:${sessionId}`);
return null;
}
return session;
}
// 세션 갱신
export async function refreshSession(
env: Env,
sessionId: string
): Promise<boolean> {
const session = await getSession(env, sessionId);
if (!session) return false;
const ttl = parseInt(env.SESSION_TTL) || 3600;
session.expiresAt = Date.now() + ttl * 1000;
await env.SESSIONS.put(
`session:${sessionId}`,
JSON.stringify(session),
{ expirationTtl: ttl }
);
return true;
}
// 세션 삭제 (로그아웃)
export async function deleteSession(
env: Env,
sessionId: string
): Promise<void> {
await env.SESSIONS.delete(`session:${sessionId}`);
}
// wrangler.toml 설정 예시
/*
name = "session-api"
main = "src/index.ts"
compatibility_date = "2024-01-01"
kv_namespaces = [
{ binding = "SESSIONS", id = "abc123..." }
]
[vars]
SESSION_TTL = "7200"
*/
Free 플랜은 요청당 CPU 시간 10ms, Paid는 30ms 제한이 있습니다.
무거운 연산(이미지 처리, 대용량 JSON 파싱)은 Worker에서 직접 처리하지 말고
오리진 서버로 위임하세요. ctx.waitUntil()은 응답 후 실행되지만 여전히 제한 적용됩니다.
Workers KV는 전 세계적으로 복제되지만 최종 일관성(eventually consistent)을 가집니다. 데이터 쓰기 후 다른 리전에서 즉시 읽으면 구 버전이 반환될 수 있습니다. 강한 일관성이 필요하면 Durable Objects를 사용하세요.
Workers는 인증/인가, 캐시 조회, 요청 라우팅, 헤더 조작 등 가벼운 로직에 최적화되어 있습니다. DB 쿼리나 복잡한 비즈니스 로직은 오리진에서 처리하고, Workers는 엣지 프록시/캐시 레이어로 활용하세요.