feat: migrate from Next.js to pure Bun relay proxy

- Replace Next.js app router with standalone Bun.serve() entry point
- Add middleware stack: rate limiter, body limiter, structured logger, SSRF protection
- Add WebSocket bidirectional relay via x-relay-target header
- Implement error classification (DNS→502, timeouts→504, SSRF→403, rate→429)
- Remove all Next.js dependencies and config files
- Update deploy workflow, tsconfig, wrangler config for Bun deployment

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-10 19:54:55 +07:00
co-authored by Claude Fable 5
parent 124dab98a2
commit f58a92e2aa
43 changed files with 2981 additions and 1659 deletions
+264 -71
View File
@@ -1,119 +1,312 @@
# 🚀 Edge Relay (Proxy Bun)
# Edge Proxy Relay -- Pure Bun HTTP + WebSocket Relay
High-performance HTTP Proxy & Relay handler optimized for Vercel Edge Runtime, Cloudflare Workers, and Bun.
[![Bun](https://img.shields.io/badge/Bun-0.8+-000?logo=bun&logoColor=fff)](https://bun.sh)
## 🌐 Live Deployment
A high-performance HTTP and WebSocket relay proxy built entirely with Bun's standard library. Zero framework dependencies. No Next.js, no Express, no React.
| Provider | Endpoint |
|----------|----------|
| **Primary (Vercel)** | `https://proxy-bun.vercel.app` |
| **Secondary (CF Workers)** | `https://opennext-app.superaseph.workers.dev` |
| **Leapcell** | `https://proxy-bun-mytheclipse8647-orfq73fe.apn.leapcell.dev` |
| **Interactive Docs** | `https://proxy-bun.vercel.app/docs` |
Accepts requests with an `x-relay-target` header and forwards them to the upstream target. Supports both HTTP relay and WebSocket relay in a single `Bun.serve()` instance.
---
## 🛠 Cara Pakai
## Quick Start
Proxy ini bekerja dengan menangkap request ke endpoint relay dan meneruskannya ke target yang ditentukan via headers.
```bash
bun install
bun run dev # development with HMR
bun start # production
```
The server starts on `http://localhost:3000` by default.
---
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `PORT` | `3000` | Server listen port |
| `RELAY_TIMEOUT_MS` | `30000` | Upstream fetch timeout in milliseconds |
| `BODY_MAX_BYTES` | `1048576` | Maximum accepted request body size in bytes (1 MB) |
| `RATE_LIMIT_MAX` | `100` | Maximum requests per sliding window per client IP |
| `RATE_LIMIT_WINDOW_MS` | `60000` | Sliding window duration in milliseconds (1 minute) |
---
## Endpoints
| Path | Method | Description |
|------|--------|-------------|
| `/` | GET | Status page (HTML) |
| `/health` | GET | Health check (JSON): `{ "status": "ok", "uptime": ..., "version": "1.0.0" }` |
| `/docs` | GET | Interactive documentation page (HTML) |
| `/*` | Any | HTTP relay (requires `x-relay-target`) |
| `/*` | GET | WebSocket relay (requires `x-relay-target` with `ws://` or `wss://`, `Upgrade: websocket`) |
### CORS Preflight
Any `OPTIONS` request to any path returns a `204 No Content` response with permissive CORS headers (`Access-Control-Allow-Origin: *`).
---
## HTTP Relay
Include the `x-relay-target` header to specify the upstream URL. The request method, body, headers, and query parameters are forwarded transparently.
### Required Headers
| Header | Required | Default | Deskripsi |
|--------|----------|---------|-----------|
| `x-relay-target` | **Yes** | - | Base URL target (e.g. `https://api.openai.com`) |
| `x-relay-path` | No | `/` | Path tambahan (e.g. `/v1/chat/completions`) |
| Header | Required | Description |
|--------|----------|-------------|
| `x-relay-target` | Yes | Base URL of the upstream target (e.g. `https://api.openai.com`) |
| `x-relay-path` | No | Path to append to the target URL (default: `/`) |
---
### Examples
## 📖 Contoh Penggunaan
**Simple GET relay:**
### 1. Simple GET Request
Mengambil data dari JSONPlaceholder.
```bash
curl -H "x-relay-target: https://jsonplaceholder.typicode.com/posts/1" \
https://proxy-bun.vercel.app/
http://localhost:3000/
```
### 2. POST with Body & Headers
Meneruskan API Key dan data JSON ke target.
**POST with body and authorization:**
```bash
curl -X POST \
-H "x-relay-target: https://api.example.com" \
-H "x-relay-path: /v1/data" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "x-relay-target: https://api.openai.com" \
-H "x-relay-path: /v1/chat/completions" \
-H "Authorization: Bearer sk-..." \
-H "Content-Type: application/json" \
-d '{"key": "value"}' \
https://proxy-bun.vercel.app/
-d '{"model": "gpt-4", "messages": [{"role": "user", "content": "Hello"}]}' \
http://localhost:3000/
```
### 3. Binary Data / Upload
Mendukung upload file via `POST`/`PUT` (streaming).
**Binary upload (streaming):**
```bash
curl -X PUT \
-H "x-relay-target: https://storage.com" \
-H "x-relay-target: https://storage.example.com" \
-H "x-relay-path: /upload/image.png" \
--data-binary "@/path/to/image.png" \
https://proxy-bun.vercel.app/
http://localhost:3000/
```
---
### Supported Methods
## ⚙️ Fitur & Spesifikasi
### ⚡ Protocol Support
- **HTTP/1.1 & HTTP/2** - Full support.
- **Methods** - `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`.
- **Streaming** - Mendukung response streaming (Server-Sent Events / SSE) secara native.
- **CORS** - Otomatis menambahkan header `Access-Control-Allow-*` agar bisa diakses dari browser.
### 🛡️ Security & Header Handling
Relay ini bersifat transparan kecuali untuk header berikut yang di-**strip** sebelum diteruskan ke target:
- `host` (diganti dengan host target)
- `x-relay-target`
- `x-relay-path`
Semua header lain (seperti `Authorization`, `User-Agent`, `Cookie`, dsb) akan diteruskan apa adanya.
### 🧪 Error Codes
| Status | Deskripsi |
|--------|-----------|
| `400` | Missing `x-relay-target` header. |
| `403` | Target domain tidak valid (jika whitelist aktif). |
| `502` | Target gagal dihubungi / Bad Gateway. |
`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`. Response streaming (Server-Sent Events, large payloads) is supported natively.
---
## 📂 Struktur Project
## WebSocket Relay
Set `x-relay-target` to a `ws://` or `wss://` URL and the server upgrades the connection and relays bidirectionally.
### Node.js Client
```ts
import { WebSocket } from "ws";
const ws = new WebSocket("wss://your-proxy.example/relay", {
headers: { "x-relay-target": "wss://echo-websocket.example" },
});
ws.on("open", () => ws.send("Hello via relay!"));
ws.on("message", (data) => console.log("Received:", data.toString()));
ws.on("error", (err) => console.error("WebSocket error:", err));
```
### Browser Client
```js
const ws = new WebSocket("wss://your-proxy.example/relay", {
headers: { "x-relay-target": "wss://echo-websocket.example" },
});
ws.onopen = () => ws.send("Hello via relay!");
ws.onmessage = (event) => console.log("Received:", event.data);
ws.onerror = (err) => console.error("WebSocket error:", err);
```
### With Bun's Built-in WebSocket
```ts
const ws = new WebSocket(
"wss://your-proxy.example/relay",
{ headers: { "x-relay-target": "wss://echo-websocket.example" } },
);
ws.onopen = () => ws.send("Hello via relay!");
ws.onmessage = (e) => console.log("Got:", e.data);
```
The relay handles text frames, binary frames (`Buffer`, `Uint8Array`, `ArrayBuffer`, `Blob`), and forwards close events with status codes.
---
## Architecture
```text
src/
├── app/
├── route.ts # Entry point proxy (Edge Handler)
│ └── docs/ # UI Interactive Docs & Tester
└── lib/
├── relay-utils.ts # Logic filter header & request builder
└── utils.ts # Helper UI
├── index.ts # Entry point: Bun.serve() with routing, WS relay,
# graceful shutdown, middleware orchestration
├── lib/
│ └── relay-utils.ts # URL normalization, SSRF protection, header
# filtering, request/response building, error
│ # classification, CORS preflight
└── middleware/
├── index.ts # Barrel exports
├── rate-limiter.ts # In-memory sliding window rate limiter (per IP)
├── logger.ts # Structured JSON logging with TTY colorization
└── body-limiter.ts # Content-Length validation against configurable max
```
## 🏗 Development
### Request Flow
Gunakan [Bun](https://bun.sh) untuk performa terbaik.
```
Client Request
|
v
Bun.serve() -- routes: /health, /docs, / --> static handlers
|
+--> OPTIONS? --> 204 CORS preflight response
|
+--> Upgrade: websocket + ws:// target? --> WebSocket relay (bidirectional)
|
+--> HTTP relay:
1. Body size check (413 if exceeded)
2. Rate limit check (429 if exceeded)
3. Normalize target URL from x-relay-target header
4. SSRF validation (403 if blocked)
5. Filter request headers (strip relay, platform, hop-by-hop)
6. Fetch upstream with timeout (504 on timeout, 502 on error)
7. Filter response headers, attach CORS
8. Return relayed response
```
### WebSocket Relay Flow
```
Client WebSocket Bun.serve() Upstream WebSocket
| | |
|-- upgrade req ---------------> |
| (x-relay-target: wss://) | |
| |--- open upstream ---->|
| |<-- onopen ------------|
|<-- open (101 Switching) ------ |
| | |
|-- send "hello" ------------->| |
| |--- "hello" ---------->|
| |<-- "echo" ------------|
|<-- onmessage "echo" --------- |
| | |
|-- close -------------------->| |
| |--- close upstream --->|
```
---
## Error Codes
| Status | Code | Meaning |
|--------|------|---------|
| 204 | -- | CORS preflight success (OPTIONS request) |
| 400 | `INVALID_TARGET` | Missing or malformed `x-relay-target` header |
| 403 | `SSRF_BLOCKED` | Target resolves to a private or internal IP range |
| 413 | `BODY_TOO_LARGE` | Request body `Content-Length` exceeds `BODY_MAX_BYTES` |
| 429 | `RATE_LIMITED` | Client IP has exceeded the rate limit |
| 502 | `DNS_FAILURE` | DNS resolution failed for the target hostname |
| 502 | `CONNECTION_REFUSED` | Upstream actively refused the connection |
| 502 | `NETWORK_ERROR` | Generic network error (connection reset, unreachable, etc.) |
| 504 | `TIMEOUT` | Upstream did not respond within `RELAY_TIMEOUT_MS` |
All error responses return JSON with `error`, `code`, and `message` fields, plus CORS headers.
---
## Security
### SSRF Protection
The proxy blocks requests to private and internal network ranges:
- IPv4 loopback (`127.x.x.x`), private ranges (`10.x.x.x`, `172.16-31.x.x`, `192.168.x.x`), link-local (`169.254.x.x`)
- IPv6 loopback (`::1`), link-local (`fe80::`), unique local (`fc00::`/`fd00::`)
- Common internal hostnames (`localhost`, `*.local`, `*.internal`, cloud metadata endpoints)
### Header Filtering
Before forwarding requests upstream, the proxy strips:
- Relay control headers (`x-relay-target`, `x-relay-path`, `host`)
- Hop-by-hop headers (`connection`, `transfer-encoding`, etc.)
- Platform metadata headers (Vercel `x-vercel-*`, Cloudflare `cf-*`, `x-forwarded-*`)
- Sensitive headers (`cookie`, `set-cookie`, `via`)
### Rate Limiting
In-memory sliding window rate limiter keyed by client IP (default: 100 requests per minute). The `Retry-After` header is set on 429 responses. A periodic cleanup routine prunes expired entries from memory.
### Body Size Limiting
Requests with a `Content-Length` exceeding `BODY_MAX_BYTES` (default 1 MB) are rejected with a 413 response. Requests without `Content-Length` (streaming) are passed through.
### Connection Timeout
All upstream fetches are bounded by `RELAY_TIMEOUT_MS` (default 30 seconds) using `AbortSignal.timeout()`. Timeouts are classified as 504 responses.
### Graceful Shutdown
The server listens for `SIGTERM` and `SIGINT`, stops accepting new connections, and exits cleanly.
---
## Deployment
Deploy as a standalone Bun process. No framework adapter required.
```bash
# Production
bun src/index.ts
# With environment overrides
PORT=8080 RELAY_TIMEOUT_MS=10000 bun src/index.ts
```
### Deployment Targets
- **Any VPS / VM**: Run as a systemd service or under a process manager (e.g., `pm2`, `supervisord`)
- **Railway / Fly.io / Render / Koyeb**: Set the build command to `bun install` and start command to `bun src/index.ts`
- **Docker**: Use the official `oven/bun` image
```dockerfile
FROM oven/bun:latest
WORKDIR /app
COPY package.json bun.lock .
RUN bun install
COPY . .
EXPOSE 3000
CMD ["bun", "src/index.ts"]
```
---
## Development
```bash
# Install dependencies
bun install
# Run dev server
bun dev
# Start dev server with HMR
bun run dev
# Run unit tests
# Run tests
bun test
# Static analysis
bun run lint
```
## 🚀 Deployment (GitHub Actions)
Project ini otomatis dideploy ke Cloudflare Workers setiap ada push ke `master`.
Konfigurasi workflow ada di `.github/workflows/deploy.yml`.
---
🤖 **Powered by Bun + Next.js Edge Runtime**
## License
MIT