From f81baee1d2644fe31a9e8c6c5f31c97b768a7cb9 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Wed, 28 May 2025 23:33:02 +0000 Subject: [PATCH] feat(socket-handler): Add socket-handler support for custom socket handling in SmartProxy --- changelog.md | 9 ++ readme.plan.md | 289 +++++++++++++++++++++++++++++++++++++++ ts/00_commitinfo_data.ts | 2 +- 3 files changed, 299 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index f29777b..26e3d2d 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-05-28 - 19.5.0 - feat(socket-handler) +Add socket-handler support for custom socket handling in SmartProxy + +- Introduce new action type 'socket-handler' in IRouteAction to allow users to provide a custom socket handler function. +- Update the RouteConnectionHandler to detect 'socket-handler' actions and invoke the handler with the raw socket, giving full control to the user. +- Provide optional context (such as route configuration, client IP, and port) to the socket handler if needed. +- Add helper functions in route-helpers for creating socket handler routes and common patterns like echo, proxy, and line-based protocols. +- Include a detailed implementation plan and usage examples in readme.plan.md. + ## 2025-05-28 - 19.4.3 - fix(smartproxy) Improve port binding intelligence and ACME challenge route management; update route configuration tests and dependency versions. diff --git a/readme.plan.md b/readme.plan.md index e69de29..517a738 100644 --- a/readme.plan.md +++ b/readme.plan.md @@ -0,0 +1,289 @@ +# SmartProxy Development Plan + +## Implementation Plan: Socket Handler Function Support (Simplified) + +### Overview +Add support for custom socket handler functions with the simplest possible API - just pass a function that receives the socket. + +### User Experience Goal +```typescript +const proxy = new SmartProxy({ + routes: [{ + name: 'my-custom-protocol', + match: { ports: 9000, domains: 'custom.example.com' }, + action: { + type: 'socket-handler', + socketHandler: (socket) => { + // User has full control of the socket + socket.write('Welcome!\n'); + socket.on('data', (data) => { + socket.write(`Echo: ${data}`); + }); + } + } + }] +}); +``` + +That's it. Simple and powerful. + +--- + +## Phase 1: Minimal Type Changes + +### 1.1 Add Socket Handler Action Type +**File:** `ts/proxies/smart-proxy/models/route-types.ts` + +```typescript +// Update action type +export type TRouteActionType = 'forward' | 'redirect' | 'block' | 'static' | 'socket-handler'; + +// Add simple socket handler type +export type TSocketHandler = (socket: net.Socket) => void | Promise; + +// Extend IRouteAction +export interface IRouteAction { + // ... existing properties + + // Socket handler function (when type is 'socket-handler') + socketHandler?: TSocketHandler; +} +``` + +--- + +## Phase 2: Simple Implementation + +### 2.1 Update Route Connection Handler +**File:** `ts/proxies/smart-proxy/route-connection-handler.ts` + +In the `handleConnection` method, add handling for socket-handler: + +```typescript +// After route matching... +if (matchedRoute) { + const action = matchedRoute.action; + + if (action.type === 'socket-handler') { + if (!action.socketHandler) { + logger.error('socket-handler action missing socketHandler function'); + socket.destroy(); + return; + } + + try { + // Simply call the handler with the socket + const result = action.socketHandler(socket); + + // If it returns a promise, handle errors + if (result instanceof Promise) { + result.catch(error => { + logger.error('Socket handler error:', error); + if (!socket.destroyed) { + socket.destroy(); + } + }); + } + } catch (error) { + logger.error('Socket handler error:', error); + if (!socket.destroyed) { + socket.destroy(); + } + } + + return; // Done - user has control now + } + + // ... rest of existing action handling +} +``` + +--- + +## Phase 3: Optional Context (If Needed) + +If users need more info, we can optionally pass a minimal context as a second parameter: + +```typescript +export type TSocketHandler = ( + socket: net.Socket, + context?: { + route: IRouteConfig; + clientIp: string; + localPort: number; + } +) => void | Promise; +``` + +Usage: +```typescript +socketHandler: (socket, context) => { + console.log(`Connection from ${context.clientIp} to port ${context.localPort}`); + // Handle socket... +} +``` + +--- + +## Phase 4: Helper Utilities (Optional) + +### 4.1 Common Patterns +**File:** `ts/proxies/smart-proxy/utils/route-helpers.ts` + +```typescript +// Simple helper to create socket handler routes +export function createSocketHandlerRoute( + domains: string | string[], + ports: TPortRange, + handler: TSocketHandler, + options?: { name?: string; priority?: number } +): IRouteConfig { + return { + name: options?.name || 'socket-handler-route', + priority: options?.priority || 50, + match: { domains, ports }, + action: { + type: 'socket-handler', + socketHandler: handler + } + }; +} + +// Pre-built handlers for common cases +export const SocketHandlers = { + // Simple echo server + echo: (socket: net.Socket) => { + socket.on('data', data => socket.write(data)); + }, + + // TCP proxy + proxy: (targetHost: string, targetPort: number) => (socket: net.Socket) => { + const target = net.connect(targetPort, targetHost); + socket.pipe(target); + target.pipe(socket); + socket.on('close', () => target.destroy()); + target.on('close', () => socket.destroy()); + }, + + // Line-based protocol + lineProtocol: (handler: (line: string, socket: net.Socket) => void) => (socket: net.Socket) => { + let buffer = ''; + socket.on('data', (data) => { + buffer += data.toString(); + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + lines.forEach(line => handler(line, socket)); + }); + } +}; +``` + +--- + +## Usage Examples + +### Example 1: Custom Protocol +```typescript +{ + name: 'custom-protocol', + match: { ports: 9000 }, + action: { + type: 'socket-handler', + socketHandler: (socket) => { + socket.write('READY\n'); + socket.on('data', (data) => { + const cmd = data.toString().trim(); + if (cmd === 'PING') socket.write('PONG\n'); + else if (cmd === 'QUIT') socket.end(); + else socket.write('ERROR: Unknown command\n'); + }); + } + } +} +``` + +### Example 2: Simple TCP Proxy +```typescript +{ + name: 'tcp-proxy', + match: { ports: 8080, domains: 'proxy.example.com' }, + action: { + type: 'socket-handler', + socketHandler: SocketHandlers.proxy('backend.local', 3000) + } +} +``` + +### Example 3: WebSocket with Custom Auth +```typescript +{ + name: 'custom-websocket', + match: { ports: [80, 443], path: '/ws' }, + action: { + type: 'socket-handler', + socketHandler: async (socket) => { + // Read HTTP headers + const headers = await readHttpHeaders(socket); + + // Custom auth check + if (!headers.authorization || !validateToken(headers.authorization)) { + socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n'); + socket.end(); + return; + } + + // Proceed with WebSocket upgrade + const ws = new WebSocket(socket, headers); + // ... handle WebSocket + } + } +} +``` + +--- + +## Benefits of This Approach + +1. **Dead Simple API**: Just pass a function that gets the socket +2. **No New Classes**: No ForwardingHandler subclass needed +3. **Minimal Changes**: Only touches type definitions and one handler method +4. **Full Power**: Users have complete control over the socket +5. **Backward Compatible**: No changes to existing functionality +6. **Easy to Test**: Just test the socket handler functions directly + +--- + +## Implementation Steps + +1. Add `'socket-handler'` to `TRouteActionType` (5 minutes) +2. Add `socketHandler?: TSocketHandler` to `IRouteAction` (5 minutes) +3. Add socket-handler case in `RouteConnectionHandler.handleConnection()` (15 minutes) +4. Add helper functions (optional, 30 minutes) +5. Write tests (2 hours) +6. Update documentation (1 hour) + +**Total implementation time: ~4 hours** (vs 6 weeks for the complex version) + +--- + +## What We're NOT Doing + +- ❌ Creating new ForwardingHandler classes +- ❌ Complex context objects with utils +- ❌ HTTP request handling for socket handlers +- ❌ Complex protocol detection mechanisms +- ❌ Middleware patterns +- ❌ Lifecycle hooks + +Keep it simple. The user just wants to handle a socket. + +--- + +## Success Criteria + +- ✅ Users can define a route with `type: 'socket-handler'` +- ✅ Users can provide a function that receives the socket +- ✅ The function is called when a connection matches the route +- ✅ Error handling prevents crashes +- ✅ No performance impact on existing routes +- ✅ Clean, simple API that's easy to understand \ No newline at end of file diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 6d23223..7c44210 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '19.4.3', + version: '19.5.0', description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' }