-
-
Notifications
You must be signed in to change notification settings - Fork 34.3k
http: trim off brackets from IPv6 addresses with string operations #59420
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
http: trim off brackets from IPv6 addresses with string operations #59420
Conversation
|
Review requested:
|
Uzlopak
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The benchmark is actually wrong. You have to write a benchmark, which uses the effected node code. This is a microbenchmark where you compare the performance of two or three implementations. This is not useful, if you want to determine if there is any performance improvement between two node versions.
|
Is it even a hotpath? |
|
The benchmark should look like this: 'use strict';
const common = require('../common.js');
// Benchmark configuration
const bench = common.createBenchmark(main, {
hostname: [
'[::]',
'127.0.0.1',
'localhost',
'www.example.proxy',
],
n: [1e6]
}, {
flags: ['--expose-internals'],
});
function main({ hostname, n }) {
const { parseProxyConfigFromEnv } = require('internal/http');
const protocol = 'https:';
const env = {
https_proxy: `https://${hostname}`,
}
// Warmup
for (let i = 0; i < n; i++) {
parseProxyConfigFromEnv(env, protocol);
}
// // Benchmark
bench.start();
for (let i = 0; i < n; i++) {
parseProxyConfigFromEnv(env, protocol);
}
bench.end(n);
} |
| // // Benchmark | ||
| bench.start(); | ||
| for (let i = 0; i < n; i++) { | ||
| parseProxyConfigFromEnv(env, protocol); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Create a variable outside the loop to store the result of this function and add one assertion after the loop to ensure the returned value is valid
This is necessary to avoid v8 dead code elimination
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@H4ad is this what you suggested?
'use strict';
const common = require('../common.js');
const assert = require('assert');
// Benchmark configuration
const bench = common.createBenchmark(main, {
hostname: [
'[::]',
'127.0.0.1',
'localhost',
'www.example.proxy',
],
n: [1e6]
}, {
flags: ['--expose-internals'],
});
function main({ hostname, n }) {
const { parseProxyConfigFromEnv } = require('internal/http');
const protocol = 'https:';
const env = {
https_proxy: `https://${hostname}`,
};
// Variable to store results outside the loop
let lastResult;
// Warmup
for (let i = 0; i < n; i++) {
lastResult = parseProxyConfigFromEnv(env, protocol);
}
// Expected hostname after parsing (square brackets removed for IPv6)
const expectedHostname = hostname[0] === '[' ? hostname.slice(1, -1) : hostname;
// Assertion to ensure the function returns a valid result
assert(
lastResult && typeof lastResult === 'object',
'Invalid proxy config result after warmup'
);
assert(
'hostname' in lastResult,
'Proxy config result should have hostname property'
);
// Benchmark
bench.start();
for (let i = 0; i < n; i++) {
lastResult = parseProxyConfigFromEnv(env, protocol);
}
bench.end(n);
// Final validation
assert(
lastResult && typeof lastResult === 'object',
'Invalid proxy config result after benchmark'
);
assert.strictEqual(
lastResult.hostname,
expectedHostname,
`Proxy config hostname should be ${expectedHostname} (got ${lastResult.hostname})`
);
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@pckrishnadas88 That's right
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Benchmark updated and pushed.
|
This looks like a very decent PR. Good work. |
Thank you for the feedback. Just updated the benchmark as well as suggested by @H4ad |
|
The warm up phase is still excessive. Instead of doing n-iterations it is enough to do 1000 iterations. For the warm up.. |
PR updated with that change. Thank you for the valuable suggestions. |
It's a path only used by http/https clients that are using proxies, and likely only ever called once throughout the lifetime of a Node.js http/https clients as part of the agent initialisation (typically, once per process to initialize the global agent), so...not really. (This path is not used by the server, FWIW). I would suggest dropping the benchmark, that is a bit of an overkill for a code path like this. We don't do that for every single string operation we do in the initialisations that are usually only run once per application lifecycle. |
|
@joyeecheung |
lib/internal/http.js
Outdated
| this.href = proxyUrl; // Full URL of the proxy server. | ||
| this.host = host; // Full host including port, e.g. 'localhost:8080'. | ||
| this.hostname = hostname.replace(/^\[|\]$/g, ''); // Trim off the brackets from IPv6 addresses. | ||
| this.hostname = hostname[0] === '[' ? hostname.slice(1, -1) : hostname; // Trim off the brackets from IPv6 addresses. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking change, since the original removes all leading and trailing brackets?
original
'[[[::1]]]' => '::1'
new
// double brackets at the end
'[::1]]' => '[::1]'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope.
aras@aras-HP-ZBook-15-G3:~/workspace/eventsource$ node
Welcome to Node.js v22.18.0.
Type ".help" for more information.
> new URL('http://[[::1]]')
Uncaught TypeError: Invalid URL
at new URL (node:internal/url:825:25) {
code: 'ERR_INVALID_URL',
input: 'http://[[::1]]'
}
>
I will remove the benchmark and update the PR soon. |
Benchmark file removed as suggested. |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #59420 +/- ##
==========================================
- Coverage 89.89% 89.88% -0.02%
==========================================
Files 656 656
Lines 193141 193143 +2
Branches 37886 37884 -2
==========================================
- Hits 173623 173599 -24
- Misses 12051 12056 +5
- Partials 7467 7488 +21
🚀 New features to boost your workflow:
|
Uzlopak
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know my vote is irrelevant, but atleast it removes my blocker ;)
Thank you. Learned a lot from your review. |
|
@lemire The test-macOS (pull_request) job has failed several times (around 35 min mark). The CI failure is from test-debugger-run-after-quit-restart.js on macOS. It looks unrelated to my changes — could this be a flaky test? |
tniessen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @pckrishnadas88. I am +0 on this sort of change.
FWIW, I generally do not recommend alleging concrete measurements of performance gains of such micro-optimizations in commit messages, for a number of reasons. Benchmarking such tiny changes, especially when not on a hot path, across all supported platforms and CPU architectures is tricky, and even if done properly, it may be difficult to claim any particular one numeric factor as the ultimate performance improvement.
The benchmark file is removed already as per previous review. I recently started on contributing to Node.js this is my second PR so learning things now. Mistakes are not intentional and happy to correct as per the reviews. Thank you for the detailed explanations. |
|
I agree with @tniessen - it's better to drop the performance statement in the commit message, because in the case of the code being changed here, that's very likely to be untrue. This code is likely only ever executed once per process, and the optimizing compiler is not likely to touch it. Performance numbers from repeatedly calling it in a loop where the optimizing compiler would kick in are not applicable to the actual use case of this code - it's mostly likely only interpreted. |
|
Can you squash the commit and amend the commit message? I think something like this would work: |
This is simpler than using regular expressions.
6c5a3f6 to
e0b8723
Compare
This change has been done. Please let me know if anything else is required. |
mcollina
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
|
Landed in 6c215fb |
|
Thanks everyone for the reviews and guidance! I appreciate the time and feedback from all of you in helping this land. 🙏 |
|
This doesn't land cleanly on v22.x-staging so a manual backport will be necessary if this is to be released there. It probably needs to be backported together with the proxy stuff. |
Replace regex-based hostname normalization with direct string operations, selecting the most performant of three tested approaches:
Performance Hierarchy (http/proxy-config-ipv6-trim.js):
Before (regex):
After (optimized):
Implementation Details:
startsWith('[') && endsWith(']')checks (stringOp)Behavior remains identical while being significantly faster.
Verification:
Benefits:
Note: This is unrelated to #59375 (approved, awaiting merge).