diff --git a/internal/documentation/docs/pages/Configuration.md b/internal/documentation/docs/pages/Configuration.md index 85e3884794f..824220088e2 100644 --- a/internal/documentation/docs/pages/Configuration.md +++ b/internal/documentation/docs/pages/Configuration.md @@ -655,7 +655,7 @@ server: ::: -By default, UI5 CLI will serve applications using Port `8080`. When running in HTTP/2 or HTTPS mode, Port `8443` will be used. +By default, UI5 CLI will serve applications using Port `8080`. When running in HTTPS mode, Port `8443` will be used. If the default port is already in use, the next highest free port will be used. diff --git a/internal/documentation/docs/pages/Overview.md b/internal/documentation/docs/pages/Overview.md index 1281b6c3227..6baa008bcea 100644 --- a/internal/documentation/docs/pages/Overview.md +++ b/internal/documentation/docs/pages/Overview.md @@ -112,11 +112,11 @@ By placing this file in the root directory of the `my.app` application project, The structure of the dependency definition file follows that of the [`@ui5/project/graph/providers/DependencyTree~TreeNode`](../api/@ui5_project_graph_providers_DependencyTree.html#~TreeNode) type. -## HTTP/2 Development Webserver -The UI5 CLI contains a web server to serve the project via HTTP/2 protocol. +## HTTPS Development Webserver +The UI5 CLI contains a web server to serve the project via HTTPS protocol. ```sh -ui5 serve --h2 +ui5 serve --https ``` This requires an SSL certificate. You are guided through the automatic generation process. Also see the [UI5 Server documentation](./Server.md#ssl-certificates) diff --git a/internal/documentation/docs/pages/Server.md b/internal/documentation/docs/pages/Server.md index f237fe39225..e8051e65d1d 100644 --- a/internal/documentation/docs/pages/Server.md +++ b/internal/documentation/docs/pages/Server.md @@ -228,7 +228,7 @@ A project graph can be served only once. Do not call both `serveMiddleware` and ::: ## SSL Certificates -When starting the UI5 Server in HTTPS- or HTTP/2 mode, for example by using UI5 CLI parameter `--h2`, you will be prompted for the automatic generation of a local SSL certificate if necessary. +When starting the UI5 Server in HTTPS mode, for example by using UI5 CLI parameter `--https`, you will be prompted for the automatic generation of a local SSL certificate if necessary. Follow the given instructions and enter your password to install the generated certificate as trusted. You can find the generated certificate and corresponding private key under `.ui5/server` in your user's home directory. diff --git a/internal/documentation/docs/pages/Troubleshooting.md b/internal/documentation/docs/pages/Troubleshooting.md index 8a8ed873630..738176d59dd 100644 --- a/internal/documentation/docs/pages/Troubleshooting.md +++ b/internal/documentation/docs/pages/Troubleshooting.md @@ -18,7 +18,7 @@ UI5 CLI stores several kinds of data under your user's home directory in `~/.ui5 | ---- | ---- | ---- | | `~/.ui5/framework/` | Downloaded UI5 framework dependencies (one copy per version) | Yes — re-downloaded on next invocation | | `~/.ui5/buildCache/` | Build cache used by `ui5 build` and `ui5 serve` (see [Build Cache Control](./Builder.md#build-cache-control)) | Yes — rebuilt on next `ui5 build` / `ui5 serve` | -| `~/.ui5/server/` | Locally generated SSL certificate and private key for HTTPS / HTTP/2 mode | Yes — regenerated on next HTTPS server start; the new certificate must be re-trusted | +| `~/.ui5/server/` | Locally generated SSL certificate and private key for HTTPS mode | Yes — regenerated on next HTTPS server start; the new certificate must be re-trusted | #### Resolution diff --git a/internal/documentation/docs/updates/migrate-v5.md b/internal/documentation/docs/updates/migrate-v5.md index b3fbe518ba3..db68d3542b8 100644 --- a/internal/documentation/docs/updates/migrate-v5.md +++ b/internal/documentation/docs/updates/migrate-v5.md @@ -356,6 +356,20 @@ The SAP Theme Designer can create custom themes based on CSS variables instead o With UI5 CLI v5, the `--experimental-css-variables` option has therefore been removed for the `ui5 build` command. For custom theme creation with CSS variables, please check out the **SAP Theme Designer**. +## Removal of HTTP/2 Support + +Support for the HTTP/2 protocol has been removed from the development server. The underlying `spdy` package is unmaintained and does not work with Node.js v24 and higher. HTTP/2 was rarely needed for local development, as browsers handle HTTP/1.1 well. + +The `--h2` option of the `ui5 serve` command has therefore been removed and replaced by the new `--https` option, which serves the project over HTTPS using Node.js' built-in HTTPS server: + +```sh +ui5 serve --https +``` + +Like the previous `--h2` option, `--https` requires an SSL certificate and guides you through the automatic generation process on first use. See the [UI5 Server documentation](../pages/Server.md#ssl-certificates) for details. + +If you genuinely need HTTP/2 for local development, put a reverse proxy (e.g. [nginx](https://nginx.org/)) in front of the UI5 Server and let it terminate HTTP/2 towards the browser (manual setup). + ## `sap-ui-version.json` When you run `ui5 build`, the standard task [`generateVersionInfo`](../api/module-@ui5_builder_tasks_generateVersionInfo) now runs by default. This task generates an `sap-ui-version.json` file in the `resources/` directory. The task runs for all build types (default, jsdoc, and self-contained) in projects of type `application`. For other project types (such as `library`), the behavior remains unchanged: [`generateVersionInfo`](../api/module-@ui5_builder_tasks_generateVersionInfo) does not run. diff --git a/package-lock.json b/package-lock.json index c4bf8c3a99c..14d18917ba0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7669,12 +7669,6 @@ "url": "https://github.com/sponsors/mesqueeb" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, "node_modules/correct-license-metadata": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/correct-license-metadata/-/correct-license-metadata-1.5.0.tgz", @@ -8251,12 +8245,6 @@ "node": ">=8" } }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "license": "MIT" - }, "node_modules/devcert-sanscache": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/devcert-sanscache/-/devcert-sanscache-0.5.1.tgz", @@ -10238,12 +10226,6 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "license": "MIT" - }, "node_modules/handlebars": { "version": "4.7.9", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", @@ -10451,54 +10433,6 @@ "node": "20 || >=22" } }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/html-entities": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", @@ -10558,12 +10492,6 @@ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause" }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "license": "MIT" - }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -13085,6 +13013,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -13097,6 +13026,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -13114,12 +13044,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "license": "ISC" - }, "node_modules/minimatch": { "version": "10.2.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", @@ -13917,12 +13841,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "license": "MIT" - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -15092,12 +15010,6 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, "node_modules/process-on-spawn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", @@ -15441,6 +15353,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -15923,12 +15836,6 @@ "license": "MIT", "peer": true }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "license": "MIT" - }, "node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -16574,36 +16481,6 @@ "spdx-ranges": "^2.0.0" } }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, "node_modules/speakingurl": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", @@ -16706,6 +16583,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -18130,15 +18008,6 @@ "node": "20 || >=22" } }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "license": "MIT", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -19046,7 +18915,6 @@ "parseurl": "^1.3.3", "portscanner": "^2.2.0", "router": "^2.2.0", - "spdy": "^4.0.2", "ws": "^8.21.1", "yesno": "^0.4.0" }, diff --git a/packages/cli/lib/cli/commands/serve.js b/packages/cli/lib/cli/commands/serve.js index 26f05d83cc4..ffc7fdb0cce 100644 --- a/packages/cli/lib/cli/commands/serve.js +++ b/packages/cli/lib/cli/commands/serve.js @@ -19,7 +19,7 @@ serve.builder = function(cli) { applyBuildOptions(cli); return cli .option("port", { - describe: "Port to bind on (default for HTTP: 8080, HTTP/2: 8443)", + describe: "Port to bind on (default for HTTP: 8080, HTTPS: 8443)", alias: "p", type: "number" }) @@ -30,8 +30,8 @@ serve.builder = function(cli) { alias: "o", type: "string" }) - .option("h2", { - describe: "Shortcut for enabling the HTTP/2 protocol for the web server", + .option("https", { + describe: "Enable the HTTPS protocol for the web server", default: false, type: "boolean" }) @@ -127,7 +127,7 @@ serve.builder = function(cli) { }) .coerce(["framework-version", "open", "port", "key", "cert"], dedupeArray) .example("ui5 serve", "Start a web server for the current project") - .example("ui5 serve --h2", "Enable the HTTP/2 protocol for the web server (requires SSL certificate)") + .example("ui5 serve --https", "Enable the HTTPS protocol for the web server (requires SSL certificate)") .example("ui5 serve --config /path/to/ui5.yaml", "Use the project configuration from a custom path") .example("ui5 serve --dependency-definition /path/to/projectDependencies.yaml", "Use a static dependency definition file") @@ -183,7 +183,7 @@ serve.handler = async function(argv) { if (!port && graph.getRoot().getServerSettings()) { const serverSettings = graph.getRoot().getServerSettings(); - if (argv.h2) { + if (argv.https) { port = serverSettings.httpsPort; } else { port = serverSettings.httpPort; @@ -192,7 +192,7 @@ serve.handler = async function(argv) { if (!port) { changePortIfInUse = true; // only change if port isn't explicitly set - if (argv.h2) { + if (argv.https) { port = 8443; } else { port = 8080; @@ -212,12 +212,12 @@ serve.handler = async function(argv) { const serverConfig = { port, changePortIfInUse, - h2: argv.h2, + https: argv.https, simpleIndex: !!argv.simpleIndex, liveReload: !!liveReload, acceptRemoteConnections: !!argv.acceptRemoteConnections, - cert: argv.h2 ? argv.cert : undefined, - key: argv.h2 ? argv.key : undefined, + cert: argv.https ? argv.cert : undefined, + key: argv.https ? argv.key : undefined, sendSAPTargetCSP: !!argv.sapCspPolicies, serveCSPReports: !!argv.serveCspReports, cache: argv.cache, @@ -230,7 +230,7 @@ serve.handler = async function(argv) { dependencyDefinitionPath: argv.dependencyDefinition, }; - if (serverConfig.h2) { + if (serverConfig.https) { const {getSslCertificate} = await import("@ui5/server/internal/sslUtil"); const {key, cert} = await getSslCertificate(serverConfig.key, serverConfig.cert); serverConfig.key = key; @@ -244,12 +244,12 @@ serve.handler = async function(argv) { const projectWatcher = await import("@ui5/project/internal/graph/ProjectDefinitionWatcher"); // Pass buildGraph as the graphFactory so the server can re-resolve the graph and re-create // the serving stack when its definition watcher observes a project-definition change. - const {h2, port: actualPort} = await serverServe(graph, serverConfig, function(err) { + const {https, port: actualPort} = await serverServe(graph, serverConfig, function(err) { reject(err); }, buildGraph, projectWatcher); if (argv.open !== undefined) { - const protocol = h2 ? "https" : "http"; + const protocol = https ? "https" : "http"; let browserUrl = protocol + "://localhost:" + actualPort; if (typeof argv.open === "string") { let relPath = argv.open || "/"; diff --git a/packages/cli/test/lib/cli/commands/serve.js b/packages/cli/test/lib/cli/commands/serve.js index c7bde0f9a49..b4506c4b6b8 100644 --- a/packages/cli/test/lib/cli/commands/serve.js +++ b/packages/cli/test/lib/cli/commands/serve.js @@ -13,7 +13,7 @@ function getDefaultArgv() { "logLevel": "info", "perf": false, "silent": false, - "h2": false, + "https": false, "simple-index": false, "simpleIndex": false, "accept-remote-connections": false, @@ -46,7 +46,7 @@ test.beforeEach(async (t) => { t.context.serverErrorCallback = errorCallback; t.context.handlerReadyResolvers.resolve(); return { - h2: false, + https: false, port: 8080 }; }) @@ -134,7 +134,7 @@ test.serial("ui5 serve: default", async (t) => { cache: undefined, cert: undefined, changePortIfInUse: true, - h2: false, + https: false, key: undefined, port: 8080, sendSAPTargetCSP: false, @@ -150,7 +150,7 @@ test.serial("ui5 serve: default", async (t) => { t.is(typeof server.serve.getCall(0).args[2], "function"); }); -test.serial("ui5 serve --h2", async (t) => { +test.serial("ui5 serve --https", async (t) => { const {argv, serve, graph, server, fakeGraph, sslUtil} = t.context; sslUtil.getSslCertificate.resolves({ @@ -161,10 +161,10 @@ test.serial("ui5 serve --h2", async (t) => { server.serve.callsFake((graph, config, errorCallback, graphFactory) => { t.context.serverErrorCallback = errorCallback; t.context.handlerReadyResolvers.resolve(); - return {h2: true, port: 8443}; + return {https: true, port: 8443}; }); - argv.h2 = true; + argv.https = true; serve.handler(argv); await t.context.handlerReady; @@ -178,7 +178,7 @@ test.serial("ui5 serve --h2", async (t) => { acceptRemoteConnections: false, cache: undefined, changePortIfInUse: true, - h2: true, + https: true, key: "random-key", cert: "random-cert", port: 8443, @@ -215,7 +215,7 @@ test.serial("ui5 serve --accept-remote-connections", async (t) => { cache: undefined, cert: undefined, changePortIfInUse: true, - h2: false, + https: false, key: undefined, port: 8080, sendSAPTargetCSP: false, @@ -505,7 +505,7 @@ test.serial("ui5 serve with ui5.yaml port setting", async (t) => { server.serve.callsFake((graph, config, errorCallback, graphFactory) => { t.context.serverErrorCallback = errorCallback; t.context.handlerReadyResolvers.resolve(); - return {h2: false, port: 3333}; + return {https: false, port: 3333}; }); serve.handler(argv); @@ -516,7 +516,7 @@ test.serial("ui5 serve with ui5.yaml port setting", async (t) => { t.is(server.serve.getCall(0).args[1].changePortIfInUse, false); }); -test.serial("ui5 serve --h2 with ui5.yaml port setting", async (t) => { +test.serial("ui5 serve --https with ui5.yaml port setting", async (t) => { const {argv, serve, server, sslUtil, getServerSettings} = t.context; sslUtil.getSslCertificate.resolves({ @@ -531,10 +531,10 @@ test.serial("ui5 serve --h2 with ui5.yaml port setting", async (t) => { server.serve.callsFake((graph, config, errorCallback, graphFactory) => { t.context.serverErrorCallback = errorCallback; t.context.handlerReadyResolvers.resolve(); - return {h2: true, port: 4444}; + return {https: true, port: 4444}; }); - argv.h2 = true; + argv.https = true; serve.handler(argv); await t.context.handlerReady; @@ -542,10 +542,10 @@ test.serial("ui5 serve --h2 with ui5.yaml port setting", async (t) => { t.is(server.serve.callCount, 1); t.is(server.serve.getCall(0).args[1].port, 4444); t.is(server.serve.getCall(0).args[1].changePortIfInUse, false); - t.is(server.serve.getCall(0).args[1].h2, true); + t.is(server.serve.getCall(0).args[1].https, true); }); -test.serial("ui5 serve --h2 with ui5.yaml port setting and port CLI argument", async (t) => { +test.serial("ui5 serve --https with ui5.yaml port setting and port CLI argument", async (t) => { const {argv, serve, server, sslUtil, getServerSettings} = t.context; sslUtil.getSslCertificate.resolves({ @@ -560,10 +560,10 @@ test.serial("ui5 serve --h2 with ui5.yaml port setting and port CLI argument", a server.serve.callsFake((graph, config, errorCallback, graphFactory) => { t.context.serverErrorCallback = errorCallback; t.context.handlerReadyResolvers.resolve(); - return {h2: true, port: 5555}; + return {https: true, port: 5555}; }); - argv.h2 = true; + argv.https = true; argv.port = 5555; serve.handler(argv); diff --git a/packages/server/lib/serve/Supervisor.js b/packages/server/lib/serve/Supervisor.js index af4834aa33a..a366d0711c0 100644 --- a/packages/server/lib/serve/Supervisor.js +++ b/packages/server/lib/serve/Supervisor.js @@ -208,19 +208,10 @@ class Supervisor extends EventEmitter { async #init(graph) { const { - port: requestedPort, changePortIfInUse = false, h2 = false, key, cert, + port: requestedPort, changePortIfInUse = false, https = false, key, cert, acceptRemoteConnections = false, liveReload = false, } = this.#config; - if (h2) { - const nodeVersion = parseInt(process.versions.node.split(".")[0], 10); - if (nodeVersion >= 24) { - log.error("ERROR: With Node v24, usage of HTTP/2 is no longer supported. " + - "Please check https://github.com/UI5/cli/issues/327 for updates."); - process.exit(1); - } - } - // Build the initial stack before binding so a construction failure surfaces to the caller. this.#stack = await buildApp(graph, this.#config, this.#error, this.#getDegradedError); this.#currentGraph = graph; @@ -235,8 +226,8 @@ class Supervisor extends EventEmitter { // delegate to it before rethrowing. create() rethrows without handing the instance out, so // the move to DESTROYED is not observable. try { - const listenTarget = h2 ? - await addSsl({app: dispatcher, key, cert}) : + const listenTarget = https ? + addSsl({app: dispatcher, key, cert}) : http.createServer(dispatcher); const {port, server} = await listen(listenTarget, requestedPort, changePortIfInUse, acceptRemoteConnections); @@ -258,7 +249,7 @@ class Supervisor extends EventEmitter { // stack is live. Only meaningful with a graphFactory (no factory means reinitialize is a no-op). await this.#startDefinitionWatcher(graph); - announceListening({port, h2, acceptRemoteConnections}); + announceListening({port, https, acceptRemoteConnections}); } catch (err) { await this.destroy(); throw err; diff --git a/packages/server/lib/serve/httpListener.js b/packages/server/lib/serve/httpListener.js index 03b264086b6..98f06d33988 100644 --- a/packages/server/lib/serve/httpListener.js +++ b/packages/server/lib/serve/httpListener.js @@ -1,10 +1,10 @@ import os from "node:os"; +import https from "node:https"; import portscanner from "portscanner"; /** - * HTTP-listener helpers shared between the single-shot {@link module:@ui5/server.serve} - * wrapper and the {@link Supervisor}, which binds the port once and swaps the - * request handler behind it. + * HTTP-listener helpers used by the {@link Supervisor}, which binds the port once and + * swaps the request handler behind it. * * @private * @module @ui5/server/serve/httpListener @@ -13,7 +13,7 @@ import portscanner from "portscanner"; /** * Binds an HTTP/HTTPS server to a free port and resolves once it is listening. * - * @param {object} app The express application (or spdy server) to listen with + * @param {object} app The http/https server to listen with * @param {number} port Desired port to listen to * @param {boolean} changePortIfInUse If true and the port is already in use, an unused port is searched * @param {boolean} acceptRemoteConnections If true, listens to remote connections and not only to localhost @@ -63,20 +63,17 @@ export function listen(app, port, changePortIfInUse, acceptRemoteConnections) { } /** - * Adds SSL support to an express application. + * Wraps a request handler in an HTTPS server. * * @param {object} parameters - * @param {object} parameters.app The original express application + * @param {Function} parameters.app The request handler to serve over HTTPS * @param {string} parameters.key Path to private key to be used for https * @param {string} parameters.cert Path to certificate to be used for for https - * @returns {Promise} The express application with SSL support + * @returns {object} The https server * @private */ -export async function addSsl({app, key, cert}) { - // Using spdy as http2 server as the native http2 implementation - // from Node v8.4.0 doesn't seem to work with express - const {default: spdy} = await import("spdy"); - return spdy.createServer({cert, key}, app); +export function addSsl({app, key, cert}) { + return https.createServer({key, cert}, app); } /** @@ -87,12 +84,12 @@ export async function addSsl({app, key, cert}) { * * @param {object} parameters * @param {number} parameters.port The actual bound port - * @param {boolean} parameters.h2 Whether HTTP/2 (https) is in use + * @param {boolean} parameters.https Whether HTTPS is in use * @param {boolean} parameters.acceptRemoteConnections Whether the server binds to all interfaces * @private */ -export function announceListening({port, h2, acceptRemoteConnections}) { - const protocol = h2 ? "https" : "http"; +export function announceListening({port, https, acceptRemoteConnections}) { + const protocol = https ? "https" : "http"; const urls = [{label: "Local", url: `${protocol}://localhost:${port}`}]; if (acceptRemoteConnections) { for (const addr of findNetworkInterfaceAddresses()) { diff --git a/packages/server/lib/server.js b/packages/server/lib/server.js index 6221a6789c6..c5ac3e9e118 100644 --- a/packages/server/lib/server.js +++ b/packages/server/lib/server.js @@ -43,7 +43,7 @@ const log = getLogger("server"); * @public * @typedef {object} module:@ui5/server~ServerInstance * @property {number} port Port the server is listening on - * @property {boolean} h2 Whether HTTP/2 is used + * @property {boolean} https Whether HTTPS is used * @property {module:@ui5/server~closeServer} close Stops the server * @property {Function} reinitialize Re-creates the serving stack. Returns a Promise * that resolves once the new stack is in place. A no-op when no @@ -59,7 +59,7 @@ const log = getLogger("server"); * @param {object} options Options * @param {number} options.port Port to listen to * @param {boolean} [options.changePortIfInUse=false] If true, change the port if it is already in use - * @param {boolean} [options.h2=false] Whether HTTP/2 should be used - defaults to http + * @param {boolean} [options.https=false] Whether HTTPS should be used - defaults to http * @param {string} [options.key] Path to private key to be used for https * @param {string} [options.cert] Path to certificate to be used for for https * @param {boolean} [options.simpleIndex=false] Use a simplified view for the server directory listing @@ -100,7 +100,7 @@ const log = getLogger("server"); * @returns {Promise} Promise resolving once the server is listening */ export async function serve(graph, { - port, changePortIfInUse = false, h2 = false, key, cert, + port, changePortIfInUse = false, https = false, key, cert, acceptRemoteConnections = false, sendSAPTargetCSP = false, simpleIndex = false, liveReload = false, serveCSPReports = false, cache = "Default", ui5DataDir, includedTasks, excludedTasks, @@ -116,7 +116,7 @@ export async function serve(graph, { null; const config = { - port, changePortIfInUse, h2, key, cert, + port, changePortIfInUse, https, key, cert, acceptRemoteConnections, sendSAPTargetCSP, simpleIndex, liveReload, serveCSPReports, cache, ui5DataDir, includedTasks, excludedTasks, webSocketToken, @@ -132,7 +132,7 @@ export async function serve(graph, { } return { - h2, + https, port: supervisor.getPort(), close: function(callback) { const p = supervisor.destroy(); diff --git a/packages/server/package.json b/packages/server/package.json index f9f301917ce..6d86d747a3f 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -101,7 +101,6 @@ "parseurl": "^1.3.3", "portscanner": "^2.2.0", "router": "^2.2.0", - "spdy": "^4.0.2", "ws": "^8.21.1", "yesno": "^0.4.0" }, diff --git a/packages/server/test/fixtures/ssl/server.crt b/packages/server/test/fixtures/ssl/server.crt index 298be6f38f1..e1b90193233 100644 --- a/packages/server/test/fixtures/ssl/server.crt +++ b/packages/server/test/fixtures/ssl/server.crt @@ -1,24 +1,21 @@ -----BEGIN CERTIFICATE----- -MIID9jCCAt6gAwIBAgIGAOmMgjJXMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNVBAMM -C3VpNS10b29saW5nMB4XDTE4MTIxMTE1MjI1MloXDTM4MDIwOTE1MjI1MlowFjEU -MBIGA1UEAwwLdWk1LXRvb2xpbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQDFOuJcTCUN6aiujxssnm2WApmcfI12Zj170RpTcyN+Z1tRaQN7jttJfoqq -6q03A8XYATJnBVQV6gSeoNr4VW/PGW3im0mleKOoJ4gEQ+rIBLVrU1WKCIXIvEQS -TI7il9WiHR5ODf+rZiD81on7yZu+hj/BdYZzNtdngf3wW/cjDxb+7v39CLxtOUtH -kLx9nYDoRV1lLUTx4KSsuYisBadTXeUnGtR3e5F1VEGmi7MN1v9+HAgiyS4GWPLz -fjdloixQIFUPvJtQxqBH20sh9S3DXxRbwpO3jLrhhtg5inLY+9WqBeq5Mp3XybAg -ilMLTen39EMV+kJEl+qZGoGe1weHAgMBAAGjggFIMIIBRDAJBgNVHRMEAjAAMBEG -CWCGSAGG+EIBAQQEAwIGQDAtBglghkgBhvhCAQ0EIBYedWk1LXRvb2xpbmcgSXNz -dWVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQpj6CbcGFdVzQh+wlg8fp9be/IKjBG -BgNVHSMEPzA9gBTbSBVYMFIzeYdIQIBWFmX3bNfA2qEapBgwFjEUMBIGA1UEAwwL -dWk1LXRvb2xpbmeCCQD6+/u8+n/XCjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAww -CgYIKwYBBQUHAwEwaQYDVR0RBGIwYIILdWk1LXRvb2xpbmeCCWxvY2FsaG9zdIIV -bG9jYWxob3N0LmxvY2FsZG9tYWluggZsdmgubWWCCCoubHZoLm1lggVbOjoxXYcE -fwAAAYcQ/oAAAAAAAAAAAAAAAAAAATANBgkqhkiG9w0BAQsFAAOCAQEATrvrHNnV -JHcVJhlmL2JyfsDT8W476v0JHrciAe86RJBbw1rmY/CO2EA+QN/4pxI57h+JavDV -aRvrcqga2hwcl93MHMQIpw+OyHYbHM/J4cwYNuHfspN4IrdcvkgrwKQOOtSDhxzG -6c2A/ZLO+gMMIUgGaXOVzZMNvkceF6pJ4Fi/m+r9LBT0Wz2BRNOyuALWrfjgCSWS -P+12PUlSA2IqoMBKkFMKlWKWJRxcVjjM0wEzNbg1wFwv/ieQu9vIgttDZSu5OflW -A6F0PpA4SMFOkcmIxvd0g5q1gGvhfWtkzlUTwhaKvVeHlOO6mRiKL2SZjzb3WPDU -/udvKL5MYbv8Ew== +MIIDcjCCAlqgAwIBAgIUK3FW6LPeGohNfmjlqXp11Hf3RYMwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDgyMTA4MTkyOVoXDTQ2MDgx +NjA4MTkyOVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAonGHBtcx82xF1b60GOftBTp8vjT4SoA87fd1uEcJ9C+v +K3M1J4/n6qlDf5DVT8aQ9ayuU0pltZ0VHoGkL86hQ+uMy6Zbyauf0g1Pjpyqny0n +/x5iL5CrQ6Z5dk16lhydhEOgm5n67RF/l2AbvDB3Z2BNAvzRbP3bx6iomygMrdl0 +FJBptfZZLx+Dh8iw5dx5UyPkF4QdE7TWtR6ryrgdfEgVgbI8ANqh+Njm/iriUtD9 +5AobNfCCAhJU1yFcAZ0hq8bRLjN8MKlypowrHZdwMgfQkgrQ/YW3fkD2ajwmqKZf +MLwwjdam8LhTrgmXeb+Q9NW5fwV1uA8ni5JejjUQuwIDAQABo4G7MIG4MB0GA1Ud +DgQWBBTTO34G2V0ALSZKw+TJQ2xy/ZIrTzAfBgNVHSMEGDAWgBTTO34G2V0ALSZK +w+TJQ2xy/ZIrTzBDBgNVHREEPDA6gglsb2NhbGhvc3SCFWxvY2FsaG9zdC5sb2Nh +bGRvbWFpbocEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAMBgNVHRMBAf8EAjAAMA4G +A1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQsF +AAOCAQEAf93j8qvftlM26t8LcjmiVjgPo7hcplZ6tAetemLx3rmBhOxWIduo5Y+d +Fk5TFEfYs8TWIjdK48ZwXd1dzQLQUduC5gAgLUvQLYbYc2pqOTcA3+ktmsKrg8SA +eGs1KDJmOt2RKbYgaYapB+HyAN7trRpwuWbaG6EkD4NubhnLLwQTZlXv0Ukg33E8 +E7ynbDd4beNVpdIbZUHxqx64FKjjstbN21z4h90bq3XRGLKsrh1m0YpAgVch5dlC +V3emSKBnzxogSwDILOatN28R+cHDZ4zqxCRbvv9Ouhw02s2ci3/aAv9LyncssILS +oFekZyx3m3cYe0uVolcKIorSpr3x0w== -----END CERTIFICATE----- diff --git a/packages/server/test/fixtures/ssl/server.key b/packages/server/test/fixtures/ssl/server.key index 137f48d1804..4170d927f20 100644 --- a/packages/server/test/fixtures/ssl/server.key +++ b/packages/server/test/fixtures/ssl/server.key @@ -1,27 +1,28 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAxTriXEwlDemoro8bLJ5tlgKZnHyNdmY9e9EaU3MjfmdbUWkD -e47bSX6KquqtNwPF2AEyZwVUFeoEnqDa+FVvzxlt4ptJpXijqCeIBEPqyAS1a1NV -igiFyLxEEkyO4pfVoh0eTg3/q2Yg/NaJ+8mbvoY/wXWGczbXZ4H98Fv3Iw8W/u79 -/Qi8bTlLR5C8fZ2A6EVdZS1E8eCkrLmIrAWnU13lJxrUd3uRdVRBpouzDdb/fhwI -IskuBljy8343ZaIsUCBVD7ybUMagR9tLIfUtw18UW8KTt4y64YbYOYpy2PvVqgXq -uTKd18mwIIpTC03p9/RDFfpCRJfqmRqBntcHhwIDAQABAoIBAQCM8PxruppMK+q3 -+1oO85QaO/dr5XHt5zgOsCng8omtvcx7C9FKTZcVGRGc/CEWDGMobgqHpj4lftsY -RJjWF6/TzjdBVXk2f1RsSPuhGKvBjePbM20QO/DmturqLL163cfzll1eqBBJeq0e -IW3veW4Lo8IRzJts4AbkV/eQ+RdNfCzmo0zgFFmAQbYaUy7wujpTHpS85gkbUVH0 -rQY5DCPdRQg9Zlo48jFQxvrYRgzkm5ekz0SJXUDuU7M3+Dy8fgWiRMVH1MY4SSeW -teaH9pfoU5QSaBgG+NVJ/UXKtYMJy3vpPD5rKQ8kvc8Zozg4nJtYcJSXcwI15pVk -/kqvdPf5AoGBAOMU3gaFMcpM2TTylt1J5CziinDNGzir9+2z5Dihedc/YLiHUL8A -I95lfgkH2y2qmcWFn4aEFnkh0VJgOEin0siAIGSP7uc6zFPbgx4DwAeyCc+7MAZ3 -wQYbOh8I4vS7rkfvv/k+KNKsmJA/Y/cQ8y8GyciWAbAOV5u8aq9r2+JzAoGBAN5Y -0mtUCkEPnV3/3uk2EWqOyRaF2lBwxdxNCQHidzuhIKtS1ohTkyNIo+gVoqLK2VMA -+G3qiadShJGSgWKKINM6pjEeI1kcbQN3Vv6E5UKsRU9aINR1Gmm5ZtvQMgvsWDKv -AafSKXJove4XMrxQ017FLJJotXmaeywax4kNKX2dAoGABzM8Lvy+LWnuYS1n9zBG -md29AgKe4Fcjj1fq3pjQIjESQgeYWdYJTR3utx6WpoYIRikMe9loVQLrfi8q2bLo -jg7x9/5QVuiYXfhR2fvIdPYCNaH7zdiyOB1Y5G8FszjYN9O0RJTh+nwagkhgRQ82 -hsEyWLfY6doqkPeScml7GV0CgYEA25hxyRqpPfSEAZBxwXZq4QjARYRRR1z5rUhP -+xKFm9jZARvBUcbo7NITP2YOyCT7WmT89uHKrfxrfbJqIr41CSlFdgFD/AB9Eg1N -V/Y8zwqBMYvcHLjDK2O22KXYp7n2qaf6RJrl4F7eJkW3EBHrkA/Ao6DSipUtzTNt -mkfrkGkCgYAnUKTytfD1FcLAUdDowydVFZLQCprpcKkkMYOjgXNSSVotMKGc7yxs -zEszqONKW0dD109Y5KHO0uDcivP5JYdtR86hqVZoq/9V6XTR9A+yUe8NlGWZtODe -LkMDI0ZEARoSKEdTa/2UrZJODituiNh+heXX/AhCE2f56cv9Ql/yDQ== ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCicYcG1zHzbEXV +vrQY5+0FOny+NPhKgDzt93W4Rwn0L68rczUnj+fqqUN/kNVPxpD1rK5TSmW1nRUe +gaQvzqFD64zLplvJq5/SDU+OnKqfLSf/HmIvkKtDpnl2TXqWHJ2EQ6CbmfrtEX+X +YBu8MHdnYE0C/NFs/dvHqKibKAyt2XQUkGm19lkvH4OHyLDl3HlTI+QXhB0TtNa1 +HqvKuB18SBWBsjwA2qH42Ob+KuJS0P3kChs18IICElTXIVwBnSGrxtEuM3wwqXKm +jCsdl3AyB9CSCtD9hbd+QPZqPCaopl8wvDCN1qbwuFOuCZd5v5D01bl/BXW4DyeL +kl6ONRC7AgMBAAECggEACWMyKxezOp6z3R8A3WeZxPGU/+ZOavpvivkAA+mRuQYr +GKxhvVWNpiaope3tcIdzHR92O439xA8TMgjKlOYGCnIfAtiBpBfs9oCaa7gWDCOE +lN5hTN+5ZgFwMiwBZ5IOcEmlixh3mHeoaKEUjpLmc5eXG73B2eCZxjddobR2Ichr +Ce5zMRU9SoXyLsReuuwrL64ktLFXwutaMugU9b0CvcT8C2D1u5KsdjI8GiJe/abp +VKwwXKH+iSG/6ZCwgobZhvmUR586Ce7QN16ZRAcF/aymNOLByxDnEmfl4lv/Eyja +S3Oy3DX8xxIXwsP4f6xFPclWCkppE7SVtCmpibT5YQKBgQDQ85terrdhbjo8VXyV +65up6aDYUsFvzoZdwwvbvX8P48rH3GBQmAfq7bI9PIf60APwOjUo451axG9yNFxj +jehX1QZdt5SpvXmjD1oYgfBGtXBkJqctajPh0Au549ask2E7C9gbh/zOFCuoEw0e +DIxZwu8gWWPWGpyWunERvxwWowKBgQDHBRkbdm5ZJkrvuh+Sk363/WhAtJ/ysZ3h +p0AzHHS6fiV9NQvoTyezVa6vxRL9I65BG5odeyCatHJfXrq8e1Hq7TVNqjSK5wem +b6pvzMvuivfdg0CUWzcDYCOkd30gvHTxvl/NeECc5uZsdbzZy1QjuV1Zf7nR2hRV +vAnjRSv3CQKBgQCCNCrO90RiPBOh4Qs/SI+5c1G9OuQ6zZDBAHd1OEHjHukPXW3P +Cs10QSXeLsf1LIYnvb2+ewdkth9UJPilOJpdkOrkny7ODrmrODfGKcIV7Z3ZapSm +IhWuUrvb2/O7miLublT94uFPcRY7J5P2847JqETl/Y3OrsqAyjEHA/V2zQKBgAXo +1tgD6j7nbi40Hp/hlU+tTJ0vP9ibAY8jwjpCTA0FleNj4diiERG4J5m8hlXR62iv +gyIA9/TgCs1eKLlUCBirtnt55FmDhqyggDUVpbCNm9KOBSrikn2dKzHFAogoUGil +OH8HZRPDn2/1BwiIixUMegMgUcozaYsqLDj9YtLRAoGBAMSLw24+KQ+RVEnnyyft +lK3GwdEiFQaf6Ir8vc2jwrReya/2EHVc17gWZmw90l/M8crfw24mUYeTXotlcxDo +WSPQ1PR70mtmtr6eLKM7kTMUlgmpEMNxKw2tEmMiuH3fwA4VpSJ+hyk3NDVl7tcR +G3dC9DHKwCZH8H5S3i8mhakK +-----END PRIVATE KEY----- diff --git a/packages/server/test/lib/server/h2.js b/packages/server/test/lib/server/h2.js deleted file mode 100644 index 6a7510e6616..00000000000 --- a/packages/server/test/lib/server/h2.js +++ /dev/null @@ -1,66 +0,0 @@ -import test from "ava"; -import supertest from "supertest"; -import {serve} from "../../../lib/server.js"; -import {getSslCertificate} from "../../../lib/sslUtil.js"; -import {graphFromPackageDependencies} from "@ui5/project/graph"; -import {isolatedUi5DataDir} from "../../utils/buildCacheIsolation.js"; -import path from "node:path"; - -let request; -let server; - -const nodeVersion = parseInt(process.versions.node.split(".")[0], 10); - -// Withe Node.js 24 and later, the HTTP parser is missing, which breaks the HTTP/2 support in the spdy package. -// Tests need to be NodeJs version agnostic. -if (nodeVersion < 24) { - // Start server before running tests - test.before(async (t) => { - process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0"; - - const graph = await graphFromPackageDependencies({ - cwd: "./test/fixtures/application.a" - }); - const sslPath = path.join(process.cwd(), "./test/fixtures/ssl/"); - const {key, cert} = await getSslCertificate( - path.join(sslPath, "server.key"), - path.join(sslPath, "server.crt"), - ); - server = await serve(graph, { - port: 3366, - h2: true, - key, - cert, - ui5DataDir: isolatedUi5DataDir(t), - }); - request = supertest("https://localhost:3366"); - }); - - test.after(() => { - return new Promise((resolve, reject) => { - server.close((error) => { - if (error) { - reject(error); - } else { - resolve(); - } - }); - }); - }); - - test("Get resource from application.a (/index.html)", async (t) => { - const res = await request.get("/index.html"); - if (res.error) { - t.fail(res.error.text); - } - t.is(res.statusCode, 200, "Correct HTTP status code"); - t.regex(res.headers["content-type"], /html/, "Correct content type"); - t.regex(res.text, /Application A<\/title>/, "Correct response"); - }); -} else { - test("HTTP Parser is missing", async (t) => { - await t.throwsAsync(async () => { - await import("spdy"); - }); - }); -} diff --git a/packages/server/test/lib/server/https.js b/packages/server/test/lib/server/https.js new file mode 100644 index 00000000000..ec664b1d113 --- /dev/null +++ b/packages/server/test/lib/server/https.js @@ -0,0 +1,53 @@ +import test from "ava"; +import supertest from "supertest"; +import {serve} from "../../../lib/server.js"; +import {getSslCertificate} from "../../../lib/sslUtil.js"; +import {graphFromPackageDependencies} from "@ui5/project/graph"; +import {isolatedUi5DataDir} from "../../utils/buildCacheIsolation.js"; +import path from "node:path"; + +let request; +let server; + +// Start server before running tests +test.before(async (t) => { + const graph = await graphFromPackageDependencies({ + cwd: "./test/fixtures/application.a" + }); + const sslPath = path.join(process.cwd(), "./test/fixtures/ssl/"); + const {key, cert} = await getSslCertificate( + path.join(sslPath, "server.key"), + path.join(sslPath, "server.crt"), + ); + server = await serve(graph, { + port: 3366, + https: true, + key, + cert, + ui5DataDir: isolatedUi5DataDir(t), + }); + // Trust the server's self-signed certificate for all requests made through this agent + request = supertest.agent("https://localhost:3366").ca(cert); +}); + +test.after(() => { + return new Promise((resolve, reject) => { + server.close((error) => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); +}); + +test("Get resource from application.a (/index.html)", async (t) => { + const res = await request.get("/index.html"); + if (res.error) { + t.fail(res.error.text); + } + t.is(res.statusCode, 200, "Correct HTTP status code"); + t.regex(res.headers["content-type"], /html/, "Correct content type"); + t.regex(res.text, /<title>Application A<\/title>/, "Correct response"); +}); diff --git a/packages/server/test/lib/server/serve/Supervisor.js b/packages/server/test/lib/server/serve/Supervisor.js index 55b9dbe70d9..b4f519d0cb2 100644 --- a/packages/server/test/lib/server/serve/Supervisor.js +++ b/packages/server/test/lib/server/serve/Supervisor.js @@ -25,7 +25,7 @@ function createMocks({stacks, buildAppImpl, definitionWatcherCreate} = {}) { const createdHandlers = []; const listen = sinon.stub().resolves({port: 3000, server: httpServer}); - const addSsl = sinon.stub().callsFake(async ({app}) => app); + const addSsl = sinon.stub().callsFake(({app}) => app); const announceListening = sinon.stub(); const liveReloadHandle = {close: sinon.stub()}; diff --git a/packages/server/test/lib/server/server.js b/packages/server/test/lib/server/server.js index 8eb49b5f505..9e2b3556e80 100644 --- a/packages/server/test/lib/server/server.js +++ b/packages/server/test/lib/server/server.js @@ -3,7 +3,7 @@ import sinon from "sinon"; import esmock from "esmock"; // server.js is now a thin wrapper over Supervisor: it generates the live-reload token, builds -// the config, delegates to Supervisor.create(), and shapes the {h2, port, close, reinitialize} +// the config, delegates to Supervisor.create(), and shapes the {https, port, close, reinitialize} // result. These tests exercise that wrapper; the swap/relay/dispatcher behavior lives in // serve/Supervisor.js and is covered there. @@ -32,14 +32,14 @@ test.afterEach.always(() => { sinon.restore(); }); -test("serve() delegates to Supervisor.create and returns port/h2/close/reinitialize", async (t) => { +test("serve() delegates to Supervisor.create and returns port/https/close/reinitialize", async (t) => { const {supervisor, Supervisor} = createSupervisorMock({port: 3000}); const {serve} = await importServe(Supervisor); const graph = {}; const graphFactory = sinon.stub(); const projectWatcher = {default: {}, RecoveryBudget: class {}}; - const result = await serve(graph, {port: 3000, h2: false, liveReload: true}, undefined, graphFactory, + const result = await serve(graph, {port: 3000, https: false, liveReload: true}, undefined, graphFactory, projectWatcher); t.true(Supervisor.create.calledOnce); @@ -50,7 +50,7 @@ test("serve() delegates to Supervisor.create and returns port/h2/close/reinitial t.is(typeof config.webSocketToken, "string", "a token is generated when liveReload is active"); t.is(config.webSocketToken.length, 12, "the token is 72 bits base64url-encoded to 12 characters"); t.is(result.port, 3000); - t.is(result.h2, false); + t.is(result.https, false); t.is(typeof result.close, "function"); t.is(typeof result.reinitialize, "function");