smartproxy/readme.plan.md

316 lines
8.4 KiB
Markdown
Raw Normal View History

# SmartProxy Development Plan
## Implementation Plan: Socket Handler Function Support (Simplified) ✅ COMPLETED
### 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<void>;
// 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<void>;
```
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
---
## Implementation Notes (Completed)
### What Was Implemented
1. **Type Definitions** - Added 'socket-handler' to TRouteActionType and TSocketHandler type
2. **Route Handler** - Added socket-handler case in RouteConnectionHandler switch statement
3. **Error Handling** - Both sync and async errors are caught and logged
4. **Initial Data Handling** - Initial chunks are re-emitted to handler's listeners
5. **Helper Functions** - Added createSocketHandlerRoute and pre-built handlers (echo, proxy, etc.)
6. **Full Test Coverage** - All test cases pass including async handlers and error handling
### Key Implementation Details
- Socket handlers require initial data from client to trigger routing (not TLS handshake)
- The handler receives the raw socket after route matching
- Both sync and async handlers are supported
- Errors in handlers terminate the connection gracefully
- Helper utilities provide common patterns (echo server, TCP proxy, line protocol)
### Usage Notes
- Clients must send initial data to trigger the handler (even just a newline)
- The socket is passed directly to the handler function
- Handler has complete control over the socket lifecycle
- No special context object needed - keeps it simple
**Total implementation time: ~3 hours**