[feature] Add requestDefault parameters

This commit is contained in:
Pavel Ostrovskij
2025-04-03 17:36:14 +03:00
parent d3cc432d0e
commit 47399233bb
3 changed files with 303 additions and 3 deletions

View File

@ -320,8 +320,8 @@
"User-Agent": "Node.js/6.13",
"Connection": "Keep-Alive"
},
"decompress": true,
"rejectUnauthorized": true
"gzip": true,
"rejectUnauthorized": true
},
"autoAssembly": {
"enable": false,

View File

@ -296,6 +296,7 @@ function isAllowDirectRequest(ctx, uri, isInJwtToken) {
}
function addExternalRequestOptions(ctx, uri, isInJwtToken, options) {
let res = false;
const tenTenantRequestDefaults = ctx.getCfg('services.CoAuthoring.requestDefaults', cfgRequestDefaults);
const tenExternalRequestAction = ctx.getCfg('externalRequest.action', cfgExternalRequestAction);
const tenRequestFilteringAgent = ctx.getCfg('services.CoAuthoring.request-filtering-agent', cfgRequesFilteringAgent);
if (isAllowDirectRequest(ctx, uri, isInJwtToken)) {
@ -316,6 +317,10 @@ function addExternalRequestOptions(ctx, uri, isInJwtToken, options) {
agentOptions.port = parsedProxyUrl.port;
agentOptions.protocol = parsedProxyUrl.protocol;
}
if (tenTenantRequestDefaults.forever !== undefined) {
agentOptions.keepAlive = !!tenTenantRequestDefaults.forever;
}
if (uri.startsWith('https:')) {
options.httpsAgent = new RequestFilteringHttpsAgent(agentOptions);
@ -348,6 +353,11 @@ async function downloadUrlPromise(ctx, uri, optTimeout, optLimit, opt_Authorizat
uri = URI.serialize(URI.parse(uri));
const connectionAndInactivity = optTimeout?.connectionAndInactivity ? ms(optTimeout.connectionAndInactivity) : undefined;
const options = config.util.cloneDeep(tenTenantRequestDefaults);
if (options.gzip !== undefined && !options.gzip) {
options.headers = options.headers || {};
options.headers['Accept-Encoding'] = 'identity';
delete options.gzip;
}
if (!exports.addExternalRequestOptions(ctx, uri, opt_filterPrivate, options)) {
throw new Error('Block external request. See externalRequest config options');
}
@ -355,6 +365,9 @@ async function downloadUrlPromise(ctx, uri, optTimeout, optLimit, opt_Authorizat
const protocol = new URL(uri).protocol;
if (!options.httpsAgent && !options.httpAgent) {
const agentOptions = { ...https.globalAgent.options, rejectUnauthorized: tenTenantRequestDefaults.rejectUnauthorized === false? false : true};
if (tenTenantRequestDefaults.forever !== undefined) {
agentOptions.keepAlive = !!tenTenantRequestDefaults.forever;
}
if (protocol === 'https:') {
options.httpsAgent = new https.Agent(agentOptions);
} else if (protocol === 'http:') {
@ -536,6 +549,13 @@ async function postRequestPromise(ctx, uri, postData, postDataStream, postDataSi
timeout: connectionAndInactivity,
validateStatus: (status) => status === 200 || status === 204
});
if (options.gzip !== undefined && !options.gzip) {
options.headers = options.headers || {};
options.headers['Accept-Encoding'] = 'identity';
delete options.gzip;
}
if (!addExternalRequestOptions(ctx, uri, opt_isInJwtToken, options)) {
throw new Error('Block external request. See externalRequest config options');
}
@ -545,6 +565,11 @@ async function postRequestPromise(ctx, uri, postData, postDataStream, postDataSi
...https.globalAgent.options,
rejectUnauthorized: tenTenantRequestDefaults.rejectUnauthorized === false ? false : true
};
if (tenTenantRequestDefaults.forever !== undefined) {
agentOptions.keepAlive = !!tenTenantRequestDefaults.forever;
}
if (protocol === 'https:') {
options.httpsAgent = new https.Agent(agentOptions);
} else if (protocol === 'http:') {

View File

@ -152,6 +152,35 @@ describe('HTTP Request Integration Tests', () => {
const buffer = Buffer.alloc(2097152);
res.send(buffer);
});
app.get('/api/headers', (req, res) => {
// Ensure you're only sending headers, which won't contain circular references
res.json({ headers: req.headers });
});
// Endpoint that returns connection header info
app.get('/api/connection', (req, res) => {
res.json({
connection: req.headers.connection,
keepAlive: req.headers.connection?.toLowerCase() === 'keep-alive'
});
});
// Endpoint that returns only the accept-encoding header
app.get('/api/accept-encoding', (req, res) => {
res.json({
acceptEncoding: req.headers['accept-encoding'] || null
});
});
// Endpoint that returns only the connection header
app.get('/api/connection-header', (req, res) => {
const connectionHeader = req.headers['connection'] || '';
res.json({
connection: connectionHeader,
keepAlive: connectionHeader.toLowerCase() === 'keep-alive'
});
});
// Start server
server = http.createServer(app);
@ -336,6 +365,163 @@ describe('HTTP Request Integration Tests', () => {
null
)).rejects.toThrow('Error response: content-length:2097152');
});
test('enables compression when gzip is true', async () => {
// Setup a simple server that captures headers
let capturedHeaders = {};
const app = express();
app.get('/test', (req, res) => {
capturedHeaders = {
acceptEncoding: req.headers['accept-encoding']
};
res.json({ success: true });
});
const testServer = http.createServer(app);
const testPort = PORT + 1000;
await new Promise(resolve => testServer.listen(testPort, resolve));
try {
const mockCtx = createMockContext({
'services.CoAuthoring.requestDefaults': {
headers: { "User-Agent": "Node.js/6.13" },
gzip: true,
rejectUnauthorized: false
}
});
await utils.downloadUrlPromise(
mockCtx,
`http://localhost:${testPort}/test`,
{ wholeCycle: '2s' },
1024 * 1024,
null,
false,
null,
null
);
// When gzip is true, 'accept-encoding' should include 'gzip'
expect(capturedHeaders.acceptEncoding).toBeDefined();
expect(capturedHeaders.acceptEncoding).toMatch(/gzip/i);
} finally {
await new Promise(resolve => testServer.close(resolve));
}
});
test('disables compression when gzip is false', async () => {
// Setup a simple server that captures headers
let capturedHeaders = {};
const app = express();
app.get('/test', (req, res) => {
capturedHeaders = {
acceptEncoding: req.headers['accept-encoding']
};
res.json({ success: true });
});
const testServer = http.createServer(app);
const testPort = PORT + 1001;
await new Promise(resolve => testServer.listen(testPort, resolve));
try {
const mockCtx = createMockContext({
'services.CoAuthoring.requestDefaults': {
headers: { "User-Agent": "Node.js/6.13" },
gzip: false,
rejectUnauthorized: false
}
});
await utils.downloadUrlPromise(
mockCtx,
`http://localhost:${testPort}/test`,
{ wholeCycle: '2s' },
1024 * 1024,
null,
false,
null,
null
);
expect(capturedHeaders.acceptEncoding === 'identity' || capturedHeaders.acceptEncoding === undefined).toBe(true);
} finally {
await new Promise(resolve => testServer.close(resolve));
}
});
test('enables keep-alive when forever is true', async () => {
// Setup a simple server that captures headers
let capturedHeaders = {};
const app = express();
app.get('/test', (req, res) => {
capturedHeaders = {
connection: req.headers['connection']
};
res.json({ success: true });
});
const testServer = http.createServer(app);
const testPort = PORT + 1002;
await new Promise(resolve => testServer.listen(testPort, resolve));
try {
const mockCtx = createMockContext({
'services.CoAuthoring.requestDefaults': {
headers: { "User-Agent": "Node.js/6.13" },
forever: true,
rejectUnauthorized: false
}
});
await utils.downloadUrlPromise(
mockCtx,
`http://localhost:${testPort}/test`,
{ wholeCycle: '2s' },
1024 * 1024,
null,
false,
null,
null
);
// When forever is true, connection should be 'keep-alive'
expect(capturedHeaders.connection?.toLowerCase()).toMatch(/keep-alive/i);
} finally {
await new Promise(resolve => testServer.close(resolve));
}
});
test('disables keep-alive when forever is false', async () => {
const mockCtx = createMockContext({
'services.CoAuthoring.requestDefaults': {
headers: {
"User-Agent": "Node.js/6.13"
},
forever: false,
rejectUnauthorized: false
}
});
const result = await utils.downloadUrlPromise(
mockCtx,
`${BASE_URL}/api/connection-header`,
{ wholeCycle: '5s', connectionAndInactivity: '3s' },
1024 * 1024,
null,
false,
null,
null
);
expect(result).toBeDefined();
const responseData = JSON.parse(result.body.toString());
// When forever is false, connection should NOT be 'keep-alive'
// Note: Different HTTP clients might handle this differently,
// so we're checking that keepAlive is false
expect(responseData.keepAlive).toBe(false);
});
});
test('handles binary data correctly', async () => {
@ -768,5 +954,94 @@ describe('HTTP Request Integration Tests', () => {
expect(error).toBeDefined();
}
});
test('applies gzip setting to POST requests', async () => {
// Setup a simple server that captures headers
let capturedHeaders = {};
const app = express();
app.post('/test', express.json(), (req, res) => {
capturedHeaders = {
acceptEncoding: req.headers['accept-encoding']
};
res.json({ success: true });
});
const testServer = http.createServer(app);
const testPort = PORT + 1003;
await new Promise(resolve => testServer.listen(testPort, resolve));
try {
const mockCtx = createMockContext({
'services.CoAuthoring.requestDefaults': {
headers: { "User-Agent": "Node.js/6.13" },
gzip: false,
rejectUnauthorized: false
}
});
const postData = JSON.stringify({ test: 'data' });
await utils.postRequestPromise(
mockCtx,
`http://localhost:${testPort}/test`,
postData,
null,
postData.length,
{ wholeCycle: '2s' },
null,
false,
{ 'Content-Type': 'application/json' }
);
expect(capturedHeaders.acceptEncoding === 'identity' || capturedHeaders.acceptEncoding === undefined).toBe(true);
} finally {
await new Promise(resolve => testServer.close(resolve));
}
});
test('applies forever setting to POST requests', async () => {
// Setup a simple server that captures headers
let capturedHeaders = {};
const app = express();
app.post('/test', express.json(), (req, res) => {
capturedHeaders = {
connection: req.headers['connection']
};
res.json({ success: true });
});
const testServer = http.createServer(app);
const testPort = PORT + 1004;
await new Promise(resolve => testServer.listen(testPort, resolve));
try {
const mockCtx = createMockContext({
'services.CoAuthoring.requestDefaults': {
headers: { "User-Agent": "Node.js/6.13" },
forever: true,
rejectUnauthorized: false
}
});
const postData = JSON.stringify({ test: 'data' });
await utils.postRequestPromise(
mockCtx,
`http://localhost:${testPort}/test`,
postData,
null,
postData.length,
{ wholeCycle: '2s' },
null,
false,
{ 'Content-Type': 'application/json' }
);
// When forever is true, connection should be 'keep-alive'
expect(capturedHeaders.connection?.toLowerCase()).toMatch(/keep-alive/i);
} finally {
await new Promise(resolve => testServer.close(resolve));
}
});
});
});
});