mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 18:25:13 +08:00
When `options.path` passed to `http.request()` contains an absolute URL, `http.request` has been sending it directly as the request target in the HTTP 1.1 message. If the receiving server is a proxy, the proxy server typically forwards the request to the destination specified in the request target and ignores the Host header. This means eventually the request can be forwarded to a destination that is not consistent with `options.host`, depending on how the receiving server behaves. Mimatched Host header and request target also violates RFC 9112 Section 3.2, which we have been entirely leaving to the users to verify. This patch documents this behavior and warns that the user needs to ensure the `path`, `option` and `headers` conform to the RFC. If the receiving server is known to be a proxy server because the request is routed by Node.js' built-in HTTP proxy support, we now do a best-effort check to verify that the authority in `options.path` (if absolute), Host headers and `options.host` agree at request construction time. Node.js will give up on the require target rewriting and throw an error when they don't match at request construction. Signed-off-by: Joyee Cheung <joyeec9h3@gmail.com> PR-URL: https://github.com/nodejs/node/pull/64108 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: Ulises Gascón <ulisesgascongonzalez@gmail.com>
71 lines
2.1 KiB
JavaScript
71 lines
2.1 KiB
JavaScript
// This tests that proxied HTTP requests succeed end-to-end when the
|
|
// absolute-form request-target matches the request authority derived from options.
|
|
|
|
import * as common from '../common/index.mjs';
|
|
import assert from 'node:assert';
|
|
import http from 'node:http';
|
|
import { once } from 'events';
|
|
import { createProxyServer } from '../common/proxy-server.js';
|
|
|
|
const server = http.createServer(common.mustCall((req, res) => {
|
|
res.end('Hello world');
|
|
}, 6));
|
|
server.on('error', common.mustNotCall());
|
|
server.listen(0);
|
|
await once(server, 'listening');
|
|
|
|
const { proxy, logs } = createProxyServer();
|
|
proxy.listen(0);
|
|
await once(proxy, 'listening');
|
|
|
|
const port = server.address().port;
|
|
const serverHost = `localhost:${port}`;
|
|
const requestUrl = `http://${serverHost}/test`;
|
|
|
|
const agent = new http.Agent({
|
|
proxyEnv: {
|
|
HTTP_PROXY: `http://localhost:${proxy.address().port}`,
|
|
},
|
|
});
|
|
|
|
async function roundTrip(options) {
|
|
const req = http.request({ agent, ...options });
|
|
req.end();
|
|
const [res] = await once(req, 'response');
|
|
res.setEncoding('utf8');
|
|
let body = '';
|
|
for await (const chunk of res) body += chunk;
|
|
assert.strictEqual(body, 'Hello world');
|
|
}
|
|
|
|
const baseAbsolute = { host: 'localhost', port, path: requestUrl };
|
|
|
|
const options = [
|
|
// No user-supplied headers.
|
|
baseAbsolute,
|
|
// Object form with an explicit Host that matches.
|
|
{ ...baseAbsolute, headers: { Host: serverHost } },
|
|
// Flat array form.
|
|
{ ...baseAbsolute, headers: ['Host', serverHost] },
|
|
// Array-of-pairs form.
|
|
{ ...baseAbsolute, headers: [['Host', serverHost]] },
|
|
// Contains defaultPort that matches options.port.
|
|
{ host: 'localhost', port, defaultPort: port, path: '/test' },
|
|
// Stringifiable non-string path object.
|
|
{ host: 'localhost', port, path: { toString() { return '/test'; } } },
|
|
];
|
|
|
|
for (const opts of options) {
|
|
await roundTrip(opts);
|
|
// Check what the proxy server received.
|
|
const log = logs.pop();
|
|
assert.strictEqual(logs.length, 0);
|
|
assert.strictEqual(log.method, 'GET');
|
|
assert.strictEqual(log.url, requestUrl);
|
|
assert.strictEqual(log.headers.host, serverHost);
|
|
}
|
|
|
|
server.close();
|
|
proxy.close();
|
|
agent.destroy();
|