fixed httpclient
This commit is contained in:
parent
a282dac6cd
commit
557c157228
10 changed files with 603 additions and 427 deletions
|
|
@ -9,8 +9,8 @@ import type {
|
|||
import {
|
||||
HttpError,
|
||||
TimeoutError,
|
||||
HttpClientConfigSchema,
|
||||
RequestConfigSchema,
|
||||
validateHttpClientConfig,
|
||||
validateRequestConfig,
|
||||
} from './types.js';
|
||||
import { RateLimiter } from './rate-limiter.js';
|
||||
import { ProxyManager } from './proxy-manager.js';
|
||||
|
|
@ -19,10 +19,9 @@ export class HttpClient {
|
|||
private readonly config: HttpClientConfig;
|
||||
private readonly rateLimiter?: RateLimiter;
|
||||
private readonly logger?: Logger;
|
||||
|
||||
constructor(config: Partial<HttpClientConfig> = {}, logger?: Logger) {
|
||||
// Validate and set default configuration
|
||||
this.config = HttpClientConfigSchema.parse(config);
|
||||
this.config = validateHttpClientConfig(config);
|
||||
this.logger = logger;
|
||||
|
||||
// Initialize rate limiter if configured
|
||||
|
|
@ -38,10 +37,9 @@ export class HttpClient {
|
|||
|
||||
/**
|
||||
* Make an HTTP request
|
||||
*/
|
||||
async request<T = any>(config: RequestConfig): Promise<HttpResponse<T>> {
|
||||
*/ async request<T = any>(config: RequestConfig): Promise<HttpResponse<T>> {
|
||||
// Validate request configuration
|
||||
const validatedConfig = RequestConfigSchema.parse(config);
|
||||
const validatedConfig = validateRequestConfig(config);
|
||||
|
||||
// Merge with default configuration
|
||||
const finalConfig = this.mergeConfig(validatedConfig);
|
||||
|
|
@ -54,10 +52,8 @@ export class HttpClient {
|
|||
this.logger?.debug('Making HTTP request', {
|
||||
method: finalConfig.method,
|
||||
url: finalConfig.url
|
||||
});
|
||||
|
||||
let lastError: Error | undefined;
|
||||
const maxRetries = finalConfig.retries ?? this.config.retries;
|
||||
}); let lastError: Error | undefined;
|
||||
const maxRetries = finalConfig.retries ?? 3;
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
|
|
@ -99,7 +95,7 @@ export class HttpClient {
|
|||
|
||||
// Wait before retrying (except on last attempt)
|
||||
if (attempt < maxRetries) {
|
||||
const delay = finalConfig.retryDelay ?? this.config.retryDelay;
|
||||
const delay = finalConfig.retryDelay ?? 1000;
|
||||
await this.sleep(delay * Math.pow(2, attempt)); // Exponential backoff
|
||||
}
|
||||
}
|
||||
|
|
@ -133,10 +129,9 @@ export class HttpClient {
|
|||
|
||||
/**
|
||||
* Execute the actual HTTP request
|
||||
*/
|
||||
private async executeRequest<T>(config: RequestConfig): Promise<HttpResponse<T>> {
|
||||
*/ private async executeRequest<T>(config: RequestConfig): Promise<HttpResponse<T>> {
|
||||
const url = this.buildUrl(config.url);
|
||||
const timeout = config.timeout ?? this.config.timeout;
|
||||
const timeout = config.timeout ?? 30000;
|
||||
|
||||
// Create abort controller for timeout
|
||||
const abortController = new AbortController();
|
||||
|
|
@ -144,13 +139,21 @@ export class HttpClient {
|
|||
abortController.abort();
|
||||
}, timeout);
|
||||
|
||||
try {
|
||||
// Prepare request options
|
||||
try { // Prepare request options
|
||||
const requestOptions: RequestInit = {
|
||||
method: config.method,
|
||||
headers: config.headers,
|
||||
headers: config.headers ? {...config.headers} : {},
|
||||
signal: abortController.signal,
|
||||
};
|
||||
|
||||
// Add basic auth if provided
|
||||
if (config.auth) {
|
||||
const authValue = `Basic ${Buffer.from(`${config.auth.username}:${config.auth.password}`).toString('base64')}`;
|
||||
requestOptions.headers = {
|
||||
...requestOptions.headers,
|
||||
'Authorization': authValue
|
||||
};
|
||||
}
|
||||
|
||||
// Add body for non-GET requests
|
||||
if (config.body && config.method !== 'GET' && config.method !== 'HEAD') {
|
||||
|
|
@ -229,31 +232,44 @@ export class HttpClient {
|
|||
|
||||
/**
|
||||
* Merge request config with default config
|
||||
*/
|
||||
private mergeConfig(config: RequestConfig): RequestConfig {
|
||||
*/ private mergeConfig(config: RequestConfig): RequestConfig {
|
||||
return {
|
||||
...config,
|
||||
headers: {
|
||||
...this.config.defaultHeaders,
|
||||
...config.headers,
|
||||
},
|
||||
timeout: config.timeout ?? this.config.timeout,
|
||||
retries: config.retries ?? this.config.retries,
|
||||
retryDelay: config.retryDelay ?? this.config.retryDelay,
|
||||
validateStatus: config.validateStatus ?? this.config.validateStatus,
|
||||
timeout: config.timeout ?? this.config.timeout ?? 30000,
|
||||
retries: config.retries ?? this.config.retries ?? 3,
|
||||
retryDelay: config.retryDelay ?? this.config.retryDelay ?? 1000,
|
||||
validateStatus: config.validateStatus ?? this.config.validateStatus ?? this.defaultValidateStatus,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build full URL from base URL and request URL
|
||||
*/
|
||||
private buildUrl(url: string): string {
|
||||
// If it's already a full URL, return it as is
|
||||
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||
return url;
|
||||
}
|
||||
|
||||
// If we have a baseURL, combine them
|
||||
if (this.config.baseURL) {
|
||||
return new URL(url, this.config.baseURL).toString();
|
||||
try {
|
||||
// Try to use URL constructor
|
||||
return new URL(url, this.config.baseURL).toString();
|
||||
} catch (e) {
|
||||
// Fall back to string concatenation if URL constructor fails
|
||||
const base = this.config.baseURL.endsWith('/') ? this.config.baseURL : `${this.config.baseURL}/`;
|
||||
const path = url.startsWith('/') ? url.substring(1) : url;
|
||||
return `${base}${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
// No baseURL, so prepend https:// if it's a domain-like string
|
||||
if (!url.includes('://') && url.includes('.')) {
|
||||
return `https://${url}`;
|
||||
}
|
||||
|
||||
return url;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue