feat: add upstream proxy configuration for outbound requests#1458
feat: add upstream proxy configuration for outbound requests#1458Andreybest wants to merge 10 commits intofinos:mainfrom
Conversation
✅ Deploy Preview for endearing-brigadeiros-63f9d0 canceled.
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1458 +/- ##
==========================================
- Coverage 90.21% 90.06% -0.16%
==========================================
Files 69 69
Lines 5511 5607 +96
Branches 944 976 +32
==========================================
+ Hits 4972 5050 +78
- Misses 521 539 +18
Partials 18 18 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
jescalada
left a comment
There was a problem hiding this comment.
@Andreybest Thanks for the contribution! 🚀
I left some comments for code refactoring. It'd be very helpful if you could upload images or a video demonstrating that this feature works as intended.
@coopernetes Wondering if this implements your original requirements (#759) and works on your end 🤔
dcoric
left a comment
There was a problem hiding this comment.
Thanks for tackling #759. I've left a few comments focused on correctness and robustness, especially around NO_PROXY pattern handling which is important to get right for the target use case. The main items are the missing */leading-dot NO_PROXY support and proxy URL validation.
| return undefined; | ||
| } | ||
|
|
||
| return new HttpsProxyAgent(proxyUrl); |
There was a problem hiding this comment.
The proxyUrl (from config or env vars) is passed directly to new HttpsProxyAgent(proxyUrl) without validation. If the URL is malformed (typo, missing protocol, empty string after trimming), this throws a cryptic Invalid URL error that surfaces through express-http-proxy's error handler with no indication that the upstream proxy config is the problem.
Consider wrapping this in a try-catch or validating first:
try {
new URL(proxyUrl); // validate before constructing agent
} catch {
throw new Error(`Invalid upstream proxy URL: check your upstreamProxy.url config or HTTPS_PROXY env var`);
}
return new HttpsProxyAgent(proxyUrl);There was a problem hiding this comment.
This comment might still need fixing! 🙂
There was a problem hiding this comment.
Don't know how I've missed it, added, thanks @jescalada and @dcoric !
|
Thanks for the reviews @jescalada and @dcoric! |
|
@jescalada Screen.Recording.2026-03-25.at.21.35.35_no_audio.mov |
Co-authored-by: Thomas Cooper <[email protected]> Signed-off-by: Andrew <[email protected]>
jescalada
left a comment
There was a problem hiding this comment.
LGTM after fixing Denis' comment about validating/error handling the HttpsProxyAgent creation.
@coopernetes Does everything work as you expected? 🤔
|
|
||
| const getOrCreateProxyAgent = (proxyUrl: string): HttpsProxyAgent<string> => { | ||
| if (!_cachedProxyAgent || _cachedProxyAgent.proxyUrl !== proxyUrl) { | ||
| _cachedProxyAgent = { proxyUrl, agent: new HttpsProxyAgent(proxyUrl) }; |
There was a problem hiding this comment.
Just repeating Denis earlier comment - this should be properly validated and/or throw a descriptive error
| return undefined; | ||
| } | ||
|
|
||
| return new HttpsProxyAgent(proxyUrl); |
There was a problem hiding this comment.
This comment might still need fixing! 🙂
|
|
||
| const proxyUrl = url || getEnvProxyUrl(); | ||
|
|
||
| if (enabled === false || !proxyUrl) { |
There was a problem hiding this comment.
I think the env-var-only path is still broken. proxy.config.json is imported as the default config, and it now contains upstreamProxy.enabled: false. When a user omits upstreamProxy from their config and relies only on HTTPS_PROXY/HTTP_PROXY, the merged config still has enabled === false, so buildUpstreamProxyAgent() returns before using the env var. This contradicts the docs saying env vars enable proxying when upstreamProxy is not configured. The current test mocks getUpstreamProxyConfig() as {}, so it does not cover the real default-config path. Can we either remove upstreamProxy from the shipped default config, distinguish “unset” from explicitly disabled, or change the documented behavior and add a test for the default merge?
There was a problem hiding this comment.
I don't know how I've missed it, but now it's fixed. Unset enable now is treated as false. Thanks!
| const getOrCreateProxyAgent = (proxyUrl: string): HttpsProxyAgent<string> => { | ||
| if (!_cachedProxyAgent || _cachedProxyAgent.proxyUrl !== proxyUrl) { | ||
| try { | ||
| new URL(proxyUrl); |
There was a problem hiding this comment.
This validation still accepts values that are syntactically parseable URLs but not valid HTTP(S) proxy URLs. For example, new URL("socks5://proxy.example.com:1080"), new URL("ftp://proxy.example.com:21"), and even new URL("localhost:8081") all succeed. HttpsProxyAgent also constructs for those values, then treats non-https: schemes as plain TCP proxy connections, so the user gets a later connectivity failure instead of a descriptive config error. Can we parse once, require protocol to be http: or https:, require a non-empty hostname, and add tests for unsupported schemes and missing protocol?
There was a problem hiding this comment.
Added checks to accept only https and http, and for hostname to exist.
coopernetes
left a comment
There was a problem hiding this comment.
LGTM. The only rub is the lack of direct authentication support in the proxy agent. Many client and server libraries do not implement auth directly so it's fine to leave it out for now. Worth making a follow up issue to add HTTP Basic and NTLM (Windows) auth support which are the two most common methods (at least that I'm aware of).
I'd suggest making that HTTP Basic, NTLM and Negotiate (the negotiate protocol is used to detect support for NTLM or Kerberos auth and then kick off which ever is supported). |
|
@dcoric Thank you again for the review! Can you please check again? :) |
Resolves #759.
Adds upstream proxy support. Uses values from both configuration file in
upstreamProxyfields and environment variables (HTTPS_PROXY/HTTP_PROXY,NO_PROXY)