Skip to content

Commit 0437b6e

Browse files
Fixed proxy issue
1 parent 072fc59 commit 0437b6e

3 files changed

Lines changed: 160 additions & 173 deletions

File tree

packages/contentstack-utilities/src/contentstack-management-sdk.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { client, ContentstackClient, ContentstackConfig } from '@contentstack/ma
22
import authHandler from './auth-handler';
33
import { Agent } from 'node:https';
44
import configHandler, { default as configStore } from './config-handler';
5-
import { getProxyConfigForHost, resolveRequestHost, clearProxyEnv } from './proxy-helper';
5+
import {
6+
getProxyConfigForHost,
7+
resolveRequestHost,
8+
clearProxyEnv,
9+
shouldBypassProxy,
10+
} from './proxy-helper';
611
import dotenv from 'dotenv';
712

813
dotenv.config();
@@ -22,8 +27,8 @@ class ManagementSDKInitiator {
2227
// NO_PROXY has priority over HTTP_PROXY/HTTPS_PROXY and config-set proxy
2328
const proxyConfig = getProxyConfigForHost(host);
2429

25-
// When bypassing, clear proxy env immediately so SDK never see it (they may read at init or first request).
26-
if (!proxyConfig) {
30+
// When NO_PROXY matches, strip proxy env so the SDK/axios cannot pick up HTTP_PROXY for this process.
31+
if (host && shouldBypassProxy(host)) {
2732
clearProxyEnv();
2833
}
2934

@@ -118,6 +123,8 @@ class ManagementSDKInitiator {
118123

119124
if (proxyConfig) {
120125
option.proxy = proxyConfig;
126+
} else if (host && shouldBypassProxy(host)) {
127+
option.proxy = false;
121128
}
122129
if (config.endpoint) {
123130
option.endpoint = config.endpoint;

packages/contentstack-utilities/src/http-client/client.ts

Lines changed: 70 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ import { IHttpClient } from './client-interface';
33
import { HttpResponse } from './http-response';
44
import configStore from '../config-handler';
55
import authHandler from '../auth-handler';
6-
import { hasProxy, getProxyUrl, getProxyConfig, getProxyConfigForHost } from '../proxy-helper';
6+
import {
7+
hasProxy,
8+
getProxyUrl,
9+
getProxyConfigForHost,
10+
resolveRequestHost,
11+
shouldBypassProxy,
12+
} from '../proxy-helper';
13+
14+
type AxiosRequestConfigWithRetry = AxiosRequestConfig & { __httpClientRetryCount?: number };
715

816
/**
917
* Derive request host from baseURL or url for NO_PROXY checks.
@@ -52,6 +60,62 @@ export class HttpClient implements IHttpClient {
5260

5361
// Sets payload format as json by default
5462
this.asJson();
63+
this.attachResponseInterceptor();
64+
}
65+
66+
/** Single interceptor per instance — avoids stacking handlers on every request (major perf win). */
67+
private attachResponseInterceptor(): void {
68+
this.axiosInstance.interceptors.response.use(null, async (error) => {
69+
const cfg = error.config as AxiosRequestConfigWithRetry | undefined;
70+
if (!cfg) {
71+
return Promise.reject(error);
72+
}
73+
74+
const { message, response, code } = error;
75+
const proxyFromCfg = cfg.proxy;
76+
const isProxyConfigured = !!proxyFromCfg || hasProxy();
77+
78+
const proxyErrorCodes = ['ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND', 'ERR_BAD_RESPONSE'];
79+
if (isProxyConfigured && (proxyErrorCodes.includes(code) || message?.includes('ERR_BAD_RESPONSE'))) {
80+
const p = proxyFromCfg as { protocol?: string; host?: string; port?: number } | undefined;
81+
const proxyUrl =
82+
p && typeof p === 'object' && p.host
83+
? `${p.protocol ?? 'http'}://${p.host}:${p.port ?? 80}`
84+
: getProxyUrl();
85+
86+
return Promise.reject(
87+
new Error(
88+
`Proxy error: Unable to connect to proxy server at ${proxyUrl}. Please verify your proxy configuration.`,
89+
),
90+
);
91+
}
92+
93+
if (response?.data?.error_message?.includes('access token is invalid or expired')) {
94+
const token = await this.refreshToken();
95+
this.headers({ ...this.request.headers, authorization: token.authorization });
96+
return this.axiosInstance({
97+
...cfg,
98+
headers: { ...cfg.headers, authorization: token.authorization },
99+
});
100+
}
101+
102+
if (
103+
!(
104+
message?.includes('timeout') ||
105+
message?.includes('Network Error') ||
106+
message?.includes('getaddrinfo ENOTFOUND')
107+
)
108+
) {
109+
return Promise.reject(error);
110+
}
111+
112+
const retries = cfg.__httpClientRetryCount ?? 0;
113+
if (retries < 1) {
114+
cfg.__httpClientRetryCount = retries + 1;
115+
return this.axiosInstance(cfg);
116+
}
117+
return Promise.reject(error);
118+
});
55119
}
56120

57121
/**
@@ -373,52 +437,6 @@ export class HttpClient implements IHttpClient {
373437
* @returns {Request}
374438
*/
375439
async createAndSendRequest(method: HttpMethod, url: string): Promise<AxiosResponse> {
376-
let counter = 0;
377-
this.axiosInstance.interceptors.response.use(null, async (error) => {
378-
const { message, response, code } = error;
379-
380-
// Don't retry proxy connection errors - fail fast
381-
const proxyErrorCodes = ['ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND', 'ERR_BAD_RESPONSE'];
382-
const isProxyConfigured = this.request.proxy || hasProxy();
383-
384-
if (isProxyConfigured && (proxyErrorCodes.includes(code) || message?.includes('ERR_BAD_RESPONSE'))) {
385-
const proxyUrl = this.request.proxy && typeof this.request.proxy === 'object'
386-
? `${this.request.proxy.protocol}://${this.request.proxy.host}:${this.request.proxy.port}`
387-
: getProxyUrl();
388-
389-
return Promise.reject(new Error(`Proxy error: Unable to connect to proxy server at ${proxyUrl}. Please verify your proxy configuration.`));
390-
}
391-
392-
if (response?.data?.error_message?.includes('access token is invalid or expired')) {
393-
const token = await this.refreshToken();
394-
this.headers({ ...this.request.headers, authorization: token.authorization });
395-
return await this.axiosInstance({
396-
url,
397-
method,
398-
withCredentials: true,
399-
...this.request,
400-
data: this.prepareRequestPayload(),
401-
});
402-
}
403-
404-
if (
405-
!(message.includes('timeout') || message.includes('Network Error') || message.includes('getaddrinfo ENOTFOUND'))
406-
) {
407-
return Promise.reject(error);
408-
}
409-
if (counter < 1) {
410-
counter++;
411-
return await this.axiosInstance({
412-
url,
413-
method,
414-
withCredentials: true,
415-
...this.request,
416-
data: this.prepareRequestPayload(),
417-
});
418-
}
419-
return Promise.reject(error);
420-
});
421-
422440
if (!this.disableEarlyAccessHeaders) {
423441
// Add early access header by default
424442
const earlyAccessHeaders = configStore.get(`earlyAccessHeaders`);
@@ -427,12 +445,14 @@ export class HttpClient implements IHttpClient {
427445
}
428446
}
429447

430-
// Configure proxy if available. NO_PROXY has priority: hosts in NO_PROXY never use proxy.
448+
// Configure proxy if available. NO_PROXY has priority; fall back to region CMA for host resolution.
431449
if (!this.request.proxy) {
432-
const host = getRequestHost(this.request.baseURL, url);
433-
const proxyConfig = host ? getProxyConfigForHost(host) : getProxyConfig();
450+
const host = getRequestHost(this.request.baseURL, url) || resolveRequestHost({});
451+
const proxyConfig = getProxyConfigForHost(host);
434452
if (proxyConfig) {
435453
this.request.proxy = proxyConfig;
454+
} else if (host && shouldBypassProxy(host)) {
455+
this.request.proxy = false;
436456
}
437457
}
438458

0 commit comments

Comments
 (0)