From a5d4735b804b30da1aeccdacf09c0dd05a359d3c Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Wed, 13 Dec 2023 14:35:52 +0000 Subject: [PATCH] fix(@angular-devkit/build-angular): construct SSR request URL using server resolvedUrls With vite `header.host` is undefined when SSL is enabled. This resulted in an invalid URL to be constructed. Closes #26652 --- .../src/builders/dev-server/vite-server.ts | 4 +- tests/legacy-cli/e2e.bzl | 4 + .../serve/ssr-http-requests-assets.ts | 80 +++++++++++++++++++ 3 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 tests/legacy-cli/e2e/tests/commands/serve/ssr-http-requests-assets.ts diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts index fa414729be75..0362d2f03a36 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/vite-server.ts @@ -679,11 +679,9 @@ export async function setupServer( } transformIndexHtmlAndAddHeaders(url, rawHtml, res, next, async (html) => { - const protocol = serverOptions.ssl ? 'https' : 'http'; - const route = `${protocol}://${req.headers.host}${req.originalUrl}`; const { content } = await renderPage({ document: html, - route, + route: new URL(req.originalUrl ?? '/', server.resolvedUrls?.local[0]).toString(), serverContext: 'ssr', loadBundle: (uri: string) => // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/tests/legacy-cli/e2e.bzl b/tests/legacy-cli/e2e.bzl index d190c699c446..0c45c53f94c9 100644 --- a/tests/legacy-cli/e2e.bzl +++ b/tests/legacy-cli/e2e.bzl @@ -33,6 +33,7 @@ ESBUILD_TESTS = [ "tests/build/**", "tests/commands/add/**", "tests/commands/e2e/**", + "tests/commands/serve/ssr-http-requests-assets.js", "tests/i18n/**", "tests/vite/**", "tests/test/**", @@ -40,6 +41,9 @@ ESBUILD_TESTS = [ WEBPACK_IGNORE_TESTS = [ "tests/vite/**", + "tests/commands/serve/ssr-http-requests-assets.js", + "tests/build/prerender/http-requests-assets.js", + "tests/build/prerender/error-with-sourcemaps.js", ] def _to_glob(patterns): diff --git a/tests/legacy-cli/e2e/tests/commands/serve/ssr-http-requests-assets.ts b/tests/legacy-cli/e2e/tests/commands/serve/ssr-http-requests-assets.ts new file mode 100644 index 000000000000..ecb0e0fd8d00 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/commands/serve/ssr-http-requests-assets.ts @@ -0,0 +1,80 @@ +import assert from 'node:assert'; + +import { killAllProcesses, ng } from '../../../utils/process'; +import { rimraf, writeMultipleFiles } from '../../../utils/fs'; +import { installWorkspacePackages } from '../../../utils/packages'; +import { ngServe, useSha } from '../../../utils/project'; + +export default async function () { + // Forcibly remove in case another test doesn't clean itself up. + await rimraf('node_modules/@angular/ssr'); + await ng('add', '@angular/ssr', '--skip-confirmation'); + await useSha(); + await installWorkspacePackages(); + + await writeMultipleFiles({ + // Add http client and route + 'src/app/app.config.ts': ` + import { ApplicationConfig } from '@angular/core'; + import { provideRouter } from '@angular/router'; + + import { HomeComponent } from './home/home.component'; + import { provideClientHydration } from '@angular/platform-browser'; + import { provideHttpClient, withFetch } from '@angular/common/http'; + + export const appConfig: ApplicationConfig = { + providers: [ + provideRouter([{ + path: '', + component: HomeComponent, + }]), + provideClientHydration(), + provideHttpClient(withFetch()), + ], + }; + `, + // Add asset + 'src/assets/media.json': JSON.stringify({ dataFromAssets: true }), + // Update component to do an HTTP call to asset. + 'src/app/app.component.ts': ` + import { Component, inject } from '@angular/core'; + import { CommonModule } from '@angular/common'; + import { RouterOutlet } from '@angular/router'; + import { HttpClient } from '@angular/common/http'; + + @Component({ + selector: 'app-root', + standalone: true, + imports: [CommonModule, RouterOutlet], + template: \` +

{{ data | json }}

+ + \`, + }) + export class AppComponent { + data: any; + constructor() { + const http = inject(HttpClient); + http.get('/assets/media.json').toPromise().then((d) => { + this.data = d; + }); + } + } + `, + }); + + await ng('generate', 'component', 'home'); + const match = /

{[\S\s]*"dataFromAssets":[\s\S]*true[\S\s]*}<\/p>/; + const port = await ngServe('--no-ssl'); + assert.match(await (await fetch(`http://localhost:${port}/`)).text(), match); + + await killAllProcesses(); + + try { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; + const sslPort = await ngServe('--ssl'); + assert.match(await (await fetch(`https://localhost:${sslPort}/`)).text(), match); + } finally { + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '1'; + } +}