Skip to content

Commit c641283

Browse files
committed
Add handleDeno and handleNode functions
1 parent 083df0b commit c641283

File tree

8 files changed

+191
-26
lines changed

8 files changed

+191
-26
lines changed

examples/deno.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { Web } from "../packages/core/src/index.ts";
2+
import { cors } from "../packages/middleware/src/cors.ts";
3+
4+
const app = new Web();
5+
6+
app.use(cors());
7+
8+
app.get("/", async (ctx) => ctx.html(`<h1>Hello World from ${ctx.clientIp}</h1>`));
9+
10+
Deno.serve(
11+
{
12+
port: 8080,
13+
},
14+
(req, info) => app.handleDeno(req, info)
15+
);

examples/node.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { createServer } from "http";
2+
import { Web } from "../packages/core/dist/index.js";
3+
4+
const app = new Web();
5+
6+
app.get("/", async (ctx) => ctx.html(`<h1>Hello World from ${ctx.clientIp}</h1>`));
7+
8+
createServer((req, res) => app.handleNode(req, res)).listen(3000);

packages/core/README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,21 @@ app.get('/users/:id', async (ctx) => {
5050
return ctx.json(user);
5151
});
5252

53-
// Start server
53+
// Start with Bun server
5454
Bun.serve({
5555
port: 3000,
56-
fetch: app.handle
56+
fetch: app.handleBun
5757
});
5858

