Initial commit
This commit is contained in:
689
skills/skill/references/advanced-features.md
Normal file
689
skills/skill/references/advanced-features.md
Normal file
@@ -0,0 +1,689 @@
|
||||
# Advanced Features
|
||||
|
||||
Advanced Workers capabilities for complex applications and enterprise use cases.
|
||||
|
||||
## Workers for Platforms
|
||||
|
||||
Deploy and manage customer-provided Workers on your infrastructure.
|
||||
|
||||
**Use cases:**
|
||||
- SaaS platforms where customers write custom code
|
||||
- Low-code/no-code platforms
|
||||
- Plugin systems
|
||||
- Custom business logic hosting
|
||||
|
||||
### Setup
|
||||
|
||||
**wrangler.toml:**
|
||||
|
||||
```toml
|
||||
[[dispatch_namespaces]]
|
||||
binding = "DISPATCHER"
|
||||
namespace = "my-platform"
|
||||
|
||||
# Optional outbound Worker
|
||||
outbound = { service = "my-outbound-worker" }
|
||||
```
|
||||
|
||||
### Dynamic Dispatch
|
||||
|
||||
Route requests to customer Workers dynamically.
|
||||
|
||||
**Dispatch Worker:**
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
const customerId = url.hostname.split(".")[0];
|
||||
|
||||
// Get customer's Worker
|
||||
const userWorker = env.DISPATCHER.get(customerId);
|
||||
|
||||
// Forward request to customer's Worker
|
||||
return userWorker.fetch(request);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Upload User Workers
|
||||
|
||||
**Via API:**
|
||||
|
||||
```typescript
|
||||
async function uploadUserWorker(
|
||||
customerId: string,
|
||||
code: string,
|
||||
env: Env
|
||||
): Promise<void> {
|
||||
const response = await fetch(
|
||||
`https://api.cloudflare.com/client/v4/accounts/${env.ACCOUNT_ID}/workers/dispatch/namespaces/${env.NAMESPACE}/scripts/${customerId}`,
|
||||
{
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Authorization": `Bearer ${env.API_TOKEN}`,
|
||||
"Content-Type": "application/javascript",
|
||||
},
|
||||
body: code,
|
||||
}
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to upload: ${await response.text()}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Via Wrangler:**
|
||||
|
||||
```bash
|
||||
wrangler dispatch-namespace put my-platform customer-123 ./customer-worker.js
|
||||
```
|
||||
|
||||
### Outbound Workers
|
||||
|
||||
Control what customer Workers can access.
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
// Validate request from customer Worker
|
||||
const url = new URL(request.url);
|
||||
|
||||
// Block certain domains
|
||||
const blockedDomains = ["internal.example.com"];
|
||||
if (blockedDomains.some((d) => url.hostname.includes(d))) {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
|
||||
// Add authentication
|
||||
request.headers.set("X-Platform-Auth", env.PLATFORM_SECRET);
|
||||
|
||||
// Forward to destination
|
||||
return fetch(request);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Limits and Quotas
|
||||
|
||||
Set limits for customer Workers.
|
||||
|
||||
```toml
|
||||
[[dispatch_namespaces]]
|
||||
binding = "DISPATCHER"
|
||||
namespace = "my-platform"
|
||||
|
||||
[dispatch_namespaces.outbound]
|
||||
service = "outbound-worker"
|
||||
parameters = {
|
||||
cpu_ms = 50,
|
||||
requests = 1000
|
||||
}
|
||||
```
|
||||
|
||||
## Smart Placement
|
||||
|
||||
Automatically place Workers near data sources to reduce latency.
|
||||
|
||||
### Enable Smart Placement
|
||||
|
||||
**wrangler.toml:**
|
||||
|
||||
```toml
|
||||
[placement]
|
||||
mode = "smart"
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
- Workers monitors where your Worker makes subrequests
|
||||
- Automatically places future executions near those data sources
|
||||
- Reduces round-trip time for database/API calls
|
||||
- No code changes required
|
||||
|
||||
**Best for:**
|
||||
- Database-heavy Workers
|
||||
- Workers making many external API calls
|
||||
- Geographically distributed data sources
|
||||
|
||||
**Limitations:**
|
||||
- Not compatible with Durable Objects
|
||||
- Requires Workers Standard plan
|
||||
|
||||
## WebSockets
|
||||
|
||||
Build real-time applications with WebSockets.
|
||||
|
||||
### Basic WebSocket Server
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const upgradeHeader = request.headers.get("Upgrade");
|
||||
|
||||
if (upgradeHeader !== "websocket") {
|
||||
return new Response("Expected WebSocket", { status: 426 });
|
||||
}
|
||||
|
||||
const [client, server] = Object.values(new WebSocketPair());
|
||||
|
||||
// Handle WebSocket messages
|
||||
server.accept();
|
||||
|
||||
server.addEventListener("message", (event) => {
|
||||
console.log("Received:", event.data);
|
||||
|
||||
// Echo message back
|
||||
server.send(`Echo: ${event.data}`);
|
||||
});
|
||||
|
||||
server.addEventListener("close", (event) => {
|
||||
console.log("WebSocket closed:", event.code, event.reason);
|
||||
});
|
||||
|
||||
server.addEventListener("error", (event) => {
|
||||
console.error("WebSocket error:", event);
|
||||
});
|
||||
|
||||
return new Response(null, {
|
||||
status: 101,
|
||||
webSocket: client,
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### WebSocket with Durable Objects
|
||||
|
||||
Use Durable Objects for coordinated WebSocket servers.
|
||||
|
||||
**Durable Object:**
|
||||
|
||||
```typescript
|
||||
export class ChatRoom {
|
||||
state: DurableObjectState;
|
||||
sessions: Set<WebSocket>;
|
||||
|
||||
constructor(state: DurableObjectState) {
|
||||
this.state = state;
|
||||
this.sessions = new Set();
|
||||
}
|
||||
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const [client, server] = Object.values(new WebSocketPair());
|
||||
|
||||
server.accept();
|
||||
this.sessions.add(server);
|
||||
|
||||
server.addEventListener("message", (event) => {
|
||||
// Broadcast to all connected clients
|
||||
this.broadcast(event.data as string);
|
||||
});
|
||||
|
||||
server.addEventListener("close", () => {
|
||||
this.sessions.delete(server);
|
||||
});
|
||||
|
||||
return new Response(null, {
|
||||
status: 101,
|
||||
webSocket: client,
|
||||
});
|
||||
}
|
||||
|
||||
broadcast(message: string) {
|
||||
for (const session of this.sessions) {
|
||||
try {
|
||||
session.send(message);
|
||||
} catch (error) {
|
||||
// Remove failed sessions
|
||||
this.sessions.delete(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Worker:**
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
const roomId = url.pathname.substring(1) || "default";
|
||||
|
||||
// Get Durable Object for this room
|
||||
const id = env.CHAT_ROOM.idFromName(roomId);
|
||||
const room = env.CHAT_ROOM.get(id);
|
||||
|
||||
return room.fetch(request);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### WebSocket Hibernation
|
||||
|
||||
Reduce costs by hibernating idle WebSocket connections.
|
||||
|
||||
```typescript
|
||||
export class HibernatingChatRoom {
|
||||
state: DurableObjectState;
|
||||
|
||||
constructor(state: DurableObjectState) {
|
||||
this.state = state;
|
||||
|
||||
// Enable hibernation
|
||||
state.setWebSocketAutoResponse(
|
||||
new WebSocketRequestResponsePair("ping", "pong")
|
||||
);
|
||||
}
|
||||
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const [client, server] = Object.values(new WebSocketPair());
|
||||
|
||||
// Accept with hibernation
|
||||
this.state.acceptWebSocket(server);
|
||||
|
||||
return new Response(null, {
|
||||
status: 101,
|
||||
webSocket: client,
|
||||
});
|
||||
}
|
||||
|
||||
async webSocketMessage(ws: WebSocket, message: string) {
|
||||
// Called when message received (Worker woken up)
|
||||
const data = JSON.parse(message);
|
||||
|
||||
// Broadcast to all
|
||||
this.state.getWebSockets().forEach((socket) => {
|
||||
socket.send(message);
|
||||
});
|
||||
}
|
||||
|
||||
async webSocketClose(ws: WebSocket, code: number, reason: string) {
|
||||
// Cleanup on close
|
||||
ws.close(code, reason);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Streaming
|
||||
|
||||
Stream responses for large datasets.
|
||||
|
||||
### Streaming JSON
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const { readable, writable } = new TransformStream();
|
||||
const writer = writable.getWriter();
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
// Stream in background
|
||||
(async () => {
|
||||
try {
|
||||
await writer.write(encoder.encode("["));
|
||||
|
||||
const results = await env.DB.prepare(
|
||||
"SELECT * FROM large_table"
|
||||
).all();
|
||||
|
||||
for (let i = 0; i < results.results.length; i++) {
|
||||
const item = results.results[i];
|
||||
await writer.write(encoder.encode(JSON.stringify(item)));
|
||||
|
||||
if (i < results.results.length - 1) {
|
||||
await writer.write(encoder.encode(","));
|
||||
}
|
||||
}
|
||||
|
||||
await writer.write(encoder.encode("]"));
|
||||
await writer.close();
|
||||
} catch (error) {
|
||||
await writer.abort(error);
|
||||
}
|
||||
})();
|
||||
|
||||
return new Response(readable, {
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Server-Sent Events (SSE)
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const { readable, writable } = new TransformStream();
|
||||
const writer = writable.getWriter();
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
// Send events in background
|
||||
(async () => {
|
||||
try {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
await writer.write(
|
||||
encoder.encode(`data: ${JSON.stringify({ count: i })}\n\n`)
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
await writer.close();
|
||||
} catch (error) {
|
||||
await writer.abort(error);
|
||||
}
|
||||
})();
|
||||
|
||||
return new Response(readable, {
|
||||
headers: {
|
||||
"Content-Type": "text/event-stream",
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Custom Domains
|
||||
|
||||
Configure custom domains for Workers.
|
||||
|
||||
### Via Wrangler
|
||||
|
||||
```toml
|
||||
routes = [
|
||||
{ pattern = "api.example.com/*", zone_name = "example.com" }
|
||||
]
|
||||
```
|
||||
|
||||
### Via Dashboard
|
||||
|
||||
1. Navigate to Workers & Pages
|
||||
2. Select your Worker
|
||||
3. Go to Settings > Triggers
|
||||
4. Add Custom Domain
|
||||
|
||||
### Multiple Domains
|
||||
|
||||
```toml
|
||||
routes = [
|
||||
{ pattern = "api.example.com/*", zone_name = "example.com" },
|
||||
{ pattern = "api.other-domain.com/*", zone_name = "other-domain.com" }
|
||||
]
|
||||
```
|
||||
|
||||
## Static Assets
|
||||
|
||||
Serve static files with Workers.
|
||||
|
||||
### Configuration
|
||||
|
||||
**wrangler.toml:**
|
||||
|
||||
```toml
|
||||
[assets]
|
||||
directory = "./public"
|
||||
binding = "ASSETS"
|
||||
|
||||
# HTML handling
|
||||
html_handling = "auto-trailing-slash"
|
||||
|
||||
# 404 handling
|
||||
not_found_handling = "single-page-application"
|
||||
```
|
||||
|
||||
### Custom Asset Handling
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
|
||||
// API routes
|
||||
if (url.pathname.startsWith("/api/")) {
|
||||
return handleAPI(request, env);
|
||||
}
|
||||
|
||||
// Static assets
|
||||
try {
|
||||
const asset = await env.ASSETS.fetch(request);
|
||||
|
||||
// Add custom headers
|
||||
const response = new Response(asset.body, asset);
|
||||
response.headers.set("X-Custom-Header", "value");
|
||||
|
||||
return response;
|
||||
} catch {
|
||||
return new Response("Not found", { status: 404 });
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Framework Integration
|
||||
|
||||
**Next.js:**
|
||||
|
||||
```bash
|
||||
npm create cloudflare@latest my-next-app -- --framework=next
|
||||
```
|
||||
|
||||
**Remix:**
|
||||
|
||||
```bash
|
||||
npm create cloudflare@latest my-remix-app -- --framework=remix
|
||||
```
|
||||
|
||||
**Astro:**
|
||||
|
||||
```bash
|
||||
npm create cloudflare@latest my-astro-app -- --framework=astro
|
||||
```
|
||||
|
||||
## TCP Sockets
|
||||
|
||||
Connect to external services via TCP.
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const socket = connect({
|
||||
hostname: "example.com",
|
||||
port: 6379, // Redis
|
||||
});
|
||||
|
||||
const writer = socket.writable.getWriter();
|
||||
const encoder = new TextEncoder();
|
||||
|
||||
// Send Redis command
|
||||
await writer.write(encoder.encode("PING\r\n"));
|
||||
|
||||
// Read response
|
||||
const reader = socket.readable.getReader();
|
||||
const { value } = await reader.read();
|
||||
const response = new TextDecoder().decode(value);
|
||||
|
||||
return Response.json({ response });
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## HTML Rewriter
|
||||
|
||||
Transform HTML on the fly.
|
||||
|
||||
```typescript
|
||||
class LinkRewriter {
|
||||
element(element: Element) {
|
||||
const href = element.getAttribute("href");
|
||||
|
||||
if (href && href.startsWith("/")) {
|
||||
// Make absolute
|
||||
element.setAttribute("href", `https://example.com${href}`);
|
||||
}
|
||||
|
||||
// Add tracking
|
||||
element.setAttribute("data-tracked", "true");
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request): Promise<Response> {
|
||||
const response = await fetch(request);
|
||||
|
||||
return new HTMLRewriter()
|
||||
.on("a", new LinkRewriter())
|
||||
.on("img", {
|
||||
element(element) {
|
||||
// Lazy load images
|
||||
element.setAttribute("loading", "lazy");
|
||||
},
|
||||
})
|
||||
.transform(response);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Scheduled Events (Cron)
|
||||
|
||||
Run Workers on a schedule.
|
||||
|
||||
**wrangler.toml:**
|
||||
|
||||
```toml
|
||||
[triggers]
|
||||
crons = [
|
||||
"0 0 * * *", # Daily at midnight
|
||||
"*/15 * * * *", # Every 15 minutes
|
||||
"0 9 * * 1-5" # Weekdays at 9 AM
|
||||
]
|
||||
```
|
||||
|
||||
**Handler:**
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async scheduled(event: ScheduledEvent, env: Env, ctx: ExecutionContext) {
|
||||
console.log("Cron trigger:", event.cron);
|
||||
|
||||
// Cleanup old data
|
||||
await env.DB.prepare("DELETE FROM logs WHERE created_at < ?")
|
||||
.bind(Date.now() - 30 * 24 * 60 * 60 * 1000)
|
||||
.run();
|
||||
|
||||
// Send daily report
|
||||
ctx.waitUntil(sendDailyReport(env));
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Email Handler
|
||||
|
||||
Process incoming emails.
|
||||
|
||||
**wrangler.toml:**
|
||||
|
||||
```toml
|
||||
[email]
|
||||
name = "support@example.com"
|
||||
```
|
||||
|
||||
**Handler:**
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async email(message: ForwardableEmailMessage, env: Env) {
|
||||
console.log("From:", message.from);
|
||||
console.log("Subject:", message.headers.get("subject"));
|
||||
|
||||
// Forward to support team
|
||||
await message.forward("support-team@example.com");
|
||||
|
||||
// Or process email
|
||||
const rawEmail = await new Response(message.raw).text();
|
||||
await env.EMAILS.put(message.headers.get("message-id"), rawEmail);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Tail Workers
|
||||
|
||||
Monitor and log other Workers.
|
||||
|
||||
**wrangler.toml:**
|
||||
|
||||
```toml
|
||||
[tail_consumers]
|
||||
service = "my-logging-worker"
|
||||
```
|
||||
|
||||
**Tail Worker:**
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async tail(events: TraceItem[], env: Env) {
|
||||
for (const event of events) {
|
||||
if (event.outcome === "exception") {
|
||||
// Log errors to external service
|
||||
await fetch("https://logs.example.com", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
scriptName: event.scriptName,
|
||||
error: event.exceptions,
|
||||
timestamp: event.event.request.timestamp,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Multi-Region Deployments
|
||||
|
||||
Deploy Workers to specific regions (Enterprise only).
|
||||
|
||||
```toml
|
||||
[placement]
|
||||
mode = "regional"
|
||||
regions = ["us-east", "eu-west"]
|
||||
```
|
||||
|
||||
## Workers Analytics Engine
|
||||
|
||||
Write custom metrics.
|
||||
|
||||
```toml
|
||||
[[analytics_engine_datasets]]
|
||||
binding = "ANALYTICS"
|
||||
```
|
||||
|
||||
```typescript
|
||||
export default {
|
||||
async fetch(request: Request, env: Env): Promise<Response> {
|
||||
const start = Date.now();
|
||||
const response = await handleRequest(request, env);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
// Write custom metrics
|
||||
env.ANALYTICS.writeDataPoint({
|
||||
blobs: [request.url, request.method, String(response.status)],
|
||||
doubles: [duration],
|
||||
indexes: [request.headers.get("user-agent") || "unknown"],
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- **Workers for Platforms**: https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/
|
||||
- **Smart Placement**: https://developers.cloudflare.com/workers/configuration/smart-placement/
|
||||
- **WebSockets**: https://developers.cloudflare.com/workers/runtime-apis/websockets/
|
||||
- **Static Assets**: https://developers.cloudflare.com/workers/static-assets/
|
||||
- **Scheduled Events**: https://developers.cloudflare.com/workers/configuration/cron-triggers/
|
||||
Reference in New Issue
Block a user