@push.rocks/smartproxy
Version:
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.
268 lines • 21.3 kB
JavaScript
/**
* Socket Handler Functions
*
* This module provides pre-built socket handlers for common use cases
* like echoing, proxying, HTTP responses, and redirects.
*/
import * as plugins from '../../../plugins.js';
import { createSocketTracker } from '../../../core/utils/socket-tracker.js';
/**
* Minimal HTTP request parser for socket handlers.
* Parses method, path, and optionally headers from a raw buffer.
*/
function parseHttpRequest(data, extractHeaders = false) {
const str = data.toString('utf8');
const headerEnd = str.indexOf('\r\n\r\n');
const isComplete = headerEnd !== -1;
const headerSection = isComplete ? str.slice(0, headerEnd) : str;
const lines = headerSection.split('\r\n');
const requestLine = lines[0];
if (!requestLine)
return null;
const parts = requestLine.split(' ');
if (parts.length < 2)
return null;
const method = parts[0];
const path = parts[1];
// Quick check: valid HTTP method
const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS', 'CONNECT', 'TRACE'];
if (!validMethods.includes(method))
return null;
const headers = {};
if (extractHeaders) {
for (let i = 1; i < lines.length; i++) {
const colonIdx = lines[i].indexOf(':');
if (colonIdx > 0) {
const name = lines[i].slice(0, colonIdx).trim().toLowerCase();
const value = lines[i].slice(colonIdx + 1).trim();
headers[name] = value;
}
}
}
const body = isComplete ? str.slice(headerEnd + 4) : undefined;
return { method, path, headers, isComplete, body };
}
/**
* Pre-built socket handlers for common use cases
*/
export const SocketHandlers = {
/**
* Simple echo server handler
*/
echo: (socket, context) => {
socket.write('ECHO SERVER READY\n');
socket.on('data', data => socket.write(data));
},
/**
* TCP proxy handler
*/
proxy: (targetHost, targetPort) => (socket, context) => {
const target = plugins.net.connect(targetPort, targetHost);
socket.pipe(target);
target.pipe(socket);
socket.on('close', () => target.destroy());
target.on('close', () => socket.destroy());
target.on('error', (err) => {
console.error('Proxy target error:', err);
socket.destroy();
});
},
/**
* Line-based protocol handler
*/
lineProtocol: (handler) => (socket, context) => {
let buffer = '';
socket.on('data', (data) => {
buffer += data.toString();
const lines = buffer.split('\n');
buffer = lines.pop() || '';
lines.forEach(line => {
if (line.trim()) {
handler(line.trim(), socket);
}
});
});
},
/**
* Simple HTTP response handler (for testing)
*/
httpResponse: (statusCode, body) => (socket, context) => {
const response = [
`HTTP/1.1 ${statusCode} ${statusCode === 200 ? 'OK' : 'Error'}`,
'Content-Type: text/plain',
`Content-Length: ${body.length}`,
'Connection: close',
'',
body
].join('\r\n');
socket.write(response);
socket.end();
},
/**
* Block connection immediately
*/
block: (message) => (socket, context) => {
const finalMessage = message || `Connection blocked from ${context.clientIp}`;
if (finalMessage) {
socket.write(finalMessage);
}
socket.end();
},
/**
* HTTP block response
*/
httpBlock: (statusCode = 403, message) => (socket, context) => {
const defaultMessage = `Access forbidden for ${context.domain || context.clientIp}`;
const finalMessage = message || defaultMessage;
const response = [
`HTTP/1.1 ${statusCode} ${finalMessage}`,
'Content-Type: text/plain',
`Content-Length: ${finalMessage.length}`,
'Connection: close',
'',
finalMessage
].join('\r\n');
socket.write(response);
socket.end();
},
/**
* HTTP redirect handler
*/
httpRedirect: (locationTemplate, statusCode = 301) => (socket, context) => {
const tracker = createSocketTracker(socket);
const handleData = (data) => {
const parsed = parseHttpRequest(data);
if (parsed) {
const path = parsed.path || '/';
const domain = context.domain || 'localhost';
const port = context.port;
const finalLocation = locationTemplate
.replace('{domain}', domain)
.replace('{port}', String(port))
.replace('{path}', path)
.replace('{clientIp}', context.clientIp);
const message = `Redirecting to ${finalLocation}`;
const response = [
`HTTP/1.1 ${statusCode} ${statusCode === 301 ? 'Moved Permanently' : 'Found'}`,
`Location: ${finalLocation}`,
'Content-Type: text/plain',
`Content-Length: ${message.length}`,
'Connection: close',
'',
message
].join('\r\n');
socket.write(response);
}
else {
socket.write('HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n');
}
socket.end();
tracker.cleanup();
};
socket.once('data', handleData);
tracker.addListener('error', (err) => {
tracker.safeDestroy(err);
});
tracker.addListener('close', () => {
tracker.cleanup();
});
},
/**
* HTTP server handler for ACME challenges and other HTTP needs
*/
httpServer: (handler) => (socket, context) => {
const tracker = createSocketTracker(socket);
let requestParsed = false;
let responseTimer = null;
const processData = (data) => {
if (requestParsed)
return;
const parsed = parseHttpRequest(data, true);
if (!parsed || !parsed.isComplete) {
return; // Not a complete HTTP request yet
}
requestParsed = true;
socket.removeListener('data', processData);
const req = {
method: parsed.method,
url: parsed.path,
headers: parsed.headers,
body: parsed.body || ''
};
let statusCode = 200;
const responseHeaders = {};
let ended = false;
const res = {
status: (code) => {
statusCode = code;
},
header: (name, value) => {
responseHeaders[name] = value;
},
send: (data) => {
if (ended)
return;
ended = true;
if (responseTimer) {
clearTimeout(responseTimer);
responseTimer = null;
}
if (!responseHeaders['content-type']) {
responseHeaders['content-type'] = 'text/plain';
}
responseHeaders['content-length'] = String(data.length);
responseHeaders['connection'] = 'close';
const statusText = statusCode === 200 ? 'OK' :
statusCode === 404 ? 'Not Found' :
statusCode === 500 ? 'Internal Server Error' : 'Response';
let response = `HTTP/1.1 ${statusCode} ${statusText}\r\n`;
for (const [name, value] of Object.entries(responseHeaders)) {
response += `${name}: ${value}\r\n`;
}
response += '\r\n';
response += data;
socket.write(response);
socket.end();
},
end: () => {
if (ended)
return;
ended = true;
socket.write('HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n');
socket.end();
}
};
try {
handler(req, res);
responseTimer = setTimeout(() => {
if (!ended) {
res.send('');
}
responseTimer = null;
}, 1000);
tracker.addTimer(responseTimer);
}
catch (error) {
if (!ended) {
res.status(500);
res.send('Internal Server Error');
}
tracker.safeDestroy(error instanceof Error ? error : new Error('Handler error'));
}
};
tracker.addListener('data', processData);
tracker.addListener('error', (err) => {
if (!requestParsed) {
tracker.safeDestroy(err);
}
});
tracker.addListener('close', () => {
if (responseTimer) {
clearTimeout(responseTimer);
responseTimer = null;
}
tracker.cleanup();
});
}
};
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic29ja2V0LWhhbmRsZXJzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vdHMvcHJveGllcy9zbWFydC1wcm94eS91dGlscy9zb2NrZXQtaGFuZGxlcnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7O0dBS0c7QUFFSCxPQUFPLEtBQUssT0FBTyxNQUFNLHFCQUFxQixDQUFDO0FBRS9DLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxNQUFNLHVDQUF1QyxDQUFDO0FBRTVFOzs7R0FHRztBQUNILFNBQVMsZ0JBQWdCLENBQUMsSUFBWSxFQUFFLGlCQUEwQixLQUFLO0lBT3JFLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDbEMsTUFBTSxTQUFTLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUMxQyxNQUFNLFVBQVUsR0FBRyxTQUFTLEtBQUssQ0FBQyxDQUFDLENBQUM7SUFDcEMsTUFBTSxhQUFhLEdBQUcsVUFBVSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxTQUFTLENBQUMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDO0lBQ2pFLE1BQU0sS0FBSyxHQUFHLGFBQWEsQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDMUMsTUFBTSxXQUFXLEdBQUcsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDO0lBQzdCLElBQUksQ0FBQyxXQUFXO1FBQUUsT0FBTyxJQUFJLENBQUM7SUFFOUIsTUFBTSxLQUFLLEdBQUcsV0FBVyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNyQyxJQUFJLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQztRQUFFLE9BQU8sSUFBSSxDQUFDO0lBRWxDLE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUN4QixNQUFNLElBQUksR0FBRyxLQUFLLENB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