59+
// Start with Deno server
60+
Deno.serve({ port: 3000 },
61+
(req, info) => app.handleDeno(req, info)
62+
);
63+
64+
// Start with Node server
65+
import { createServer } from "http";
66+
createServer((req, res) => app.handleNode(req, res)).listen(3000);
67+
5968
console.log('Server running at http://localhost:3000');
6069
```
6170

packages/core/jsr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rabbit-company/web",
3-
"version": "0.5.0",
3+
"version": "0.6.0",
44
"license": "MIT",
55
"exports": "./src/index.ts",
66
"publish": {

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rabbit-company/web",
3-
"version": "0.5.0",
3+
"version": "0.6.0",
44
"description": "High-performance web framework",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",

packages/core/src/index.ts

Lines changed: 153 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,36 +1396,21 @@ export class Web<T extends Record<string, unknown> = Record<string, unknown>> {
13961396
}
13971397

13981398
/**
1399-
* Request handler optimized for Bun runtime with automatic IP extraction.
1400-
* Uses Bun's server.requestIP() for reliable client IP detection.
1399+
* Internal handler that processes requests with a known client IP.
1400+
* This is the common implementation used by both runtime-specific handlers.
14011401
*
14021402
* @param req - The incoming Request object
1403-
* @param server - Bun.Server instance for accessing runtime-specific features
1403+
* @param clientIp - The client IP address (optional)
14041404
* @returns Promise that resolves to a Response object
14051405
*
1406-
* @example
1407-
* ```typescript
1408-
* const server = Bun.serve({
1409-
* port: 3000,
1410-
* hostname: 'localhost',
1411-
* fetch: (req, server) => app.handleBun(req, server)
1412-
* });
1413-
*
1414-
* // Or using shorthand
1415-
* const server = Bun.serve({
1416-
* port: 3000,
1417-
* fetch: app.handleBun
1418-
* });
1419-
* ```
1406+
* @internal
14201407
*/
1421-
async handleBun(req: Request, server: Bun.Server): Promise<Response> {
1408+
private async handleWithIp(req: Request, clientIp?: string): Promise<Response> {
14221409
const method = req.method as Method;
14231410
const parsedUrl = this.parseUrl(req.url);
14241411
const path = parsedUrl.pathname;
14251412

14261413
try {
1427-
const clientIp = server.requestIP(req)?.address;
1428-
14291414
// Match route first
14301415
const matched = this.match(method, path);
14311416
if (!matched) {
@@ -1557,6 +1542,7 @@ export class Web<T extends Record<string, unknown> = Record<string, unknown>> {
15571542
req,
15581543
params: EMPTY_PARAMS,
15591544
state: {} as T,
1545+
clientIp,
15601546
// Minimal implementations for error handling
15611547
text: (data, status = 500) => new Response(data, { status }),
15621548
json: (data, status = 500) =>
@@ -1581,6 +1567,153 @@ export class Web<T extends Record<string, unknown> = Record<string, unknown>> {
15811567
return new Response("Internal Server Error", { status: 500 });
15821568
}
15831569
}
1570+
1571+
/**
1572+
* Request handler optimized for Bun runtime with automatic IP extraction.
1573+
* Uses Bun's server request info for reliable client IP detection.
1574+
*
1575+
* @param req - The incoming Request object
1576+
* @param server - Bun server instance
1577+
* @returns Promise that resolves to a Response object
1578+
*
1579+
* @example
1580+
* ```typescript
1581+
* Bun.serve({
1582+
* port: 3000,
1583+
* hostname: 'localhost',
1584+
* fetch: app.handleBun
1585+
* });
1586+
* ```
1587+
*/
1588+
async handleBun(req: Request, server: unknown): Promise<Response> {
1589+
// Extract client IP from Bun's server object
1590+
const clientIp = (server as any)?.requestIP?.(req)?.address;
1591+
return this.handleWithIp(req, clientIp);
1592+
}
1593+
1594+
/**
1595+
* Request handler optimized for Deno runtime with automatic IP extraction.
1596+
* Uses Deno's ServeHandlerInfo for reliable client IP detection.
1597+
*
1598+
* @param req - The incoming Request object
1599+
* @param info - Deno.ServeHandlerInfo instance for accessing runtime-specific features
1600+
* @returns Promise that resolves to a Response object
1601+
*
1602+
* @example
1603+
* ```typescript
1604+
* Deno.serve({
1605+
* port: 3000
1606+
* },
1607+
* (req, info) => app.handleDeno(req, info)
1608+
* );
1609+
* ```
1610+
*/
1611+
async handleDeno(req: Request, info: unknown): Promise<Response> {
1612+
// Extract client IP from Deno's ServeHandlerInfo
1613+
const clientIp = (info as any)?.remoteAddr?.hostname;
1614+
return this.handleWithIp(req, clientIp);
1615+
}
1616+
1617+
/**
1618+
* Request handler for Node.js that handles both request and response.
1619+
* Automatically converts between Node.js and Web APIs and extracts client IP.
1620+
*
1621+
* @param nodeReq - Node.js IncomingMessage object
1622+
* @param nodeRes - Node.js ServerResponse object
1623+
* @returns Promise that resolves when response is sent
1624+
*
1625+
* @example
1626+
* ```typescript
1627+
* import { createServer } from "http";
1628+
*
1629+
* createServer((req, res) => app.handleNode(req, res)).listen(3000);
1630+
* ```
1631+
*/
1632+
async handleNode(nodeReq: unknown, nodeRes: unknown): Promise<void> {
1633+
const req = nodeReq as any;
1634+
const res = nodeRes as any;
1635+
1636+
try {
1637+
// Extract client IP from Node.js request
1638+
const clientIp: string | undefined = req.socket?.remoteAddress;
1639+
1640+
// Convert Node.js request to Web Request
1641+
const host = req.headers.host || "localhost";
1642+
const protocol = req.socket?.encrypted ? "https" : "http";
1643+
const url = `${protocol}://${host}${req.url}`;
1644+
1645+
// Create headers
1646+
const headers = new Headers();
1647+
for (const [key, value] of Object.entries(req.headers)) {
1648+
if (value) {
1649+
if (Array.isArray(value)) {
1650+
value.forEach((v) => headers.append(key, v));
1651+
} else {
1652+
headers.set(key, value as string);
1653+
}
1654+
}
1655+
}
1656+
1657+
// Handle request body
1658+
let body: BodyInit | null = null;
1659+
if (req.method !== "GET" && req.method !== "HEAD") {
1660+
// Collect body data
1661+
const chunks: Buffer[] = [];
1662+
await new Promise<void>((resolve, reject) => {
1663+
req.on("data", (chunk: Buffer) => chunks.push(chunk));
1664+
req.on("end", () => resolve());
1665+
req.on("error", reject);
1666+
});
1667+
1668+
if (chunks.length > 0) {
1669+
body = Buffer.concat(chunks);
1670+
}
1671+
}
1672+
1673+
// Create Web Request
1674+
const webRequest = new Request(url, {
1675+
method: req.method,
1676+
headers,
1677+
body,
1678+
// @ts-ignore - Node.js doesn't have duplex, but it's safe to ignore
1679+
duplex: "half",
1680+
});
1681+
1682+
// Handle the request with extracted IP
1683+
const response = await this.handleWithIp(webRequest, clientIp);
1684+
1685+
// Convert Web Response to Node.js response
1686+
const responseHeaders: Record<string, string> = {};
1687+
response.headers.forEach((value, key) => {
1688+
responseHeaders[key] = value;
1689+
});
1690+
1691+
res.writeHead(response.status, responseHeaders);
1692+
1693+
// Stream the response body
1694+
if (response.body) {
1695+
const reader = response.body.getReader();
1696+
try {
1697+
while (true) {
1698+
const { done, value } = await reader.read();
1699+
if (done) break;
1700+
res.write(value);
1701+
}
1702+
} finally {
1703+
reader.releaseLock();
1704+
}
1705+
}
1706+
1707+
res.end();
1708+
} catch (error) {
1709+
// Handle errors
1710+
if (!res.headersSent) {
1711+
res.writeHead(500, { "Content-Type": "text/plain" });
1712+
}
1713+
res.end("Internal Server Error");
1714+
console.error("Error in handleNode:", error);
1715+
}
1716+
}
15841717
}
15851718

15861719
/**

packages/middleware/jsr.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rabbit-company/web-middleware",
3-
"version": "0.5.0",
3+
"version": "0.6.0",
44
"license": "MIT",
55
"exports": {
66
"./basic-auth": "./src/basic-auth.ts",

packages/middleware/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@rabbit-company/web-middleware",
3-
"version": "0.5.0",
3+
"version": "0.6.0",
44
"description": "Official middleware collection for Rabbit Company Web Framework",
55
"type": "module",
66
"homepage": "https://github.com/Rabbit-Company/Web-JS",

0 commit comments

Comments
 (0)