diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs-3.x.yml similarity index 70% rename from .github/workflows/nodejs.yml rename to .github/workflows/nodejs-3.x.yml index 68ad9c1ac4..e23430a1a5 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs-3.x.yml @@ -1,11 +1,10 @@ -name: CI +name: CI for 3.x on: push: - branches: [ master, 2.x, 1.x ] - + branches: [ 3.x ] pull_request: - branches: [ master, 2.x, 1.x ] + branches: [ 3.x ] jobs: Job: @@ -15,3 +14,5 @@ jobs: os: 'ubuntu-latest, macos-latest, windows-latest' version: '14, 16, 18, 20, 22' install: 'npm i -g npminstall && npminstall' + secrets: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index d8fbb48f9f..0000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Release -on: - push: - branches: [ master, 2.x, 1.x ] - -jobs: - release: - name: Node.js - uses: eggjs/github-actions/.github/workflows/node-release.yml@master - secrets: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - GIT_TOKEN: ${{ secrets.GIT_TOKEN }} - with: - install: 'npm install --legacy-peer-deps --no-package-lock --no-fund' diff --git a/History.md b/History.md new file mode 100644 index 0000000000..d0634a5fe1 --- /dev/null +++ b/History.md @@ -0,0 +1,19 @@ + +3.26.0 / 2024-07-01 +================== + +**features** + * [[`b0292a8b`](http://github.com/eggjs/egg/commit/b0292a8b7e76d5dbf7441b7164c39441dbae51ec)] - feat: allow to create httpClient from app (#5334) (fengmk2 <>) + +3.25.0 / 2024-06-27 +================== + +**features** + * [[`ceded0b1`](http://github.com/eggjs/egg/commit/ceded0b1c9217503c5ed9226f96c493d6bd00547)] - feat: allow to httpClient use HTTP2 first (#5332) (fengmk2 <>) + +**others** + * [[`8553c3f2`](http://github.com/eggjs/egg/commit/8553c3f23e423e9f60144b11a484b703fe7c9229)] - chore: remove auto release (fengmk2 <>) + * [[`b4f01a1c`](http://github.com/eggjs/egg/commit/b4f01a1c6bf006c943c85fce334b81d61f55b7d0)] - chore: fix release branches name (fengmk2 <>) + * [[`a8073b04`](http://github.com/eggjs/egg/commit/a8073b04fc3821bb23326c6c8b4fd0ccaeb5c200)] - chore: add release config (fengmk2 <>) + * [[`8ce2ff90`](http://github.com/eggjs/egg/commit/8ce2ff90bfbb9e4580a23ea49a15fdb1c185fbb5)] - chore: add npm publish tag (fengmk2 <>) + * [[`44950ed8`](http://github.com/eggjs/egg/commit/44950ed82a3ce4d5d4b9028aee98d6650298a552)] - chore: start 3.x LTS (fengmk2 <>) diff --git a/README.md b/README.md index 602a358413..c094395ff3 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,11 @@ English | [简体中文](./README.zh-CN.md) [![NPM download](https://img.shields.io/npm/dm/egg.svg?style=flat-square)](https://npmjs.org/package/egg) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Feggjs%2Fegg.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Feggjs%2Fegg?ref=badge_shield) -[![Continuous Integration](https://github.com/eggjs/egg/actions/workflows/nodejs.yml/badge.svg)](https://github.com/eggjs/egg/actions?query=branch%3Amaster) +[![Continuous Integration](https://github.com/eggjs/egg/actions/workflows/nodejs-3.x.yml/badge.svg)](https://github.com/eggjs/egg/actions?query=branch%3A3.x) [![Test coverage](https://img.shields.io/codecov/c/github/eggjs/egg.svg?style=flat-square)](https://codecov.io/gh/eggjs/egg) [![Known Vulnerabilities](https://snyk.io/test/npm/egg/badge.svg?style=flat-square)](https://snyk.io/test/npm/egg) [![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/eggjs?style=flat-square)](https://opencollective.com/eggjs) - ## Features - Built-in Process Management @@ -62,5 +61,4 @@ To become a contributor, please follow our [contributing guide](CONTRIBUTING.md) [MIT](LICENSE) - -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Feggjs%2Fegg.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Feggjs%2Fegg?ref=badge_large) \ No newline at end of file +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Feggjs%2Fegg.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Feggjs%2Fegg?ref=badge_large) diff --git a/config/config.default.js b/config/config.default.js index 5c0259ea06..53a913a20a 100644 --- a/config/config.default.js +++ b/config/config.default.js @@ -304,6 +304,7 @@ module.exports = appInfo => { * @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` */ config.httpclient = { enableDNSCache: false, @@ -326,6 +327,7 @@ module.exports = appInfo => { maxFreeSockets: 256, }, useHttpClientNext: false, + // allowH2: false, }; /** diff --git a/index.d.ts b/index.d.ts index 1dbf24df55..7cff3ed7ca 100644 --- a/index.d.ts +++ b/index.d.ts @@ -296,16 +296,18 @@ declare module 'egg' { request?: HttpClientRequestOptions | RequestOptionsOld; /** Whether enable dns cache */ enableDNSCache?: boolean; - /** Enable proxy request, default is false. */ + /** Enable proxy request. Default is `false`. */ enableProxy?: boolean; - /** proxy agent uri or options, default is null. */ + /** proxy agent uri or options. Default is `null`. */ proxy?: string | { [key: string]: any }; /** DNS cache lookup interval */ dnsCacheLookupInterval?: number; /** DNS cache max age */ dnsCacheMaxLength?: number; - /** use urllib@3 HttpClient */ + /** use urllib@3 HttpClient. Default is `false` */ useHttpClientNext?: boolean; + /** Allow to use HTTP2 first, only work on `useHttpClientNext = true`. Default is `false` */ + allowH2?: boolean; } export interface EggAppConfig { diff --git a/lib/core/httpclient_next.js b/lib/core/httpclient_next.js index 266338d522..1a18e6ce23 100644 --- a/lib/core/httpclient_next.js +++ b/lib/core/httpclient_next.js @@ -2,12 +2,20 @@ const { HttpClient } = require('urllib-next'); const ms = require('humanize-ms'); class HttpClientNext extends HttpClient { - constructor(app) { + constructor(app, options) { normalizeConfig(app); - const config = app.config.httpclient; + options = options || {}; + options = { + ...app.config.httpclient, + ...options, + }; super({ app, - defaultArgs: config.request, + 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, }); this.app = app; } diff --git a/lib/egg.js b/lib/egg.js index aabba225b4..a70c69fae8 100644 --- a/lib/egg.js +++ b/lib/egg.js @@ -286,6 +286,22 @@ class EggApplication extends EggCore { return await this.httpclient.request(url, opts); } + /** + * Create a new HttpClient instance with custom options + * @param {Object} [options] HttpClient init options + */ + createHttpClient(options) { + let httpClient; + if (this.config.httpclient.useHttpClientNext) { + httpClient = new this.HttpClientNext(this, options); + } else if (this.config.httpclient.enableDNSCache) { + httpClient = new DNSCacheHttpClient(this, options); + } else { + httpClient = new this.HttpClient(this, options); + } + return httpClient; + } + /** * HttpClient instance * @see https://github.com/node-modules/urllib @@ -293,13 +309,7 @@ class EggApplication extends EggCore { */ get httpclient() { if (!this[HTTPCLIENT]) { - if (this.config.httpclient.useHttpClientNext) { - this[HTTPCLIENT] = new this.HttpClientNext(this); - } else if (this.config.httpclient.enableDNSCache) { - this[HTTPCLIENT] = new DNSCacheHttpClient(this); - } else { - this[HTTPCLIENT] = new this.HttpClient(this); - } + this[HTTPCLIENT] = this.createHttpClient(); } return this[HTTPCLIENT]; } diff --git a/package.json b/package.json index bbf1bf0cf3..2d8cf0ad98 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "egg", - "version": "3.24.1", + "version": "3.26.0", "publishConfig": { - "tag": "latest" + "tag": "release-3.x", + "access": "public" }, "description": "A web framework's framework for Node.js", "keywords": [ @@ -54,7 +55,7 @@ "onelogger": "^1.0.0", "sendmessage": "^2.0.0", "urllib": "^2.33.0", - "urllib-next": "npm:urllib@^3.22.4", + "urllib-next": "npm:urllib@^3.26.0", "utility": "^2.1.0", "ylru": "^1.3.2" }, diff --git a/test/fixtures/apps/httpclient-http2/app.js b/test/fixtures/apps/httpclient-http2/app.js new file mode 100644 index 0000000000..29c5a62b35 --- /dev/null +++ b/test/fixtures/apps/httpclient-http2/app.js @@ -0,0 +1,21 @@ +'use strict'; + +const assert = require('assert'); + +module.exports = app => { + class CustomHttpClient extends app.HttpClientNext { + request(url, opt) { + return new Promise(resolve => { + assert(/^http/.test(url), 'url should start with http, but got ' + url); + resolve(); + }).then(() => { + return super.request(url, opt); + }); + } + + curl(url, opt) { + return this.request(url, opt); + } + } + app.HttpClientNext = CustomHttpClient; +}; diff --git a/test/fixtures/apps/httpclient-http2/config/config.default.js b/test/fixtures/apps/httpclient-http2/config/config.default.js new file mode 100644 index 0000000000..77834b89ad --- /dev/null +++ b/test/fixtures/apps/httpclient-http2/config/config.default.js @@ -0,0 +1,4 @@ +exports.httpclient = { + useHttpClientNext: true, + allowH2: true, +}; diff --git a/test/fixtures/apps/httpclient-http2/package.json b/test/fixtures/apps/httpclient-http2/package.json new file mode 100644 index 0000000000..56b5d28da8 --- /dev/null +++ b/test/fixtures/apps/httpclient-http2/package.json @@ -0,0 +1,3 @@ +{ + "name": "httpclient-overwrite" +} diff --git a/test/lib/core/httpclient.test.js b/test/lib/core/httpclient.test.js index 2b5fbb73e5..a98daf7390 100644 --- a/test/lib/core/httpclient.test.js +++ b/test/lib/core/httpclient.test.js @@ -1,4 +1,5 @@ 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'); @@ -237,6 +238,12 @@ describe('test/lib/core/httpclient.test.js', () => { }); after(() => app.close()); + it('should work', async () => { + const res = await app.httpclient.request(url); + assert.equal(res.status, 200); + assert.equal(res.data.toString(), 'GET /'); + }); + it('should set request default global timeout to 99ms', () => { return app.httpclient.curl(`${url}/timeout`) .catch(err => { @@ -255,6 +262,36 @@ describe('test/lib/core/httpclient.test.js', () => { }); }); + describe('overwrite httpclient support allowH2=true', () => { + let app; + before(() => { + app = utils.app('apps/httpclient-http2'); + return app.ready(); + }); + after(() => app.close()); + + it('should work on http2', async () => { + 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); + 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); + }); + + 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')); + }); + }); + }); + describe('httpclient tracer', () => { let app; before(() => {