fix(route-connection-handler): Forward non-TLS connections on HttpProxy ports to fix ACME HTTP-01 challenge handling
This commit is contained in:
183
test/test.http-fix-unit.ts
Normal file
183
test/test.http-fix-unit.ts
Normal file
@ -0,0 +1,183 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import * as net from 'net';
|
||||
|
||||
// Unit test for the HTTP forwarding fix
|
||||
tap.test('should forward non-TLS connections on HttpProxy ports', async (tapTest) => {
|
||||
// Test configuration
|
||||
const testPort = 8080;
|
||||
const httpProxyPort = 8844;
|
||||
|
||||
// Track forwarding logic
|
||||
let forwardedToHttpProxy = false;
|
||||
let setupDirectConnection = false;
|
||||
|
||||
// Create mock settings
|
||||
const mockSettings = {
|
||||
useHttpProxy: [testPort],
|
||||
httpProxyPort: httpProxyPort,
|
||||
routes: [{
|
||||
name: 'test-route',
|
||||
match: { ports: testPort },
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 8181 }
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
// Create mock connection record
|
||||
const mockRecord = {
|
||||
id: 'test-connection',
|
||||
localPort: testPort,
|
||||
remoteIP: '127.0.0.1',
|
||||
isTLS: false
|
||||
};
|
||||
|
||||
// Mock HttpProxyBridge
|
||||
const mockHttpProxyBridge = {
|
||||
getHttpProxy: () => ({ available: true }),
|
||||
forwardToHttpProxy: async () => {
|
||||
forwardedToHttpProxy = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Test the logic from handleForwardAction
|
||||
const route = mockSettings.routes[0];
|
||||
const action = route.action;
|
||||
|
||||
// Simulate the fixed logic
|
||||
if (!action.tls) {
|
||||
// No TLS settings - check if this port should use HttpProxy
|
||||
const isHttpProxyPort = mockSettings.useHttpProxy?.includes(mockRecord.localPort);
|
||||
|
||||
if (isHttpProxyPort && mockHttpProxyBridge.getHttpProxy()) {
|
||||
// Forward non-TLS connections to HttpProxy if configured
|
||||
console.log(`Using HttpProxy for non-TLS connection on port ${mockRecord.localPort}`);
|
||||
await mockHttpProxyBridge.forwardToHttpProxy();
|
||||
} else {
|
||||
// Basic forwarding
|
||||
console.log(`Using basic forwarding`);
|
||||
setupDirectConnection = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the fix works correctly
|
||||
expect(forwardedToHttpProxy).toEqual(true);
|
||||
expect(setupDirectConnection).toEqual(false);
|
||||
|
||||
console.log('Test passed: Non-TLS connections on HttpProxy ports are forwarded correctly');
|
||||
});
|
||||
|
||||
// Test that non-HttpProxy ports still use direct connection
|
||||
tap.test('should use direct connection for non-HttpProxy ports', async (tapTest) => {
|
||||
let forwardedToHttpProxy = false;
|
||||
let setupDirectConnection = false;
|
||||
|
||||
const mockSettings = {
|
||||
useHttpProxy: [80, 443], // Different ports
|
||||
httpProxyPort: 8844,
|
||||
routes: [{
|
||||
name: 'test-route',
|
||||
match: { ports: 8080 }, // Not in useHttpProxy
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 8181 }
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
const mockRecord = {
|
||||
id: 'test-connection-2',
|
||||
localPort: 8080, // Not in useHttpProxy
|
||||
remoteIP: '127.0.0.1',
|
||||
isTLS: false
|
||||
};
|
||||
|
||||
const mockHttpProxyBridge = {
|
||||
getHttpProxy: () => ({ available: true }),
|
||||
forwardToHttpProxy: async () => {
|
||||
forwardedToHttpProxy = true;
|
||||
}
|
||||
};
|
||||
|
||||
const route = mockSettings.routes[0];
|
||||
const action = route.action;
|
||||
|
||||
// Test the logic
|
||||
if (!action.tls) {
|
||||
const isHttpProxyPort = mockSettings.useHttpProxy?.includes(mockRecord.localPort);
|
||||
|
||||
if (isHttpProxyPort && mockHttpProxyBridge.getHttpProxy()) {
|
||||
console.log(`Using HttpProxy for non-TLS connection on port ${mockRecord.localPort}`);
|
||||
await mockHttpProxyBridge.forwardToHttpProxy();
|
||||
} else {
|
||||
console.log(`Using basic forwarding for port ${mockRecord.localPort}`);
|
||||
setupDirectConnection = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify port 8080 uses direct connection when not in useHttpProxy
|
||||
expect(forwardedToHttpProxy).toEqual(false);
|
||||
expect(setupDirectConnection).toEqual(true);
|
||||
|
||||
console.log('Test passed: Non-HttpProxy ports use direct connection');
|
||||
});
|
||||
|
||||
// Test HTTP-01 ACME challenge scenario
|
||||
tap.test('should handle ACME HTTP-01 challenges on port 80 with HttpProxy', async (tapTest) => {
|
||||
let forwardedToHttpProxy = false;
|
||||
|
||||
const mockSettings = {
|
||||
useHttpProxy: [80], // Port 80 configured for HttpProxy
|
||||
httpProxyPort: 8844,
|
||||
acme: {
|
||||
port: 80,
|
||||
email: 'test@example.com'
|
||||
},
|
||||
routes: [{
|
||||
name: 'acme-challenge',
|
||||
match: {
|
||||
ports: 80,
|
||||
paths: ['/.well-known/acme-challenge/*']
|
||||
},
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 8080 }
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
const mockRecord = {
|
||||
id: 'acme-connection',
|
||||
localPort: 80,
|
||||
remoteIP: '127.0.0.1',
|
||||
isTLS: false
|
||||
};
|
||||
|
||||
const mockHttpProxyBridge = {
|
||||
getHttpProxy: () => ({ available: true }),
|
||||
forwardToHttpProxy: async () => {
|
||||
forwardedToHttpProxy = true;
|
||||
}
|
||||
};
|
||||
|
||||
const route = mockSettings.routes[0];
|
||||
const action = route.action;
|
||||
|
||||
// Test the fix for ACME HTTP-01 challenges
|
||||
if (!action.tls) {
|
||||
const isHttpProxyPort = mockSettings.useHttpProxy?.includes(mockRecord.localPort);
|
||||
|
||||
if (isHttpProxyPort && mockHttpProxyBridge.getHttpProxy()) {
|
||||
console.log(`Using HttpProxy for ACME challenge on port ${mockRecord.localPort}`);
|
||||
await mockHttpProxyBridge.forwardToHttpProxy();
|
||||
}
|
||||
}
|
||||
|
||||
// Verify HTTP-01 challenges on port 80 go through HttpProxy
|
||||
expect(forwardedToHttpProxy).toEqual(true);
|
||||
|
||||
console.log('Test passed: ACME HTTP-01 challenges on port 80 use HttpProxy');
|
||||
});
|
||||
|
||||
tap.start();
|
168
test/test.http-fix-verification.ts
Normal file
168
test/test.http-fix-verification.ts
Normal file
@ -0,0 +1,168 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { RouteConnectionHandler } from '../ts/proxies/smart-proxy/route-connection-handler.js';
|
||||
import { ISmartProxyOptions } from '../ts/proxies/smart-proxy/models/interfaces.js';
|
||||
import * as net from 'net';
|
||||
|
||||
// Direct test of the fix in RouteConnectionHandler
|
||||
tap.test('should detect and forward non-TLS connections on useHttpProxy ports', async (tapTest) => {
|
||||
// Create mock objects
|
||||
const mockSettings: ISmartProxyOptions = {
|
||||
useHttpProxy: [8080],
|
||||
httpProxyPort: 8844,
|
||||
routes: [{
|
||||
name: 'test-route',
|
||||
match: { ports: 8080 },
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 8181 }
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
let httpProxyForwardCalled = false;
|
||||
let directConnectionCalled = false;
|
||||
|
||||
// Create mocks for dependencies
|
||||
const mockHttpProxyBridge = {
|
||||
getHttpProxy: () => ({ available: true }),
|
||||
forwardToHttpProxy: async (...args: any[]) => {
|
||||
console.log('Mock: forwardToHttpProxy called');
|
||||
httpProxyForwardCalled = true;
|
||||
}
|
||||
};
|
||||
|
||||
// Mock connection manager
|
||||
const mockConnectionManager = {
|
||||
createConnection: (socket: any) => ({
|
||||
id: 'test-connection',
|
||||
localPort: 8080,
|
||||
remoteIP: '127.0.0.1',
|
||||
isTLS: false
|
||||
}),
|
||||
initiateCleanupOnce: () => {},
|
||||
cleanupConnection: () => {}
|
||||
};
|
||||
|
||||
// Mock route manager that returns a matching route
|
||||
const mockRouteManager = {
|
||||
findMatchingRoute: (criteria: any) => ({
|
||||
route: mockSettings.routes[0]
|
||||
})
|
||||
};
|
||||
|
||||
// Create route connection handler instance
|
||||
const handler = new RouteConnectionHandler(
|
||||
mockSettings,
|
||||
mockConnectionManager as any,
|
||||
{} as any, // security manager
|
||||
{} as any, // tls manager
|
||||
mockHttpProxyBridge as any,
|
||||
{} as any, // timeout manager
|
||||
mockRouteManager as any
|
||||
);
|
||||
|
||||
// Override setupDirectConnection to track if it's called
|
||||
handler['setupDirectConnection'] = (...args: any[]) => {
|
||||
console.log('Mock: setupDirectConnection called');
|
||||
directConnectionCalled = true;
|
||||
};
|
||||
|
||||
// Test: Create a mock socket representing non-TLS connection on port 8080
|
||||
const mockSocket = new net.Socket();
|
||||
mockSocket.localPort = 8080;
|
||||
mockSocket.remoteAddress = '127.0.0.1';
|
||||
|
||||
// Simulate the handler processing the connection
|
||||
handler.handleConnection(mockSocket);
|
||||
|
||||
// Simulate receiving non-TLS data
|
||||
mockSocket.emit('data', Buffer.from('GET / HTTP/1.1\r\nHost: test.local\r\n\r\n'));
|
||||
|
||||
// Give it a moment to process
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Verify that the connection was forwarded to HttpProxy, not direct connection
|
||||
expect(httpProxyForwardCalled).toEqual(true);
|
||||
expect(directConnectionCalled).toEqual(false);
|
||||
|
||||
mockSocket.destroy();
|
||||
});
|
||||
|
||||
// Test that verifies TLS connections still work normally
|
||||
tap.test('should handle TLS connections normally', async (tapTest) => {
|
||||
const mockSettings: ISmartProxyOptions = {
|
||||
useHttpProxy: [443],
|
||||
httpProxyPort: 8844,
|
||||
routes: [{
|
||||
name: 'tls-route',
|
||||
match: { ports: 443 },
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 8443 },
|
||||
tls: { mode: 'terminate' }
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
let httpProxyForwardCalled = false;
|
||||
|
||||
const mockHttpProxyBridge = {
|
||||
getHttpProxy: () => ({ available: true }),
|
||||
forwardToHttpProxy: async (...args: any[]) => {
|
||||
httpProxyForwardCalled = true;
|
||||
}
|
||||
};
|
||||
|
||||
const mockConnectionManager = {
|
||||
createConnection: (socket: any) => ({
|
||||
id: 'test-tls-connection',
|
||||
localPort: 443,
|
||||
remoteIP: '127.0.0.1',
|
||||
isTLS: true,
|
||||
tlsHandshakeComplete: false
|
||||
}),
|
||||
initiateCleanupOnce: () => {},
|
||||
cleanupConnection: () => {}
|
||||
};
|
||||
|
||||
const mockTlsManager = {
|
||||
isTlsHandshake: (chunk: Buffer) => true,
|
||||
isClientHello: (chunk: Buffer) => true,
|
||||
extractSNI: (chunk: Buffer) => 'test.local'
|
||||
};
|
||||
|
||||
const mockRouteManager = {
|
||||
findMatchingRoute: (criteria: any) => ({
|
||||
route: mockSettings.routes[0]
|
||||
})
|
||||
};
|
||||
|
||||
const handler = new RouteConnectionHandler(
|
||||
mockSettings,
|
||||
mockConnectionManager as any,
|
||||
{} as any,
|
||||
mockTlsManager as any,
|
||||
mockHttpProxyBridge as any,
|
||||
{} as any,
|
||||
mockRouteManager as any
|
||||
);
|
||||
|
||||
const mockSocket = new net.Socket();
|
||||
mockSocket.localPort = 443;
|
||||
mockSocket.remoteAddress = '127.0.0.1';
|
||||
|
||||
handler.handleConnection(mockSocket);
|
||||
|
||||
// Simulate TLS handshake
|
||||
const tlsHandshake = Buffer.from([0x16, 0x03, 0x01, 0x00, 0x05]);
|
||||
mockSocket.emit('data', tlsHandshake);
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// TLS connections with 'terminate' mode should go to HttpProxy
|
||||
expect(httpProxyForwardCalled).toEqual(true);
|
||||
|
||||
mockSocket.destroy();
|
||||
});
|
||||
|
||||
tap.start();
|
150
test/test.http-forwarding-fix.ts
Normal file
150
test/test.http-forwarding-fix.ts
Normal file
@ -0,0 +1,150 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { SmartProxy } from '../ts/index.js';
|
||||
import * as net from 'net';
|
||||
|
||||
// Test that verifies HTTP connections on ports configured in useHttpProxy are properly forwarded
|
||||
tap.test('should detect and forward non-TLS connections on HttpProxy ports', async (tapTest) => {
|
||||
// Track whether the connection was forwarded to HttpProxy
|
||||
let forwardedToHttpProxy = false;
|
||||
let connectionPath = '';
|
||||
|
||||
// Mock the HttpProxy forwarding
|
||||
const originalForward = SmartProxy.prototype['httpProxyBridge'].prototype.forwardToHttpProxy;
|
||||
SmartProxy.prototype['httpProxyBridge'].prototype.forwardToHttpProxy = function(...args: any[]) {
|
||||
forwardedToHttpProxy = true;
|
||||
connectionPath = 'httpproxy';
|
||||
console.log('Mock: Connection forwarded to HttpProxy');
|
||||
// Just close the connection for the test
|
||||
args[1].end(); // socket.end()
|
||||
};
|
||||
|
||||
// Create a SmartProxy with useHttpProxy configured
|
||||
const proxy = new SmartProxy({
|
||||
useHttpProxy: [8080],
|
||||
httpProxyPort: 8844,
|
||||
enableDetailedLogging: true,
|
||||
routes: [{
|
||||
name: 'test-route',
|
||||
match: {
|
||||
ports: 8080
|
||||
},
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: 8181 }
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
// Override the HttpProxy initialization to avoid actual HttpProxy setup
|
||||
proxy['httpProxyBridge'].getHttpProxy = () => ({} as any);
|
||||
|
||||
await proxy.start();
|
||||
|
||||
// Make a connection to port 8080
|
||||
const client = new net.Socket();
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
client.connect(8080, 'localhost', () => {
|
||||
console.log('Client connected to proxy on port 8080');
|
||||
// Send a non-TLS HTTP request
|
||||
client.write('GET / HTTP/1.1\r\nHost: test.local\r\n\r\n');
|
||||
resolve();
|
||||
});
|
||||
|
||||
client.on('error', reject);
|
||||
});
|
||||
|
||||
// Give it a moment to process
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Verify the connection was forwarded to HttpProxy
|
||||
expect(forwardedToHttpProxy).toEqual(true);
|
||||
expect(connectionPath).toEqual('httpproxy');
|
||||
|
||||
client.destroy();
|
||||
await proxy.stop();
|
||||
|
||||
// Restore original method
|
||||
SmartProxy.prototype['httpProxyBridge'].prototype.forwardToHttpProxy = originalForward;
|
||||
});
|
||||
|
||||
// Test that verifies the fix detects non-TLS connections
|
||||
tap.test('should properly detect non-TLS connections on HttpProxy ports', async (tapTest) => {
|
||||
const targetPort = 8182;
|
||||
let receivedConnection = false;
|
||||
|
||||
// Create a target server that never receives the connection (because it goes to HttpProxy)
|
||||
const targetServer = net.createServer((socket) => {
|
||||
receivedConnection = true;
|
||||
socket.end();
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
targetServer.listen(targetPort, () => {
|
||||
console.log(`Target server listening on port ${targetPort}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Mock HttpProxyBridge to track forwarding
|
||||
let httpProxyForwardCalled = false;
|
||||
|
||||
const proxy = new SmartProxy({
|
||||
useHttpProxy: [8080],
|
||||
httpProxyPort: 8844,
|
||||
routes: [{
|
||||
name: 'test-route',
|
||||
match: {
|
||||
ports: 8080
|
||||
},
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: targetPort }
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
// Override the forwardToHttpProxy method to track calls
|
||||
const originalForward = proxy['httpProxyBridge'].forwardToHttpProxy;
|
||||
proxy['httpProxyBridge'].forwardToHttpProxy = async function(...args: any[]) {
|
||||
httpProxyForwardCalled = true;
|
||||
console.log('HttpProxy forward called with connectionId:', args[0]);
|
||||
// Just end the connection
|
||||
args[1].end();
|
||||
};
|
||||
|
||||
// Mock getHttpProxy to return a truthy value
|
||||
proxy['httpProxyBridge'].getHttpProxy = () => ({} as any);
|
||||
|
||||
await proxy.start();
|
||||
|
||||
// Make a non-TLS connection
|
||||
const client = new net.Socket();
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
client.connect(8080, 'localhost', () => {
|
||||
console.log('Connected to proxy');
|
||||
client.write('GET / HTTP/1.1\r\nHost: test.local\r\n\r\n');
|
||||
resolve();
|
||||
});
|
||||
|
||||
client.on('error', () => resolve()); // Ignore errors since we're ending the connection
|
||||
});
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// Verify that HttpProxy was called, not direct connection
|
||||
expect(httpProxyForwardCalled).toEqual(true);
|
||||
expect(receivedConnection).toEqual(false); // Target should not receive direct connection
|
||||
|
||||
client.destroy();
|
||||
await proxy.stop();
|
||||
await new Promise<void>((resolve) => {
|
||||
targetServer.close(() => resolve());
|
||||
});
|
||||
|
||||
// Restore original method
|
||||
proxy['httpProxyBridge'].forwardToHttpProxy = originalForward;
|
||||
});
|
||||
|
||||
tap.start();
|
160
test/test.http-port8080-forwarding.ts
Normal file
160
test/test.http-port8080-forwarding.ts
Normal file
@ -0,0 +1,160 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { SmartProxy } from '../ts/index.js';
|
||||
import * as http from 'http';
|
||||
|
||||
tap.test('should forward HTTP connections on port 8080 to HttpProxy', async (tapTest) => {
|
||||
// Create a mock HTTP server to act as our target
|
||||
const targetPort = 8181;
|
||||
let receivedRequest = false;
|
||||
let receivedPath = '';
|
||||
|
||||
const targetServer = http.createServer((req, res) => {
|
||||
// Log request details for debugging
|
||||
console.log(`Target server received: ${req.method} ${req.url}`);
|
||||
receivedPath = req.url || '';
|
||||
|
||||
if (req.url === '/.well-known/acme-challenge/test-token') {
|
||||
receivedRequest = true;
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('test-challenge-response');
|
||||
} else {
|
||||
res.writeHead(200);
|
||||
res.end('OK');
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
targetServer.listen(targetPort, () => {
|
||||
console.log(`Target server listening on port ${targetPort}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Create SmartProxy with port 8080 configured for HttpProxy
|
||||
const proxy = new SmartProxy({
|
||||
useHttpProxy: [8080], // Enable HttpProxy for port 8080
|
||||
httpProxyPort: 8844,
|
||||
enableDetailedLogging: true,
|
||||
routes: [{
|
||||
name: 'test-route',
|
||||
match: {
|
||||
ports: 8080,
|
||||
domains: ['test.local']
|
||||
},
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: targetPort }
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
await proxy.start();
|
||||
|
||||
// Give the proxy a moment to fully initialize
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Make an HTTP request to port 8080
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 8080,
|
||||
path: '/.well-known/acme-challenge/test-token',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Host': 'test.local'
|
||||
}
|
||||
};
|
||||
|
||||
const response = await new Promise<http.IncomingMessage>((resolve, reject) => {
|
||||
const req = http.request(options, (res) => resolve(res));
|
||||
req.on('error', reject);
|
||||
req.end();
|
||||
});
|
||||
|
||||
// Collect response data
|
||||
let responseData = '';
|
||||
response.setEncoding('utf8');
|
||||
response.on('data', chunk => responseData += chunk);
|
||||
await new Promise(resolve => response.on('end', resolve));
|
||||
|
||||
// Verify the request was properly forwarded
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(receivedPath).toEqual('/.well-known/acme-challenge/test-token');
|
||||
expect(responseData).toEqual('test-challenge-response');
|
||||
expect(receivedRequest).toEqual(true);
|
||||
|
||||
await proxy.stop();
|
||||
await new Promise<void>((resolve) => {
|
||||
targetServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('should handle basic HTTP request forwarding', async (tapTest) => {
|
||||
// Create a simple target server
|
||||
const targetPort = 8182;
|
||||
let receivedRequest = false;
|
||||
|
||||
const targetServer = http.createServer((req, res) => {
|
||||
console.log(`Target received: ${req.method} ${req.url} from ${req.headers.host}`);
|
||||
receivedRequest = true;
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('Hello from target');
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
targetServer.listen(targetPort, () => {
|
||||
console.log(`Target server listening on port ${targetPort}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Create a simple proxy without HttpProxy
|
||||
const proxy = new SmartProxy({
|
||||
routes: [{
|
||||
name: 'simple-forward',
|
||||
match: {
|
||||
ports: 8081,
|
||||
domains: ['test.local']
|
||||
},
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: targetPort }
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
await proxy.start();
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// Make request
|
||||
const options = {
|
||||
hostname: 'localhost',
|
||||
port: 8081,
|
||||
path: '/test',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Host': 'test.local'
|
||||
}
|
||||
};
|
||||
|
||||
const response = await new Promise<http.IncomingMessage>((resolve, reject) => {
|
||||
const req = http.request(options, (res) => resolve(res));
|
||||
req.on('error', reject);
|
||||
req.end();
|
||||
});
|
||||
|
||||
let responseData = '';
|
||||
response.setEncoding('utf8');
|
||||
response.on('data', chunk => responseData += chunk);
|
||||
await new Promise(resolve => response.on('end', resolve));
|
||||
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(responseData).toEqual('Hello from target');
|
||||
expect(receivedRequest).toEqual(true);
|
||||
|
||||
await proxy.stop();
|
||||
await new Promise<void>((resolve) => {
|
||||
targetServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.start();
|
96
test/test.http-port8080-simple.ts
Normal file
96
test/test.http-port8080-simple.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { SmartProxy } from '../ts/index.js';
|
||||
import * as net from 'net';
|
||||
|
||||
tap.test('should forward HTTP connections on port 8080 to HttpProxy', async (tapTest) => {
|
||||
// Create a simple echo server to act as our target
|
||||
const targetPort = 8181;
|
||||
let receivedData = '';
|
||||
|
||||
const targetServer = net.createServer((socket) => {
|
||||
console.log('Target server received connection');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
receivedData += data.toString();
|
||||
console.log('Target server received data:', data.toString().split('\n')[0]);
|
||||
|
||||
// Send a simple HTTP response
|
||||
const response = 'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\n\r\nHello, World!';
|
||||
socket.write(response);
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve) => {
|
||||
targetServer.listen(targetPort, () => {
|
||||
console.log(`Target server listening on port ${targetPort}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// Create SmartProxy with port 8080 configured for HttpProxy
|
||||
const proxy = new SmartProxy({
|
||||
useHttpProxy: [8080], // Enable HttpProxy for port 8080
|
||||
httpProxyPort: 8844,
|
||||
enableDetailedLogging: true,
|
||||
routes: [{
|
||||
name: 'test-route',
|
||||
match: {
|
||||
ports: 8080
|
||||
},
|
||||
action: {
|
||||
type: 'forward',
|
||||
target: { host: 'localhost', port: targetPort }
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
await proxy.start();
|
||||
|
||||
// Give the proxy a moment to fully initialize
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
console.log('Making test connection to proxy on port 8080...');
|
||||
|
||||
// Create a simple TCP connection to test
|
||||
const client = new net.Socket();
|
||||
const responsePromise = new Promise<string>((resolve, reject) => {
|
||||
let response = '';
|
||||
|
||||
client.on('data', (data) => {
|
||||
response += data.toString();
|
||||
console.log('Client received:', data.toString());
|
||||
});
|
||||
|
||||
client.on('end', () => {
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
client.on('error', reject);
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
client.connect(8080, 'localhost', () => {
|
||||
console.log('Client connected to proxy');
|
||||
// Send a simple HTTP request
|
||||
client.write('GET / HTTP/1.1\r\nHost: test.local\r\n\r\n');
|
||||
resolve();
|
||||
});
|
||||
|
||||
client.on('error', reject);
|
||||
});
|
||||
|
||||
// Wait for response
|
||||
const response = await responsePromise;
|
||||
|
||||
// Check that we got the response
|
||||
expect(response).toContain('Hello, World!');
|
||||
expect(receivedData).toContain('GET / HTTP/1.1');
|
||||
|
||||
client.destroy();
|
||||
await proxy.stop();
|
||||
await new Promise<void>((resolve) => {
|
||||
targetServer.close(() => resolve());
|
||||
});
|
||||
});
|
||||
|
||||
tap.start();
|
Reference in New Issue
Block a user