289 lines
7.1 KiB
Markdown
289 lines
7.1 KiB
Markdown
# 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<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 |