BREAKING CHANGE(forwarding): Refactor unified forwarding API and remove redundant documentation. Removed docs/forwarding-system.md (its content is migrated into readme.md) and updated helper functions (e.g. replacing sniPassthrough with httpsPassthrough) to accept configuration objects. Legacy fields in domain configurations (allowedIPs, blockedIPs, useNetworkProxy, networkProxyPort, connectionTimeout) have been removed in favor of forwarding.security and advanced options. Tests and examples have been updated accordingly.
This commit is contained in:
@ -26,7 +26,13 @@ class FakeNetworkProxyBridge {
|
||||
|
||||
tap.test('CertProvisioner handles static provisioning', async () => {
|
||||
const domain = 'static.com';
|
||||
const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }];
|
||||
const domainConfigs: IDomainConfig[] = [{
|
||||
domains: [domain],
|
||||
forwarding: {
|
||||
type: 'https-terminate-to-https',
|
||||
target: { host: 'localhost', port: 443 }
|
||||
}
|
||||
}];
|
||||
const fakePort80 = new FakePort80Handler();
|
||||
const fakeBridge = new FakeNetworkProxyBridge();
|
||||
// certProvider returns static certificate
|
||||
@ -68,7 +74,13 @@ tap.test('CertProvisioner handles static provisioning', async () => {
|
||||
|
||||
tap.test('CertProvisioner handles http01 provisioning', async () => {
|
||||
const domain = 'http01.com';
|
||||
const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }];
|
||||
const domainConfigs: IDomainConfig[] = [{
|
||||
domains: [domain],
|
||||
forwarding: {
|
||||
type: 'https-terminate-to-http',
|
||||
target: { host: 'localhost', port: 80 }
|
||||
}
|
||||
}];
|
||||
const fakePort80 = new FakePort80Handler();
|
||||
const fakeBridge = new FakeNetworkProxyBridge();
|
||||
// certProvider returns http01 directive
|
||||
@ -93,7 +105,13 @@ tap.test('CertProvisioner handles http01 provisioning', async () => {
|
||||
|
||||
tap.test('CertProvisioner on-demand http01 renewal', async () => {
|
||||
const domain = 'renew.com';
|
||||
const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }];
|
||||
const domainConfigs: IDomainConfig[] = [{
|
||||
domains: [domain],
|
||||
forwarding: {
|
||||
type: 'https-terminate-to-http',
|
||||
target: { host: 'localhost', port: 80 }
|
||||
}
|
||||
}];
|
||||
const fakePort80 = new FakePort80Handler();
|
||||
const fakeBridge = new FakeNetworkProxyBridge();
|
||||
const certProvider = async (): Promise<ISmartProxyCertProvisionObject> => 'http01';
|
||||
@ -113,7 +131,13 @@ tap.test('CertProvisioner on-demand http01 renewal', async () => {
|
||||
|
||||
tap.test('CertProvisioner on-demand static provisioning', async () => {
|
||||
const domain = 'ondemand.com';
|
||||
const domainConfigs: IDomainConfig[] = [{ domains: [domain], allowedIPs: [] }];
|
||||
const domainConfigs: IDomainConfig[] = [{
|
||||
domains: [domain],
|
||||
forwarding: {
|
||||
type: 'https-terminate-to-https',
|
||||
target: { host: 'localhost', port: 443 }
|
||||
}
|
||||
}];
|
||||
const fakePort80 = new FakePort80Handler();
|
||||
const fakeBridge = new FakeNetworkProxyBridge();
|
||||
const certProvider = async (): Promise<ISmartProxyCertProvisionObject> => ({
|
||||
|
@ -16,11 +16,13 @@ tap.test('Forwarding configuration examples', async (tools) => {
|
||||
// Example 1: HTTP-only configuration
|
||||
const httpOnlyConfig: IDomainConfig = {
|
||||
domains: ['http.example.com'],
|
||||
allowedIPs: [],
|
||||
forwarding: httpOnly({
|
||||
target: {
|
||||
host: 'localhost',
|
||||
port: 3000
|
||||
},
|
||||
security: {
|
||||
allowedIps: ['*'] // Allow all
|
||||
}
|
||||
})
|
||||
};
|
||||
@ -30,11 +32,13 @@ tap.test('Forwarding configuration examples', async (tools) => {
|
||||
// Example 2: HTTPS Passthrough (SNI)
|
||||
const httpsPassthroughConfig: IDomainConfig = {
|
||||
domains: ['pass.example.com'],
|
||||
allowedIPs: [],
|
||||
forwarding: httpsPassthrough({
|
||||
target: {
|
||||
host: ['10.0.0.1', '10.0.0.2'], // Round-robin target IPs
|
||||
port: 443
|
||||
},
|
||||
security: {
|
||||
allowedIps: ['*'] // Allow all
|
||||
}
|
||||
})
|
||||
};
|
||||
@ -45,7 +49,6 @@ tap.test('Forwarding configuration examples', async (tools) => {
|
||||
// Example 3: HTTPS Termination to HTTP Backend
|
||||
const terminateToHttpConfig: IDomainConfig = {
|
||||
domains: ['secure.example.com'],
|
||||
allowedIPs: [],
|
||||
forwarding: tlsTerminateToHttp({
|
||||
target: {
|
||||
host: 'localhost',
|
||||
@ -61,6 +64,9 @@ tap.test('Forwarding configuration examples', async (tools) => {
|
||||
enabled: true,
|
||||
maintenance: true,
|
||||
production: false // Use staging ACME server for testing
|
||||
},
|
||||
security: {
|
||||
allowedIps: ['*'] // Allow all
|
||||
}
|
||||
})
|
||||
};
|
||||
@ -71,7 +77,6 @@ tap.test('Forwarding configuration examples', async (tools) => {
|
||||
// Example 4: HTTPS Termination to HTTPS Backend
|
||||
const terminateToHttpsConfig: IDomainConfig = {
|
||||
domains: ['proxy.example.com'],
|
||||
allowedIPs: [],
|
||||
forwarding: tlsTerminateToHttps({
|
||||
target: {
|
||||
host: 'internal-api.local',
|
||||
|
@ -6,13 +6,13 @@ import type { IForwardConfig, ForwardingType } from '../ts/smartproxy/types/forw
|
||||
import { ForwardingHandlerFactory } from '../ts/smartproxy/forwarding/forwarding.factory.js';
|
||||
import { createDomainConfig } from '../ts/smartproxy/forwarding/domain-config.js';
|
||||
import { DomainManager } from '../ts/smartproxy/forwarding/domain-manager.js';
|
||||
import { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, sniPassthrough } from '../ts/smartproxy/types/forwarding.types.js';
|
||||
import { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, httpsPassthrough } from '../ts/smartproxy/types/forwarding.types.js';
|
||||
|
||||
const helpers = {
|
||||
httpOnly,
|
||||
tlsTerminateToHttp,
|
||||
tlsTerminateToHttps,
|
||||
sniPassthrough
|
||||
sniPassthrough: httpsPassthrough
|
||||
};
|
||||
|
||||
tap.test('ForwardingHandlerFactory - apply defaults based on type', async () => {
|
||||
@ -107,7 +107,9 @@ tap.test('DomainManager - manage domain configurations', async () => {
|
||||
|
||||
// Add a domain configuration
|
||||
await domainManager.addDomainConfig(
|
||||
createDomainConfig('example.com', helpers.httpOnly('localhost', 3000))
|
||||
createDomainConfig('example.com', helpers.httpOnly({
|
||||
target: { host: 'localhost', port: 3000 }
|
||||
}))
|
||||
);
|
||||
|
||||
// Check that the configuration was added
|
||||
@ -132,13 +134,15 @@ tap.test('DomainManager - manage domain configurations', async () => {
|
||||
const handlerAfterRemoval = domainManager.findHandlerForDomain('example.com');
|
||||
expect(handlerAfterRemoval).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
tap.test('DomainManager - support wildcard domains', async () => {
|
||||
const domainManager = new DomainManager();
|
||||
|
||||
// Add a wildcard domain configuration
|
||||
await domainManager.addDomainConfig(
|
||||
createDomainConfig('*.example.com', helpers.httpOnly('localhost', 3000))
|
||||
createDomainConfig('*.example.com', helpers.httpOnly({
|
||||
target: { host: 'localhost', port: 3000 }
|
||||
}))
|
||||
);
|
||||
|
||||
// Find a handler for a subdomain
|
||||
@ -150,15 +154,19 @@ tap.test('DomainManager - support wildcard domains', async () => {
|
||||
expect(noHandler).toBeUndefined();
|
||||
});
|
||||
tap.test('Helper Functions - create http-only forwarding config', async () => {
|
||||
const config = helpers.httpOnly('localhost', 3000);
|
||||
const config = helpers.httpOnly({
|
||||
target: { host: 'localhost', port: 3000 }
|
||||
});
|
||||
expect(config.type).toEqual('http-only');
|
||||
expect(config.target.host).toEqual('localhost');
|
||||
expect(config.target.port).toEqual(3000);
|
||||
expect(config.http?.enabled).toBeTrue();
|
||||
});
|
||||
|
||||
|
||||
tap.test('Helper Functions - create https-terminate-to-http config', async () => {
|
||||
const config = helpers.tlsTerminateToHttp('localhost', 3000);
|
||||
const config = helpers.tlsTerminateToHttp({
|
||||
target: { host: 'localhost', port: 3000 }
|
||||
});
|
||||
expect(config.type).toEqual('https-terminate-to-http');
|
||||
expect(config.target.host).toEqual('localhost');
|
||||
expect(config.target.port).toEqual(3000);
|
||||
@ -166,9 +174,11 @@ tap.test('Helper Functions - create https-terminate-to-http config', async () =>
|
||||
expect(config.acme?.enabled).toBeTrue();
|
||||
expect(config.acme?.maintenance).toBeTrue();
|
||||
});
|
||||
|
||||
|
||||
tap.test('Helper Functions - create https-terminate-to-https config', async () => {
|
||||
const config = helpers.tlsTerminateToHttps('localhost', 8443);
|
||||
const config = helpers.tlsTerminateToHttps({
|
||||
target: { host: 'localhost', port: 8443 }
|
||||
});
|
||||
expect(config.type).toEqual('https-terminate-to-https');
|
||||
expect(config.target.host).toEqual('localhost');
|
||||
expect(config.target.port).toEqual(8443);
|
||||
@ -176,9 +186,11 @@ tap.test('Helper Functions - create https-terminate-to-https config', async () =
|
||||
expect(config.acme?.enabled).toBeTrue();
|
||||
expect(config.acme?.maintenance).toBeTrue();
|
||||
});
|
||||
|
||||
|
||||
tap.test('Helper Functions - create https-passthrough config', async () => {
|
||||
const config = helpers.sniPassthrough('localhost', 443);
|
||||
const config = helpers.sniPassthrough({
|
||||
target: { host: 'localhost', port: 443 }
|
||||
});
|
||||
expect(config.type).toEqual('https-passthrough');
|
||||
expect(config.target.host).toEqual('localhost');
|
||||
expect(config.target.port).toEqual(443);
|
||||
|
@ -6,13 +6,13 @@ import type { IForwardConfig } from '../ts/smartproxy/types/forwarding.types.js'
|
||||
import { ForwardingHandlerFactory } from '../ts/smartproxy/forwarding/forwarding.factory.js';
|
||||
import { createDomainConfig } from '../ts/smartproxy/forwarding/domain-config.js';
|
||||
import { DomainManager } from '../ts/smartproxy/forwarding/domain-manager.js';
|
||||
import { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, sniPassthrough } from '../ts/smartproxy/types/forwarding.types.js';
|
||||
import { httpOnly, tlsTerminateToHttp, tlsTerminateToHttps, httpsPassthrough } from '../ts/smartproxy/types/forwarding.types.js';
|
||||
|
||||
const helpers = {
|
||||
httpOnly,
|
||||
tlsTerminateToHttp,
|
||||
tlsTerminateToHttps,
|
||||
sniPassthrough
|
||||
sniPassthrough: httpsPassthrough
|
||||
};
|
||||
|
||||
tap.test('ForwardingHandlerFactory - apply defaults based on type', async () => {
|
||||
@ -107,7 +107,9 @@ tap.test('DomainManager - manage domain configurations', async () => {
|
||||
|
||||
// Add a domain configuration
|
||||
await domainManager.addDomainConfig(
|
||||
createDomainConfig('example.com', helpers.httpOnly('localhost', 3000))
|
||||
createDomainConfig('example.com', helpers.httpOnly({
|
||||
target: { host: 'localhost', port: 3000 }
|
||||
}))
|
||||
);
|
||||
|
||||
// Check that the configuration was added
|
||||
@ -125,15 +127,19 @@ tap.test('DomainManager - manage domain configurations', async () => {
|
||||
expect(configsAfterRemoval.length).toEqual(0);
|
||||
});
|
||||
tap.test('Helper Functions - create http-only forwarding config', async () => {
|
||||
const config = helpers.httpOnly('localhost', 3000);
|
||||
const config = helpers.httpOnly({
|
||||
target: { host: 'localhost', port: 3000 }
|
||||
});
|
||||
expect(config.type).toEqual('http-only');
|
||||
expect(config.target.host).toEqual('localhost');
|
||||
expect(config.target.port).toEqual(3000);
|
||||
expect(config.http?.enabled).toBeTrue();
|
||||
});
|
||||
|
||||
|
||||
tap.test('Helper Functions - create https-terminate-to-http config', async () => {
|
||||
const config = helpers.tlsTerminateToHttp('localhost', 3000);
|
||||
const config = helpers.tlsTerminateToHttp({
|
||||
target: { host: 'localhost', port: 3000 }
|
||||
});
|
||||
expect(config.type).toEqual('https-terminate-to-http');
|
||||
expect(config.target.host).toEqual('localhost');
|
||||
expect(config.target.port).toEqual(3000);
|
||||
@ -141,9 +147,11 @@ tap.test('Helper Functions - create https-terminate-to-http config', async () =>
|
||||
expect(config.acme?.enabled).toBeTrue();
|
||||
expect(config.acme?.maintenance).toBeTrue();
|
||||
});
|
||||
|
||||
|
||||
tap.test('Helper Functions - create https-terminate-to-https config', async () => {
|
||||
const config = helpers.tlsTerminateToHttps('localhost', 8443);
|
||||
const config = helpers.tlsTerminateToHttps({
|
||||
target: { host: 'localhost', port: 8443 }
|
||||
});
|
||||
expect(config.type).toEqual('https-terminate-to-https');
|
||||
expect(config.target.host).toEqual('localhost');
|
||||
expect(config.target.port).toEqual(8443);
|
||||
@ -151,9 +159,11 @@ tap.test('Helper Functions - create https-terminate-to-https config', async () =
|
||||
expect(config.acme?.enabled).toBeTrue();
|
||||
expect(config.acme?.maintenance).toBeTrue();
|
||||
});
|
||||
|
||||
|
||||
tap.test('Helper Functions - create https-passthrough config', async () => {
|
||||
const config = helpers.sniPassthrough('localhost', 443);
|
||||
const config = helpers.sniPassthrough({
|
||||
target: { host: 'localhost', port: 443 }
|
||||
});
|
||||
expect(config.type).toEqual('https-passthrough');
|
||||
expect(config.target.host).toEqual('localhost');
|
||||
expect(config.target.port).toEqual(443);
|
||||
|
@ -575,4 +575,4 @@ process.on('exit', () => {
|
||||
testProxy.stop().then(() => console.log('[TEST] Proxy server stopped'));
|
||||
});
|
||||
|
||||
tap.start();
|
||||
export default tap.start();
|
@ -279,14 +279,21 @@ tap.test('should support optional source IP preservation in chained proxies', as
|
||||
if (index4 !== -1) allProxies.splice(index4, 1);
|
||||
});
|
||||
|
||||
// Test round-robin behavior for multiple target IPs in a domain config.
|
||||
tap.test('should use round robin for multiple target IPs in domain config', async () => {
|
||||
// Test round-robin behavior for multiple target hosts in a domain config.
|
||||
tap.test('should use round robin for multiple target hosts in domain config', async () => {
|
||||
// Create a domain config with multiple hosts in the target
|
||||
const domainConfig = {
|
||||
domains: ['rr.test'],
|
||||
allowedIPs: ['127.0.0.1'],
|
||||
targetIPs: ['hostA', 'hostB']
|
||||
} as any;
|
||||
|
||||
forwarding: {
|
||||
type: 'http-only',
|
||||
target: {
|
||||
host: ['hostA', 'hostB'], // Array of hosts for round-robin
|
||||
port: 80
|
||||
},
|
||||
http: { enabled: true }
|
||||
}
|
||||
};
|
||||
|
||||
const proxyInstance = new SmartProxy({
|
||||
fromPort: 0,
|
||||
toPort: 0,
|
||||
@ -296,11 +303,14 @@ tap.test('should use round robin for multiple target IPs in domain config', asyn
|
||||
defaultAllowedIPs: [],
|
||||
globalPortRanges: []
|
||||
});
|
||||
|
||||
|
||||
// Don't track this proxy as it doesn't actually start or listen
|
||||
|
||||
const firstTarget = proxyInstance.domainConfigManager.getTargetIP(domainConfig);
|
||||
const secondTarget = proxyInstance.domainConfigManager.getTargetIP(domainConfig);
|
||||
|
||||
// Get the first target host from the forwarding config
|
||||
const firstTarget = proxyInstance.domainConfigManager.getTargetHost(domainConfig);
|
||||
// Get the second target host - should be different due to round-robin
|
||||
const secondTarget = proxyInstance.domainConfigManager.getTargetHost(domainConfig);
|
||||
|
||||
expect(firstTarget).toEqual('hostA');
|
||||
expect(secondTarget).toEqual('hostB');
|
||||
});
|
||||
|
Reference in New Issue
Block a user