From f8ae00220be9f841b9079e0e054eb247503abd6d Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Sun, 15 Sep 2024 13:59:03 +0800 Subject: [PATCH] f --- config/config.default.js | 4 +- lib/core/httpclient4.js | 38 ++++++++++++++ lib/egg.js | 12 ++++- package.json | 6 +-- test/doc.test.js | 41 --------------- .../app.js | 6 +-- .../config/config.default.js | 4 +- .../apps/httpclient-allowH2/package.json | 3 ++ .../apps/httpclient-http2/package.json | 3 -- test/fixtures/apps/httpclient-tracer/app.js | 4 +- test/lib/core/httpclient.test.js | 52 +++++++++++++++---- 11 files changed, 104 insertions(+), 69 deletions(-) create mode 100644 lib/core/httpclient4.js delete mode 100644 test/doc.test.js rename test/fixtures/apps/{httpclient-http2 => httpclient-allowH2}/app.js (77%) rename test/fixtures/apps/{httpclient-http2 => httpclient-allowH2}/config/config.default.js (55%) create mode 100644 test/fixtures/apps/httpclient-allowH2/package.json delete mode 100644 test/fixtures/apps/httpclient-http2/package.json diff --git a/config/config.default.js b/config/config.default.js index 53a913a20a..534fba9891 100644 --- a/config/config.default.js +++ b/config/config.default.js @@ -303,8 +303,8 @@ module.exports = appInfo => { * @property {Number} httpsAgent.freeSocketTimeout - httpss agent socket keepalive max free time, default is 4000 ms. * @property {Number} httpsAgent.maxSockets - https agent max socket number of one host, default is `Number.MAX_SAFE_INTEGER` @ses https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/MAX_SAFE_INTEGER * @property {Number} httpsAgent.maxFreeSockets - https agent max free socket number of one host, default is 256. - * @property {Boolean} useHttpClientNext - use urllib@3 HttpClient - * @property {Boolean} allowH2 - Allow to use HTTP2 first, only work on `useHttpClientNext = true` + * @property {Boolean} useHttpClientNext - use urllib@3 HttpClient, default is false + * @property {Boolean} allowH2 - use urllib@4 HttpClient and enable H2, default is false. Only works on Node.js >= 18 */ config.httpclient = { enableDNSCache: false, diff --git a/lib/core/httpclient4.js b/lib/core/httpclient4.js new file mode 100644 index 0000000000..7d8134f0c2 --- /dev/null +++ b/lib/core/httpclient4.js @@ -0,0 +1,38 @@ +const { HttpClient } = require('urllib4'); +const ms = require('humanize-ms'); + +class HttpClient4 extends HttpClient { + constructor(app) { + normalizeConfig(app); + const config = app.config.httpclient; + super({ + app, + defaultArgs: config.request, + allowH2: config.allowH2, + }); + this.app = app; + } + + async request(url, options) { + options = options || {}; + if (options.ctx && options.ctx.tracer) { + options.tracer = options.ctx.tracer; + } else { + options.tracer = options.tracer || this.app.tracer; + } + return await super.request(url, options); + } + + async curl(...args) { + return await this.request(...args); + } +} + +function normalizeConfig(app) { + const config = app.config.httpclient; + if (typeof config.request.timeout === 'string') { + config.request.timeout = ms(config.request.timeout); + } +} + +module.exports = HttpClient4; diff --git a/lib/egg.js b/lib/egg.js index a70c69fae8..9bcf24c666 100644 --- a/lib/egg.js +++ b/lib/egg.js @@ -20,6 +20,13 @@ const utils = require('./core/utils'); const BaseContextClass = require('./core/base_context_class'); const BaseHookClass = require('./core/base_hook_class'); +let HttpClient4 = HttpClientNext; +const mainNodejsVersion = parseInt(process.versions.node.split('.')[0]); +if (mainNodejsVersion >= 18) { + // urllib@4 only works on Node.js >= 18 + HttpClient4 = require('./core/httpclient4'); +} + const HTTPCLIENT = Symbol('EggApplication#httpclient'); const LOGGERS = Symbol('EggApplication#loggers'); const EGG_PATH = Symbol.for('egg#eggPath'); @@ -51,6 +58,7 @@ class EggApplication extends EggCore { this.ContextHttpClient = ContextHttpClient; this.HttpClient = HttpClient; this.HttpClientNext = HttpClientNext; + this.HttpClient4 = HttpClient4; this.loader.loadConfig(); @@ -292,7 +300,9 @@ class EggApplication extends EggCore { */ createHttpClient(options) { let httpClient; - if (this.config.httpclient.useHttpClientNext) { + if (this.config.httpclient.allowH2) { + httpClient = new this.HttpClient4(this); + } else if (this.config.httpclient.useHttpClientNext) { httpClient = new this.HttpClientNext(this, options); } else if (this.config.httpclient.enableDNSCache) { httpClient = new DNSCacheHttpClient(this, options); diff --git a/package.json b/package.json index 734fbdb851..48bbb375f1 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,8 @@ "onelogger": "^1.0.0", "sendmessage": "^2.0.0", "urllib": "^2.33.0", - "urllib-next": "npm:urllib@^3.26.0", + "urllib-next": "npm:urllib@^3.27.1", + "urllib4": "npm:urllib@^4.3.0", "utility": "^2.1.0", "ylru": "^1.3.2" }, @@ -77,7 +78,6 @@ "egg-view-nunjucks": "^2.3.0", "eslint": "^8.23.1", "eslint-config-egg": "^12.0.0", - "findlinks": "^2.2.0", "formstream": "^1.1.1", "jsdoc": "^3.6.11", "koa": "^2.13.4", @@ -85,11 +85,9 @@ "node-libs-browser": "^2.2.1", "pedding": "^1.1.0", "prettier": "^2.7.1", - "puppeteer": "^19.11.1", "react": "^16.14.0", "react-dom": "^16.14.0", "react-router": "^5.3.4", - "runscript": "^1.5.3", "sdk-base": "^4.2.1", "spy": "^1.0.0", "supertest": "^6.2.4", diff --git a/test/doc.test.js b/test/doc.test.js deleted file mode 100644 index de53ea206b..0000000000 --- a/test/doc.test.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -const path = require('path'); -const findlinks = require('findlinks'); -const assert = require('assert'); -const runscript = require('runscript'); -const utils = require('./utils'); -const puppeteer = require('puppeteer'); - -describe('test/doc.test.js', () => { - /** - * This unit test is ONLY working for the latest nodejs version (v18.0.0) - * or higher version, bcoz on windows, it takes quite a lot of time - * to generate the whole doc. We only need some of the test cases to check - * whether the links inside the doc gets fine or not. - */ - it('should have no broken urls (based on non-windows platform and node\'s version >=18)', async function() { - - const mainNodejsVersion = parseInt(process.versions.node.split('.')[0]); - - if (process.platform === 'linux' && mainNodejsVersion >= 18) { - const cwd = path.dirname(__dirname); - const dumi = path.join(cwd, 'node_modules', '.bin', 'dumi'); - await runscript(`cross-env NODE_OPTIONS=--openssl-legacy-provider APP_ROOT=./site ${dumi} build`, - { - cwd, - }); - const app = utils.cluster({ - baseDir: 'apps/docapp', - }); - app.coverage(false); - await app.ready(); - const result = await findlinks({ src: app.url, logger: console, puppeteer }); - if (result.fail !== 0) console.log(result); - assert(result.fail === 0); - app.close(); - } else { - this.skip(); - } - }).timeout(10 * 60 * 1000); -}); diff --git a/test/fixtures/apps/httpclient-http2/app.js b/test/fixtures/apps/httpclient-allowH2/app.js similarity index 77% rename from test/fixtures/apps/httpclient-http2/app.js rename to test/fixtures/apps/httpclient-allowH2/app.js index 29c5a62b35..5af2297350 100644 --- a/test/fixtures/apps/httpclient-http2/app.js +++ b/test/fixtures/apps/httpclient-allowH2/app.js @@ -1,9 +1,7 @@ -'use strict'; - const assert = require('assert'); module.exports = app => { - class CustomHttpClient extends app.HttpClientNext { + class CustomHttpClient extends app.HttpClient4 { request(url, opt) { return new Promise(resolve => { assert(/^http/.test(url), 'url should start with http, but got ' + url); @@ -17,5 +15,5 @@ module.exports = app => { return this.request(url, opt); } } - app.HttpClientNext = CustomHttpClient; + app.HttpClient4 = CustomHttpClient; }; diff --git a/test/fixtures/apps/httpclient-http2/config/config.default.js b/test/fixtures/apps/httpclient-allowH2/config/config.default.js similarity index 55% rename from test/fixtures/apps/httpclient-http2/config/config.default.js rename to test/fixtures/apps/httpclient-allowH2/config/config.default.js index 77834b89ad..e477cdb9db 100644 --- a/test/fixtures/apps/httpclient-http2/config/config.default.js +++ b/test/fixtures/apps/httpclient-allowH2/config/config.default.js @@ -1,4 +1,6 @@ exports.httpclient = { - useHttpClientNext: true, allowH2: true, + request: { + timeout: 99, + }, }; diff --git a/test/fixtures/apps/httpclient-allowH2/package.json b/test/fixtures/apps/httpclient-allowH2/package.json new file mode 100644 index 0000000000..d788b7620b --- /dev/null +++ b/test/fixtures/apps/httpclient-allowH2/package.json @@ -0,0 +1,3 @@ +{ + "name": "httpclient-allowH2" +} diff --git a/test/fixtures/apps/httpclient-http2/package.json b/test/fixtures/apps/httpclient-http2/package.json deleted file mode 100644 index 56b5d28da8..0000000000 --- a/test/fixtures/apps/httpclient-http2/package.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "httpclient-overwrite" -} diff --git a/test/fixtures/apps/httpclient-tracer/app.js b/test/fixtures/apps/httpclient-tracer/app.js index 0b6d6e866c..80fd82b24c 100644 --- a/test/fixtures/apps/httpclient-tracer/app.js +++ b/test/fixtures/apps/httpclient-tracer/app.js @@ -28,14 +28,14 @@ module.exports = app => { }); assert(res.status === 200); - res = await httpclient.request('https://github.com', { + res = await httpclient.request('https://registry.npmmirror.com', { method: 'GET', timeout: 20000, }); assert(res.status === 200); - res = await httpclient.request('https://www.npmjs.com', { + res = await httpclient.request('https://npmmirror.com', { method: 'GET', timeout: 20000, }); diff --git a/test/lib/core/httpclient.test.js b/test/lib/core/httpclient.test.js index e0b26b0da3..f0ad78d222 100644 --- a/test/lib/core/httpclient.test.js +++ b/test/lib/core/httpclient.test.js @@ -1,5 +1,4 @@ const assert = require('node:assert'); -const { sensitiveHeaders } = require('node:http2'); const mm = require('egg-mock'); const urllib = require('urllib'); const Httpclient = require('../../../lib/core/httpclient'); @@ -305,7 +304,7 @@ describe('test/lib/core/httpclient.test.js', () => { describe('overwrite httpclient support allowH2=true', () => { let app; before(() => { - app = utils.app('apps/httpclient-http2'); + app = utils.app('apps/httpclient-allowH2'); return app.ready(); }); after(() => app.close()); @@ -314,21 +313,52 @@ describe('test/lib/core/httpclient.test.js', () => { const res = await app.httpclient.request(url); assert.equal(res.status, 200); assert.equal(res.data.toString(), 'GET /'); - assert.equal(sensitiveHeaders in res.headers, false); + // assert.equal(sensitiveHeaders in res.headers, false); const res2 = await app.httpclient.request('https://registry.npmmirror.com/urllib/latest', { dataType: 'json', }); assert.equal(res2.status, 200); assert.equal(res2.data.name, 'urllib'); - assert.equal(sensitiveHeaders in res2.headers, true); + // assert.equal(sensitiveHeaders in res2.headers, true); }); - it('should assert url', () => { - return app.httpclient.curl('unknown url') - .catch(err => { - assert(err); - assert(err.message.includes('url should start with http, but got unknown url')); + it('should set request default global timeout to 99ms', async () => { + await assert.rejects(async () => { + await app.httpclient.curl(`${url}/timeout`); + }, err => { + assert.equal(err.name, 'HttpClientRequestTimeoutError'); + assert(err.message.includes('Request timeout for 99 ms')); + return true; + }); + }); + + it('should request http1.1 success', async () => { + const result = await app.httpclient.curl(`${url}`, { + dataType: 'text', + }); + assert.equal(result.status, 200); + assert.equal(result.data, 'GET /'); + }); + + it('should request http2 success', async () => { + for (let i = 0; i < 10; i++) { + const result = await app.httpclient.curl('https://registry.npmmirror.com', { + dataType: 'json', + timeout: 5000, }); + assert.equal(result.status, 200); + assert.equal(result.headers['content-type'], 'application/json; charset=utf-8'); + assert.equal(result.data.sync_model, 'all'); + } + }); + + it('should assert url', async () => { + await assert.rejects(async () => { + await app.httpclient.curl('unknown url'); + }, err => { + assert.match(err.message, /url should start with http, but got unknown url/); + return true; + }); }); }); @@ -618,13 +648,13 @@ describe('test/lib/core/httpclient.test.js', () => { }); assert(res.status === 200); - res = await httpclient.request('https://github.com', { + res = await httpclient.request('https://registry.npmmirror.com', { method: 'GET', timeout: 20000, }); assert(res.status === 200); - res = await httpclient.request('https://www.npmjs.com', { + res = await httpclient.request('https://npmmirror.com', { method: 'GET', timeout: 20000, });