From fea2f99e996053dcbde18b9aff926dafcae35d72 Mon Sep 17 00:00:00 2001 From: Shigma Date: Mon, 12 Feb 2024 02:22:12 +0800 Subject: [PATCH] feat(http): basic support for proxy agent --- packages/http/package.json | 11 +++++-- packages/http/src/adapter/browser.ts | 4 +++ packages/http/src/adapter/index.d.ts | 1 + packages/http/src/adapter/node.ts | 1 + packages/http/src/index.ts | 46 ++++++++++++++++++++++------ 5 files changed, 51 insertions(+), 12 deletions(-) diff --git a/packages/http/package.json b/packages/http/package.json index d35238c..aff4f7f 100644 --- a/packages/http/package.json +++ b/packages/http/package.json @@ -3,7 +3,7 @@ "description": "Axios-style HTTP client service for Cordis", "version": "0.1.0", "type": "module", - "module": "lib/index.js", + "main": "lib/index.js", "types": "lib/index.d.ts", "exports": { ".": { @@ -42,12 +42,16 @@ "cordis", "http", "fetch", + "axios", + "undici", + "client", "request", "service", "plugin" ], "devDependencies": { - "cordis": "^3.9.2" + "cordis": "^3.9.2", + "undici": "^6.6.2" }, "peerDependencies": { "cordis": "^3.9.2" @@ -55,7 +59,8 @@ "dependencies": { "cosmokit": "^1.5.2", "file-type": "^16.5.4", - "unws": "^0.2.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.2", "ws": "^8.16.0" } } diff --git a/packages/http/src/adapter/browser.ts b/packages/http/src/adapter/browser.ts index 98f46cc..284e318 100644 --- a/packages/http/src/adapter/browser.ts +++ b/packages/http/src/adapter/browser.ts @@ -3,6 +3,10 @@ import { LookupAddress } from 'dns' import { HTTP } from '../index.js' +const ws = typeof WebSocket !== 'undefined' ? WebSocket : null + +export { ws as WebSocket } + const v4 = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/ const v6seg = '[a-fA-F\\d]{1,4}' diff --git a/packages/http/src/adapter/index.d.ts b/packages/http/src/adapter/index.d.ts index c4fb79a..0107dba 100644 --- a/packages/http/src/adapter/index.d.ts +++ b/packages/http/src/adapter/index.d.ts @@ -1,5 +1,6 @@ import { LookupAddress } from 'dns' import { HTTP } from '../index.ts' +export { WebSocket } export function loadFile(url: string): Promise export function lookup(address: string): Promise diff --git a/packages/http/src/adapter/node.ts b/packages/http/src/adapter/node.ts index 13b21f2..02680d8 100644 --- a/packages/http/src/adapter/node.ts +++ b/packages/http/src/adapter/node.ts @@ -4,6 +4,7 @@ import { fromBuffer } from 'file-type' import { HTTP } from '../index.js' import { readFile } from 'node:fs/promises' +export { WebSocket } from 'ws' export { lookup } from 'node:dns/promises' export async function loadFile(url: string): Promise { diff --git a/packages/http/src/index.ts b/packages/http/src/index.ts index f356cc3..10b05e5 100644 --- a/packages/http/src/index.ts +++ b/packages/http/src/index.ts @@ -1,9 +1,10 @@ import { Context } from 'cordis' import { base64ToArrayBuffer, Dict, trimSlash } from 'cosmokit' -import { WebSocket } from 'unws' import { ClientOptions } from 'ws' -import { loadFile, lookup } from './adapter/index.js' +import { loadFile, lookup, WebSocket } from './adapter/index.js' import { isLocalAddress } from './utils.js' +import type * as undici from 'undici' +import type * as http from 'http' declare module 'cordis' { interface Context { @@ -13,6 +14,11 @@ declare module 'cordis' { interface Intercept { http: HTTP.Config } + + interface Events { + 'http/dispatcher'(url: URL): undici.Dispatcher | undefined + 'http/http-agent'(url: URL): http.Agent | undefined + } } const _Error = Error @@ -135,11 +141,15 @@ export function apply(ctx: Context, config?: HTTP.Config) { } function resolveURL(url: string | URL, config: HTTP.RequestConfig) { - try { - return new URL(url, config.baseURL).href - } catch { - return trimSlash(config.endpoint || '') + url + if (config.endpoint) { + ctx.emit('internal/warning', 'endpoint is deprecated, please use baseURL instead') + url = trimSlash(config.endpoint) + url + } + url = new URL(url, config.baseURL) + for (const [key, value] of Object.entries(config.params ?? {})) { + url.searchParams.append(key, value) } + return url } function decode(response: Response) { @@ -153,6 +163,14 @@ export function apply(ctx: Context, config?: HTTP.Config) { } } + function resolveDispatcher(href?: string) { + if (!href) return + const url = new URL(href) + const agent = ctx.bail('http/dispatcher', url) + if (agent) return agent + throw new Error(`Cannot resolve proxy agent ${url}`) + } + const http = async function http(this: Context, ...args: any[]) { let method: HTTP.Method | undefined if (typeof args[1] === 'string' || args[1] instanceof URL) { @@ -176,6 +194,7 @@ export function apply(ctx: Context, config?: HTTP.Config) { body: config.data, headers: config.headers, signal: controller.signal, + ['dispatcher' as never]: resolveDispatcher(config?.proxyAgent), }).catch((cause) => { const error = new HTTP.Error(cause.message) error.cause = cause @@ -247,11 +266,20 @@ export function apply(ctx: Context, config?: HTTP.Config) { return response.headers } - http.ws = async function (this: HTTP, url: string, init?: HTTP.Config) { + function resolveAgent(href?: string) { + if (!href) return + const url = new URL(href) + const agent = ctx.bail('http/http-agent', url) + if (agent) return agent + throw new Error(`Cannot resolve proxy agent ${url}`) + } + + http.ws = async function (this: HTTP, url: string | URL, init?: HTTP.Config) { const caller = this[Context.current] const config = mergeConfig(caller, init) + url = resolveURL(url, config) const socket = new WebSocket(url, 'Server' in WebSocket ? { - // agent: caller.agent(config?.proxyAgent), + agent: resolveAgent(config?.proxyAgent), handshakeTimeout: config?.timeout, headers: config?.headers, } as ClientOptions as never : undefined) @@ -275,7 +303,7 @@ export function apply(ctx: Context, config?: HTTP.Config) { responseType: 'arraybuffer', timeout: +options.timeout! || undefined, }) - const mime = headers['content-type'] + const mime = headers.get('Content-Type') ?? undefined const [, name] = responseUrl.match(/.+\/([^/?]*)(?=\?)?/)! return { mime, name, data } }