From a7c6df45cec356f70d6fd874d91826b09c1e9914 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Fri, 29 Nov 2024 15:42:13 +0800 Subject: [PATCH] feat: use urllib beta --- app.ts | 74 +++++++++++++++++++++++++++++++++++++++- config/config.default.ts | 1 + package.json | 1 + 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/app.ts b/app.ts index 75014045..db484e0d 100644 --- a/app.ts +++ b/app.ts @@ -1,6 +1,12 @@ import path from 'path'; import { readFile } from 'fs/promises'; -import { Application } from 'egg'; +import { Application, Context } from 'egg'; +import { + HttpClient as RawHttpClient, + RequestURL as HttpClientRequestURL, + RequestOptions, +} from 'urllib'; +import ms from 'ms'; import { ChangesStreamService } from './app/core/service/ChangesStreamService'; declare module 'egg' { @@ -9,12 +15,78 @@ declare module 'egg' { } } +interface HttpClientRequestOptions extends RequestOptions { + ctx?: Context; + tracer?: unknown; +} + +const SSRF_HTTPCLIENT = Symbol('SSRF_HTTPCLIENT'); + +class HttpClient extends RawHttpClient { + readonly #app: Application & { tracer?: unknown }; + + constructor(app: Application, options?: any) { + normalizeConfig(app); + options = { + ...app.config.httpclient, + ...options, + }; + super({ + app, + defaultArgs: options.request, + allowH2: options.allowH2, + // use on egg-security ssrf + // https://github.com/eggjs/egg-security/blob/master/lib/extend/safe_curl.js#L11 + checkAddress: options.checkAddress, + } as any); + this.#app = app; + } + + async request(url: HttpClientRequestURL, options?: HttpClientRequestOptions) { + options = options ?? {}; + if (options.ctx?.tracer) { + options.tracer = options.ctx.tracer; + } else { + options.tracer = options.tracer ?? this.#app.tracer; + } + return await super.request(url, options); + } + + async curl(url: HttpClientRequestURL, options?: HttpClientRequestOptions) { + return await this.request(url, options); + } + + async safeCurl(url: HttpClientRequestURL, options: any = {}) { + if (!this[SSRF_HTTPCLIENT]) { + const ssrfConfig = this.#app.config.security.ssrf; + if (ssrfConfig?.checkAddress) { + options.checkAddress = ssrfConfig.checkAddress; + } else { + this.#app.logger.warn('[egg-security] please configure `config.security.ssrf` first'); + } + this[SSRF_HTTPCLIENT] = new HttpClient(this.#app, { + checkAddress: ssrfConfig.checkAddress, + }); + } + return await this[SSRF_HTTPCLIENT].request(url, options); + } +} + +function normalizeConfig(app: Application) { + const config = app.config.httpclient; + if (typeof config.request?.timeout === 'string') { + config.request.timeout = ms(config.request.timeout as string); + } +} + export default class CnpmcoreAppHook { private readonly app: Application; constructor(app: Application) { this.app = app; this.app.binaryHTML = ''; + Reflect.set(app, 'HttpClient', HttpClient); + Reflect.set(app, 'HttpClientNext', HttpClient); } async configWillLoad() { diff --git a/config/config.default.ts b/config/config.default.ts index 3b1adeff..2cf7904a 100644 --- a/config/config.default.ts +++ b/config/config.default.ts @@ -187,6 +187,7 @@ export default (appInfo: EggAppConfig) => { config.httpclient = { useHttpClientNext: true, + allowH2: true, }; config.view = { diff --git a/package.json b/package.json index cc07ea78..ae85363e 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "ssri": "^8.0.1", "type-fest": "^2.5.3", "ua-parser-js": "^1.0.34", + "urllib": "4.5.0-beta.2", "validate-npm-package-name": "^3.0.0" }, "optionalDependencies": {