7.1 KiB
7.1 KiB
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
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
// 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:
// 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:
export type TSocketHandler = (
socket: net.Socket,
context?: {
route: IRouteConfig;
clientIp: string;
localPort: number;
}
) => void | Promise<void>;
Usage:
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
// 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
{
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
{
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
{
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
- Dead Simple API: Just pass a function that gets the socket
- No New Classes: No ForwardingHandler subclass needed
- Minimal Changes: Only touches type definitions and one handler method
- Full Power: Users have complete control over the socket
- Backward Compatible: No changes to existing functionality
- Easy to Test: Just test the socket handler functions directly
Implementation Steps
- Add
'socket-handler'
toTRouteActionType
(5 minutes) - Add
socketHandler?: TSocketHandler
toIRouteAction
(5 minutes) - Add socket-handler case in
RouteConnectionHandler.handleConnection()
(15 minutes) - Add helper functions (optional, 30 minutes)
- Write tests (2 hours)
- 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