From 23e719ff2f8d13b3f25f6e36cad6718864075fdd Mon Sep 17 00:00:00 2001 From: dr-js Date: Wed, 22 Apr 2020 09:31:46 +0800 Subject: [PATCH] @dr-js/core@0.3.0-dev.8 notable change: - temp-fix: ci: delay test 14.x, wait for: https://github.com/puppeteer/puppeteer/issues/5719 - fix: ci: git CRLF config & longer test timeout - fix: sanity test for `nodejs@14` - node: add TLS server session ticket rotation - use `response.writableEnded` (need nodejs@>=12.9) - package update --- .github/workflows/ci-test.yml | 12 ++- SPEC.md | 4 + package-lock.json | 88 +++++++++---------- package.json | 14 +-- source-bin/index.js | 11 ++- source-bin/option.js | 1 + source/browser/net.test.js | 2 +- .../.sanity.test/memoryUsageDataType.test.js | 14 +-- source/node/net.test.js | 2 +- source/node/server/Responder/Common.js | 3 +- source/node/server/Server.js | 36 ++++++-- 11 files changed, 113 insertions(+), 74 deletions(-) diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index bd39eb55..032e7afd 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -5,13 +5,19 @@ on: [ push ] jobs: build: strategy: + fail-fast: false # allow all test to run to the end matrix: - os: [ ubuntu-latest ] # [ ubuntu-latest, windows-latest, macos-latest ] - node-version: [ 12.x, 13.x ] + os: [ ubuntu-latest, windows-latest ] # [ ubuntu-latest, windows-latest, macos-latest ] + node-version: [ 12.x, 13.x ] # TODO: delay test 14.x, wait for: https://github.com/puppeteer/puppeteer/issues/5719 runs-on: ${{ matrix.os }} steps: + - name: Reset Git config # fix win32 CI cause `something to commit` test error: https://github.com/actions/checkout/issues/135#issuecomment-602171132 + run: | + git config --global core.autocrlf false + git config --global core.eol lf + - name: Setup Git repo uses: actions/checkout@v2 # https://github.com/actions/checkout @@ -27,7 +33,7 @@ jobs: echo "npm: $(npm -v)" - name: Patch `libgbm1` for `puppeteer@3` # https://github.com/puppeteer/puppeteer/issues/5674 - run: sudo apt-get install -y libgbm1 + run: node -e "process.exitCode = Number(os.platform() !== 'win32')" || sudo apt-get install -y libgbm1 - run: npm ci diff --git a/SPEC.md b/SPEC.md index 24b4ebf1..6a520a97 100644 --- a/SPEC.md +++ b/SPEC.md @@ -463,6 +463,8 @@ > basic system status: -J=isOutputJSON > --open --o -o [OPTIONAL] [ARGUMENT=0-1] > use system default app to open uri or path: $0=uriOrPath/cwd +> --which --w -w [OPTIONAL] [ARGUMENT=1] +> resolve to full executable path: -R=resolveRoot/cwd, $0=commandNameOrPath > --fetch --f -f [OPTIONAL] [ARGUMENT=1-3] > fetch "GET" uri: -O=outputFile/stdout, $@=initialUrl,jumpMax/4,timeout/0 > --process-status --ps [OPTIONAL] [ARGUMENT=0-1] @@ -505,6 +507,7 @@ > export DR_JS_MODIFY_DELETE="[OPTIONAL] [ARGUMENT=0+]" > export DR_JS_STATUS="[OPTIONAL] [ARGUMENT=0+]" > export DR_JS_OPEN="[OPTIONAL] [ARGUMENT=0-1]" +> export DR_JS_WHICH="[OPTIONAL] [ARGUMENT=1]" > export DR_JS_FETCH="[OPTIONAL] [ARGUMENT=1-3]" > export DR_JS_PROCESS_STATUS="[OPTIONAL] [ARGUMENT=0-1]" > export DR_JS_JSON_FORMAT="[OPTIONAL] [ARGUMENT=0-1]" @@ -539,6 +542,7 @@ > "modifyDelete": [ "[OPTIONAL] [ARGUMENT=0+]" ], > "status": [ "[OPTIONAL] [ARGUMENT=0+]" ], > "open": [ "[OPTIONAL] [ARGUMENT=0-1]" ], +> "which": [ "[OPTIONAL] [ARGUMENT=1]" ], > "fetch": [ "[OPTIONAL] [ARGUMENT=1-3]" ], > "processStatus": [ "[OPTIONAL] [ARGUMENT=0-1]" ], > "jsonFormat": [ "[OPTIONAL] [ARGUMENT=0-1]" ], diff --git a/package-lock.json b/package-lock.json index 197cec00..f0afcae4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@dr-js/core", - "version": "0.3.0-dev.7", + "version": "0.3.0-dev.8", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -997,24 +997,24 @@ } }, "@dr-js/core": { - "version": "0.3.0-dev.6", - "resolved": "https://registry.npmjs.org/@dr-js/core/-/core-0.3.0-dev.6.tgz", - "integrity": "sha512-PNtCLgef1BAwM7Vaecd3Phs1XUWHXZcXuVA/qjCnIB6TW/+ZWXqJ8glqqvR22/ABPMy5L/3WNA6AimyDAJAmrg==", + "version": "0.3.0-dev.7", + "resolved": "https://registry.npmjs.org/@dr-js/core/-/core-0.3.0-dev.7.tgz", + "integrity": "sha512-l55x0hzKxgn1vDI7P9GAxMTHQYJeBwWm4z5ufBHLDR0SH4b6bQTWPBzE5aFSXDD7LeZLBQ9uZN0VZR2mMiEVZQ==", "dev": true }, "@dr-js/dev": { - "version": "0.3.0-dev.6", - "resolved": "https://registry.npmjs.org/@dr-js/dev/-/dev-0.3.0-dev.6.tgz", - "integrity": "sha512-OMLaviry9VOAiOfoiWJoe1+Sopqb90+dQo/vTN0BU5FQYNbYb4niwg114FgcIeO5a9+uQOA8FVTnJ1ahiuKtJA==", + "version": "0.3.0-dev.7", + "resolved": "https://registry.npmjs.org/@dr-js/dev/-/dev-0.3.0-dev.7.tgz", + "integrity": "sha512-2RkzPbu3JDKNKfLQwlGybqM3xFinsM0Bk0pFIbymJ0zd4ez1wYx8A8LPx+HPq/CEClK/uwJMwElEvSGMeFRQLQ==", "dev": true, "requires": { - "@dr-js/core": "^0.3.0 || ^0.3.0-dev.6" + "@dr-js/core": "^0.3.0 || ^0.3.0-dev.7" } }, "@dr-js/dev-web-puppeteer": { - "version": "0.3.0-dev.6", - "resolved": "https://registry.npmjs.org/@dr-js/dev-web-puppeteer/-/dev-web-puppeteer-0.3.0-dev.6.tgz", - "integrity": "sha512-B+pXNUGvG+iWC9ub1+dEeTpw+hXMlTSozxL3aZciyshqJqdLjsTE+jqn+NyRI+EhsBRupMIvjEzs3Na6lojJWA==", + "version": "0.3.0-dev.7", + "resolved": "https://registry.npmjs.org/@dr-js/dev-web-puppeteer/-/dev-web-puppeteer-0.3.0-dev.7.tgz", + "integrity": "sha512-1e94gg121NZXrVI6DUnt4rgF74N2Av/Kn6CAXxCDsUpjl1lu8hU911ansQe7xm7RacbgJqBPczc/DHd9prrMAg==", "dev": true, "requires": { "@babel/cli": "^7.8.4", @@ -1031,7 +1031,7 @@ "eslint-plugin-promise": "^4.2.1", "puppeteer": "^3.0.0", "terser": "^4.6.11", - "webpack": "^4.42.1" + "webpack": "^4.43.0" } }, "@types/color-name": { @@ -1047,9 +1047,9 @@ "dev": true }, "@types/node": { - "version": "13.13.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.1.tgz", - "integrity": "sha512-uysqysLJ+As9jqI5yqjwP3QJrhOcUwBjHUlUxPxjbplwKoILvXVsmYWEhfmAQlrPfbRZmhJB007o4L9sKqtHqQ==", + "version": "13.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.2.tgz", + "integrity": "sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==", "dev": true, "optional": true }, @@ -1481,9 +1481,9 @@ } }, "babel-plugin-dynamic-import-node": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.2.tgz", - "integrity": "sha512-yvczAMjbc73xira9yTyF1XnEmkX8QwlUhmxuhimeMUeAaA6s7busTPRVDzhVG7eeBdNcRiZ/mAwFrJ9It4vQcg==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", "dev": true, "requires": { "object.assign": "^4.1.0" @@ -1896,9 +1896,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001043", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001043.tgz", - "integrity": "sha512-MrBDRPJPDBYwACtSQvxg9+fkna5jPXhJlKmuxenl/ml9uf8LHKlDmLpElu+zTW/bEz7lC1m0wTDD7jiIB+hgFg==", + "version": "1.0.30001046", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001046.tgz", + "integrity": "sha512-CsGjBRYWG6FvgbyGy+hBbaezpwiqIOLkxQPY4A4Ea49g1eNsnQuESB+n4QM0BKii1j80MyJ26Ir5ywTQkbRE4g==", "dev": true }, "chalk": { @@ -2328,9 +2328,9 @@ } }, "electron-to-chromium": { - "version": "1.3.413", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.413.tgz", - "integrity": "sha512-Jm1Rrd3siqYHO3jftZwDljL2LYQafj3Kki5r+udqE58d0i91SkjItVJ5RwlJn9yko8i7MOcoidVKjQlgSdd1hg==", + "version": "1.3.415", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.415.tgz", + "integrity": "sha512-GbtYqKffx3sU8G0HxwXuJFfs58Q7+iwLa5rBwaULwET6jWW8IAQSrVnu7vEfiUIcMVfbYyFg7cw3zdm+EbBJmw==", "dev": true }, "elliptic": { @@ -4615,9 +4615,9 @@ "dev": true }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", "dev": true, "optional": true }, @@ -5210,9 +5210,9 @@ "dev": true }, "puppeteer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-3.0.0.tgz", - "integrity": "sha512-ArmIS8w+XhL4KGP05kxMousA9SFxmeirMkNNcVe5LjK4iGCbZ8qKnG4byuXMru7Ty7a9QwiMUIf80X+zmJuf2A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-3.0.1.tgz", + "integrity": "sha512-DxNnI9n4grVHC+9irUfNK2T6YFuRECJnvG7VzdVolxpVwWC5DQqI5ho9Z0af48K5MQW4sJY5cq3qQ5g6NkAjvw==", "dev": true, "requires": { "@types/mime-types": "^2.1.0", @@ -5442,9 +5442,9 @@ "dev": true }, "resolve": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.16.1.tgz", - "integrity": "sha512-rmAglCSqWWMrrBv/XM6sW0NuRFiKViw/W4d9EbC4pt+49H8JwHy+mcGmALTEg504AUDcLTvb1T2q3E9AnmY+ig==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -5785,9 +5785,9 @@ } }, "source-map-support": { - "version": "0.5.17", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.17.tgz", - "integrity": "sha512-bwdKOBZ5L0gFRh4KOxNap/J/MpvX9Yxsq9lFDx65s3o7F/NiHy7JRaGIS8MwW6tZPAq9UXE207Il0cfcb5yu/Q==", + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.18.tgz", + "integrity": "sha512-9luZr/BZ2QeU6tO2uG8N2aZpVSli4TSAOAqFOyTO51AJcD9P99c0K1h6dD6r6qo5dyT44BR5exweOaLLeldTkQ==", "dev": true, "requires": { "buffer-from": "^1.0.0", @@ -6305,9 +6305,9 @@ "dev": true }, "unbzip2-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.1.tgz", - "integrity": "sha512-sgDYfSDPMsA4Hr2/w7vOlrJBlwzmyakk1+hW8ObLvxSp0LA36LcL2XItGvOT3OSblohSdevMuT8FQjLsqyy4sA==", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.2.tgz", + "integrity": "sha512-pZMVAofMrrHX6Ik39hCk470kulCbmZ2SWfQLPmTWqfJV/oUm0gn1CblvHdUu4+54Je6Jq34x8kY6XjTy6dMkOg==", "dev": true, "requires": { "buffer": "^5.2.1", @@ -6514,16 +6514,16 @@ } }, "webpack": { - "version": "4.42.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.42.1.tgz", - "integrity": "sha512-SGfYMigqEfdGchGhFFJ9KyRpQKnipvEvjc1TwrXEPCM6H5Wywu10ka8o3KGrMzSMxMQKt8aCHUFh5DaQ9UmyRg==", + "version": "4.43.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz", + "integrity": "sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==", "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", "@webassemblyjs/helper-module-context": "1.9.0", "@webassemblyjs/wasm-edit": "1.9.0", "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.2.1", + "acorn": "^6.4.1", "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", @@ -6540,7 +6540,7 @@ "schema-utils": "^1.0.0", "tapable": "^1.1.3", "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.6.0", + "watchpack": "^1.6.1", "webpack-sources": "^1.4.1" }, "dependencies": { diff --git a/package.json b/package.json index f48892b1..88a3a3a3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@dr-js/core", - "version": "0.3.0-dev.7", + "version": "0.3.0-dev.8", "description": "A collection of strange functions", "author": "dr-js", "license": "MIT", @@ -28,11 +28,11 @@ "build-library-webpack-dev": "node -r @babel/register ./script/webpack development watch", "build-module": "dr-dev --EE BABEL_ENV=module -E -- babel ./source --out-dir ./output-gitignore/module", "// test ========================": "", - "test-dev": "dr-dev --EE BABEL_ENV=dev -E -- dr-dev --TR @babel/register -T source/common/module/AsyncTask.test.js", + "test-dev": "dr-dev --EE BABEL_ENV=dev -E -- dr-dev --TR @babel/register -T source/node/.sanity.test/memoryUsageDataType.test.js", "test-sanity": "dr-dev --EE BABEL_ENV=dev -E -- dr-dev --TR @babel/register -T source/common/.sanity.test/promise.test.js", - "test-source": "dr-dev --TR @babel/register -T source/env/ source/common/ source/node/ --TFS .test.js", - "test-output-library": "dr-dev -T output-gitignore/library/env/ output-gitignore/library/common/ output-gitignore/library/node/ --TFS .test.js", - "test-output-module": "dr-dev --TR @babel/register -T output-gitignore/module/env/ output-gitignore/module/common/ output-gitignore/module/node/ --TFS .test.js", + "test-source": "dr-dev --TR @babel/register -T source/env/ source/common/ source/node/ --TFS .test.js --TT 42000", + "test-output-library": "dr-dev -T output-gitignore/library/env/ output-gitignore/library/common/ output-gitignore/library/node/ --TFS .test.js --TT 42000", + "test-output-module": "dr-dev --TR @babel/register -T output-gitignore/module/env/ output-gitignore/module/common/ output-gitignore/module/node/ --TFS .test.js --TT 42000", "// test-browser ================": "", "test-browser": "node -r @babel/register ./script/testBrowser production", "test-browser-dev": "node -r @babel/register ./script/testBrowser development", @@ -47,8 +47,8 @@ "npm": ">=6.12" }, "devDependencies": { - "@dr-js/dev": "0.3.0-dev.6", - "@dr-js/dev-web-puppeteer": "0.3.0-dev.6" + "@dr-js/dev": "0.3.0-dev.7", + "@dr-js/dev-web-puppeteer": "0.3.0-dev.7" }, "sideEffects": false } diff --git a/source-bin/index.js b/source-bin/index.js index ec30870f..601cef60 100644 --- a/source-bin/index.js +++ b/source-bin/index.js @@ -19,6 +19,7 @@ import { autoTestServerPort } from '@dr-js/core/module/node/server/function' import { createServerPack } from '@dr-js/core/module/node/server/Server' import { createTCPProxyListener } from '@dr-js/core/module/node/server/Proxy' import { getDefaultOpenCommandList } from '@dr-js/core/module/node/system/DefaultOpen' +import { resolveCommandAsync } from '@dr-js/core/module/node/system/ResolveCommand' import { runSync } from '@dr-js/core/module/node/system/Run' import { getAllProcessStatusAsync, describeAllProcessStatusAsync } from '@dr-js/core/module/node/system/Process' import { getSystemStatus, describeSystemStatus } from '@dr-js/core/module/node/system/Status' @@ -60,6 +61,7 @@ const runMode = async (modeName, optionData) => { const argumentList = tryGet(modeName) || [] const isOutputJSON = Boolean(tryGet('json')) + const root = tryGetFirst('root') || process.cwd() const inputFile = tryGetFirst('input-file') const outputFile = tryGetFirst('output-file') const outputBuffer = (buffer) => outputFile @@ -133,6 +135,12 @@ const runMode = async (modeName, optionData) => { const [ command, ...prefixArgList ] = getDefaultOpenCommandList() return runSync({ command, argList: [ ...prefixArgList, uri.includes('://') ? uri : normalize(uri) ] }) } + case 'which': { + const commandNameOrPath = argumentList[ 0 ] + const resultCommand = await resolveCommandAsync(commandNameOrPath, root) + if (!resultCommand) throw new Error(`failed to resolve command: ${commandNameOrPath}`) + return logAuto(resultCommand) + } case 'status': return logAuto(isOutputJSON ? getSystemStatus() : describeSystemStatus()) @@ -175,8 +183,7 @@ const runMode = async (modeName, optionData) => { case 'server-serve-static': case 'server-serve-static-simple': { const [ expireTime = 5 * 1000 ] = argumentList // expireTime: 5sec, in msec - const staticRoot = tryGetFirst('root') || process.cwd() - return startServer(configureServerServeStatic, { isSimpleServe: modeName === 'server-serve-static-simple', expireTime: Number(expireTime), staticRoot }) + return startServer(configureServerServeStatic, { isSimpleServe: modeName === 'server-serve-static-simple', expireTime: Number(expireTime), staticRoot: root }) } case 'server-websocket-group': return startServer(configureServerWebSocketGroup) diff --git a/source-bin/option.js b/source-bin/option.js index e05e8500..b55fa2de 100644 --- a/source-bin/option.js +++ b/source-bin/option.js @@ -32,6 +32,7 @@ const MODE_FORMAT_LIST = parseList( 'status,s/T|basic system status: -J=isOutputJSON', 'open,o//0-1|use system default app to open uri or path: $0=uriOrPath/cwd', + 'which,w//1|resolve to full executable path: -R=resolveRoot/cwd, $0=commandNameOrPath', 'fetch,f//1-3|fetch "GET" uri: -O=outputFile/stdout, $@=initialUrl,jumpMax/4,timeout/0', 'process-status,ps//0-1|show system process status: -J=isOutputJSON, $0=outputMode/"pid--"', 'json-format,jf/AI/0-1|re-format JSON file: -O=outputFile/-I, -I=inputFile, $0=unfoldLevel/2', diff --git a/source/browser/net.test.js b/source/browser/net.test.js index e1eed31f..53e99a10 100644 --- a/source/browser/net.test.js +++ b/source/browser/net.test.js @@ -33,7 +33,6 @@ describe('Browser.Net', () => { () => { throw new Error('should throw time out error') }, expectError('NETWORK_TIMEOUT') ) - await fetchLikeRequest(`${baseUrl}/test-timeout`, { timeout: 80 }) // should pass await fetchLikeRequest(`${baseUrl}/test-timeout-payload`, { timeout: 80 }).then( (response) => response.arrayBuffer(), (error) => { throw new Error(`should not timeout: ${error}`) } @@ -44,6 +43,7 @@ describe('Browser.Net', () => { }, expectError('PAYLOAD_TIMEOUT') ) + await fetchLikeRequest(`${baseUrl}/test-timeout`, { timeout: 420 }) // should pass }) it('fetchLikeRequest(): arrayBuffer(), text(), json()', async () => { diff --git a/source/node/.sanity.test/memoryUsageDataType.test.js b/source/node/.sanity.test/memoryUsageDataType.test.js index 1c0ff860..e09fc98b 100644 --- a/source/node/.sanity.test/memoryUsageDataType.test.js +++ b/source/node/.sanity.test/memoryUsageDataType.test.js @@ -17,7 +17,7 @@ describe('Common.SanityTest.MemoryUsageDataType (very slow)', () => { [ 'string (dynamic)', '24±0.1', (index) => String(index) ], [ 'object (minimal)', '0±0.1', () => {} ], - [ 'object (dynamic)', '40±0.1', (index) => ({ index, some: 'value' }) ], + [ 'object (dynamic)', '40±0.1', (index) => ({ index, some: 'value' }) ] ] }))) @@ -33,11 +33,11 @@ describe('Common.SanityTest.MemoryUsageDataType (very slow)', () => { [ 'promise (Promise.resolve)', '0±0.1', () => Promise.resolve('value') ], [ 'promise (Promise.all with 1 value)', '64±0.1', () => Promise.all([ Promise.resolve(0) ]) ], [ 'promise (Promise.all with 2 value)', '72±0.1', () => Promise.all([ Promise.resolve(0), Promise.resolve(1) ]) ], - [ 'promise (Promise.all with 3 value)', '80±0.1', () => Promise.all([ Promise.resolve(0), Promise.resolve(1), Promise.resolve(2) ]) ], + [ 'promise (Promise.all with 3 value)', '80±0.1', () => Promise.all([ Promise.resolve(0), Promise.resolve(1), Promise.resolve(2) ]) ] ] }))) - it('function', createTestFunc(0, commonFunc, async (triggerGC, { formatMemory, markMemory, runSubjectPredictionTestConfig }) => runSubjectPredictionTestConfig({ + it('function', createTestFunc(0, commonFunc, async (triggerGC, { formatMemory, markMemory, runSubjectPredictionTestConfig, isNodejs14 = Number(process.versions.node.split('.')[ 0 ]) >= 14 }) => runSubjectPredictionTestConfig({ testConfigName: 'rough data size test', testKeepRound: 6, // suggest at least 4 testDropRound: 3, // first 2-4 result may be less stable @@ -59,21 +59,21 @@ describe('Common.SanityTest.MemoryUsageDataType (very slow)', () => { markMemory() // reference to outer func runSubjectPredictionTestConfig() // reference to outer func } ], - [ 'arrow function (closure with 1 object)', '144±0.1', (index) => { + [ 'arrow function (closure with 1 object)', isNodejs14 ? '128±0.1' : '144±0.1', (index) => { const objectA = { index } return () => { console.log('arrow function', objectA) } } ], - [ 'arrow function (closure with 1 arrow function)', '168±0.1', () => { + [ 'arrow function (closure with 1 arrow function)', isNodejs14 ? '152±0.1' : '168±0.1', () => { const funcA = (arg0, arg1) => { console.log('arrow function', arg0, arg1) } return () => { funcA('arrow function') } } ], - [ 'arrow function (closure with 3 value)', '296±0.1', () => { + [ 'arrow function (closure with 3 value)', isNodejs14 ? '280±0.1' : '296±0.1', () => { const funcA = (arg0, arg1) => { console.log('arrow function', arg0, arg1) } const funcB = (arg0, arg1) => { console.log('arrow function', arg0, arg1) } const funcC = (arg0, arg1) => { funcB('arrow function', funcA(arg0), arg1) } return () => { console.log('arrow function', funcC(funcA(funcB))) } } ], - [ 'arrow function (deeper closure with 3 value)', '296±0.1', () => { + [ 'arrow function (deeper closure with 3 value)', isNodejs14 ? '280±0.1' : '296±0.1', () => { const funcA = (arg0, arg1) => { console.log('arrow function', arg0, arg1) } const funcB = (arg0, arg1) => { console.log('arrow function', funcA(arg0, arg1)) } const funcC = (arg0, arg1) => { funcB('arrow function', arg0, arg1) } diff --git a/source/node/net.test.js b/source/node/net.test.js index 7d7d3d56..08dfaa7e 100644 --- a/source/node/net.test.js +++ b/source/node/net.test.js @@ -44,7 +44,6 @@ describe('Node.Net', () => { () => { throw new Error('should throw time out error') }, expectError('NETWORK_TIMEOUT') ) - await fetchLikeRequest(`${baseUrl}/test-timeout`, { timeout: 80 }) // should pass await fetchLikeRequest(`${baseUrl}/test-timeout-payload`, { timeout: 40 }).then( (response) => response.buffer(), (error) => { throw new Error(`should not timeout: ${error}`) } @@ -55,6 +54,7 @@ describe('Node.Net', () => { }, expectError('PAYLOAD_TIMEOUT') ) + await fetchLikeRequest(`${baseUrl}/test-timeout`, { timeout: 420 }) // should pass })) it('fetchLikeRequest(): stream(), buffer(), arrayBuffer(), text(), json()', withTestServer(async (baseUrl) => { diff --git a/source/node/server/Responder/Common.js b/source/node/server/Responder/Common.js index 777c114f..fb929a89 100644 --- a/source/node/server/Responder/Common.js +++ b/source/node/server/Responder/Common.js @@ -3,8 +3,7 @@ import { clock } from 'source/common/time' // TODO: add responderEndRandomErrorStatus? const responderEnd = (store) => { - // TODO: change to `writableEnded` since node@>=12.9, check: https://nodejs.org/dist/latest-v13.x/docs/api/deprecations.html#deprecations_dep0136_http_finished - if (store.response.finished) return // NOTE: normally this should be it, the request is handled and response ended + if (store.response.writableEnded) return // NOTE: normally this should be it, the request is handled and response ended const { error } = store.getState() !store.response.headersSent && store.response.writeHead(error ? 500 : 400) store.response.end() // force end the response to prevent pending diff --git a/source/node/server/Server.js b/source/node/server/Server.js index 65d0c7bb..075cf965 100644 --- a/source/node/server/Server.js +++ b/source/node/server/Server.js @@ -1,4 +1,5 @@ import { networkInterfaces } from 'os' +import { randomBytes } from 'crypto' import { createServer as createTCPServer } from 'net' import { createServer as createTLSServer } from 'tls' import { createServer as createHttpServer } from 'http' @@ -12,22 +13,29 @@ import { createCacheMap } from 'source/common/data/CacheMap' import { createStateStoreLite } from 'source/common/immutable/StateStore' import { responderEnd } from './Responder/Common' +const SESSION_CLIENT_TIMEOUT_SEC = 4 * 60 * 60 // in sec, 4hour const SESSION_CACHE_MAX = 4 * 1024 -const SESSION_EXPIRE_TIME = 10 * 60 * 1000 // in msec, 10min +const SESSION_CACHE_EXPIRE_TIME = 10 * 60 * 1000 // in msec, 10min +const SESSION_TICKET_ROTATE_TIME = 4 * 60 * 60 * 1000 // in msec, 4hour + const applyServerSessionCache = (server) => { // TODO: consider move to `ticketKeys`: https://nodejs.org/dist/latest-v12.x/docs/api/tls.html#tls_tls_createserver_options_secureconnectionlistener const sessionCacheMap = createCacheMap({ valueSizeSumMax: SESSION_CACHE_MAX, eventHub: null }) server.on('newSession', (sessionId, sessionData, next) => { - __DEV__ && console.log('newSession', sessionId.toString('hex')) - sessionCacheMap.set(sessionId.toString('hex'), sessionData, 1, Date.now() + SESSION_EXPIRE_TIME) + __DEV__ && console.log('newSession', sessionId.toString('base64')) + sessionCacheMap.set(sessionId.toString('base64'), sessionData, 1, Date.now() + SESSION_CACHE_EXPIRE_TIME) next() }) server.on('resumeSession', (sessionId, next) => { - __DEV__ && console.log('resumeSession', sessionId.toString('hex')) - next(null, sessionCacheMap.get(sessionId.toString('hex')) || null) + __DEV__ && console.log('resumeSession', sessionId.toString('base64')) + next(null, sessionCacheMap.get(sessionId.toString('base64')) || null) }) } -const createServerPack = ({ protocol, ...option }) => { +const createServerPack = ({ + protocol, + skipSessionPatch, // allow disable session patch (but session ticket without rotation will still work) + ...option +}) => { if (![ 'tcp:', 'tls:', 'http:', 'https:' ].includes(protocol)) throw new Error(`invalid protocol: ${protocol}`) const isSecure = [ 'tls:', 'https:' ].includes(protocol) option = { @@ -41,6 +49,9 @@ const createServerPack = ({ protocol, ...option }) => { // ca: 'BUFFER: CA.pem', // [optional] // SNICallback: (hostname, callback) => callback(null, secureContext), // [optional] // dhparam: 'BUFFER: DHPARAM.pem', // [optional] Diffie-Hellman Key Exchange, generate with `openssl dhparam -dsaparam -outform PEM -out output/path/dh4096.pem 4096` + // sessionTimeout: SESSION_CLIENT_TIMEOUT_SEC, // [optional] in seconds, for both session cache and ticket + // ticketKeys: 'BUFFER to reuse, 48byte', // [optional] check: https://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener // and: https://github.com/nodejs/node/issues/27167 + ...(isSecure && !skipSessionPatch && { sessionTimeout: SESSION_CLIENT_TIMEOUT_SEC }), ...option } option.baseUrl = `${protocol}//${option.hostname}:${option.port}` @@ -51,7 +62,7 @@ const createServerPack = ({ protocol, ...option }) => { : protocol === 'https:' ? createHttpsServer(option) : null - isSecure && applyServerSessionCache(server) + isSecure && !skipSessionPatch && applyServerSessionCache(server) const socketSet = new Set() server.on('connection', (socket) => { @@ -63,6 +74,8 @@ const createServerPack = ({ protocol, ...option }) => { }) }) + let sessionTicketRotateToken + return { // call this serverPack server, socketSet, @@ -71,10 +84,19 @@ const createServerPack = ({ protocol, ...option }) => { server.on('error', reject) server.listen(option.port, option.hostname.replace(/[[\]]/g, ''), () => { server.off('error', reject) + if (isSecure && !skipSessionPatch) { + // https://blog.filippo.io/we-need-to-talk-about-session-tickets/ + // https://timtaubert.de/blog/2017/02/the-future-of-session-resumption/ + const resetSessionTicketKey = () => server.setTicketKeys(randomBytes(48)) + sessionTicketRotateToken && clearInterval(sessionTicketRotateToken) + sessionTicketRotateToken = setInterval(resetSessionTicketKey, SESSION_TICKET_ROTATE_TIME) + resetSessionTicketKey() + } resolve() }) }), stop: async ({ isForce = false } = {}) => server.listening && new Promise((resolve) => { + sessionTicketRotateToken && clearInterval(sessionTicketRotateToken) server.close(() => resolve()) if (isForce) { for (const socket of socketSet) socket.destroy() } })