dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.22k stars 9.95k forks source link

Http request to localhost breaks prerendering #5222

Closed Spaier closed 5 years ago

Spaier commented 6 years ago

Http request to any localhost url crashes angular app with server-side rendering. Calling external api or calling localhost without prerendering works.

UPD: It works in production environment, but still crashes in dev environment with localhost.

Expected behavior: Application works

Current behavior: API isn't called, angular app crashes.

Nodejs: 8.11.3 or 10.7.0 Yarn: 1.7.0 AspNetCore: 2.1.2, IIS Express or Console aspnet-prerendering: 3.0.1 Angular: 6.0.9 Angular CLI: 6.0.8 Visual studio 2017 Community 15.7.5 Windows 10 Pro x64

app.browser.module.ts

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'vethelp' })
  ],
  bootstrap: [AppComponent],
  providers: [
    ConfigurationService,
    {
      provide: APP_INITIALIZER,
      useFactory: (configService: ConfigurationService) => async () => {
        await configService.loadConfiguration()
      },
      deps: [ConfigurationService],
      multi: true
    }
})
export class AppBrowserModule { }

configuration.service.ts

export class Configuration {
  constructor(
    public abra?: string,
    public kadabra?: string
  ) { }
}

@Injectable({
  providedIn: 'root'
})
export class ConfigurationService {

  private _configuration: Configuration

  get configuration(): Configuration {
    return this._configuration
  }

  constructor(private readonly httpClient: HttpClient) { }

  public async loadConfiguration(): Promise<Configuration> {
    const url = 'api/ClientConfiguration'
    return this.httpClient.get<Configuration>(url).toPromise().then(
      result => {
        return this._configuration = result
      },
      async error => {
        return Promise.reject(error)
      }
    )
  }
}

app.server.module.ts

@NgModule({
  imports: [
    AppBrowserModule,
    ServerModule,
    ModuleMapLoaderModule
  ],
  bootstrap: [AppComponent],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: ServerInterceptor,
      multi: true
    }
  ]
})
export class AppServerModule { }

server.interceptor.ts

@Injectable({
  providedIn: 'root'
})
export class ServerInterceptor implements HttpInterceptor {

  constructor(@Optional() @Inject(SERVER_URL) private readonly origin: string) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (this.origin) {
      const apiReq = req.clone({ url: `${this.origin}${req.url}` })
      return next.handle(apiReq)
    } else {
      return next.handle(req)
    }
  }
}

main.aspnetcore.ts

import 'zone.js/dist/zone-node'
import 'core-js/es7/reflect'

if (environment.production) {
  enableProdMode()
}

export { AppServerModule } from './app/app.server.module'

export default createServerRenderer(params => {
  const {
    AppServerModule,
    AppServerModuleNgFactory,
    LAZY_MODULE_MAP
  } = (module as any).exports

  const options = {
    document: params.data.originalHtml,
    url: params.url,
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP),
      { provide: APP_BASE_HREF, useValue: params.baseUrl },
      { provide: SERVER_URL, useValue: params.origin + params.baseUrl }
    ]
  }

  const renderPromise = AppServerModuleNgFactory
    ? /* AoT */ renderModuleFactory(AppServerModuleNgFactory, options)
    : /* dev */ renderModule(AppServerModule, options)

  return renderPromise.then(html => ({ html }))
})

angular.json

        "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "dist/vethelp-aspnetcore",
            "main": "src/main.aspnetcore.ts",
            "tsConfig": "src/tsconfig.aspnetcore.json"
          },
          "configurations": {
            "production": {
              "optimization": true,
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "fileReplacements": [{
                "replace": "src/environments/environment.ts",
                "with": "src/environments/environment.prod.ts"
              }]
            }
          }
        },
    public class Startup
    {
        public const string ClientRoot = "Client";
        public const string ClientDist = ClientRoot + "/dist";
        public const string AngularDevPolicy = "AngularDev";
        public const string AngularDevServerUri = "http://localhost:4200";
        public const string HttpDevServerUri = "http://localhost:5000";
        public const string HttpsDevServerUri = "https://localhost:44300";

        public void ConfigureServices(IServiceCollection services)
        {
            if (Environment.IsDevelopment())
            {
                services.AddCors(options =>
                {
                    options.AddPolicy(AngularDevPolicy, builder =>
                    {
                        builder.WithOrigins(AngularDevServerUri, HttpDevServerUri, HttpsDevServerUri)
                            .AllowAnyHeader()
                            .AllowAnyMethod()
                            .AllowCredentials();
                    });
                });
            }

            services.AddMvc((options) =>
            {
                // Add [Authorize] by default
                var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
                options.Filters.Add(new AuthorizeFilter(policy));
            }).AddJsonOptions(options =>
            {
                options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            }).SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            services.Configure<ClientConfiguration>(options =>
            {
                options.Abra = "123";
                options.Kadabra = "123";
            });
            services.AddSpaStaticFiles(configuration => configuration.RootPath = $"{ClientDist}/vethelp");
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment environment)
        {
            var isDev = environment.IsDevelopment();

            if (isDev)
            {
                app.UseCors(AngularDevPolicy);
                app.UseDeveloperExceptionPage();
                app.UseDatabaseErrorPage();
            }

            app.UseHttpsRedirection();

            app.UseStaticFiles();
            app.UseSpaStaticFiles();

            app.UseMvc();

            app.UseSpa(configuration =>
            {
                const bool isSsr = true;
                if (isSsr)
                {
                    configuration.UseSpaPrerendering(options =>
                    {
                        options.BootModulePath = $"{ClientDist}/vethelp-aspnetcore/main.js";
                        options.ExcludeUrls = new[] { "/sockjs-node" };
                        options.SupplyData = (context, data) =>
                        {
                            // We can provide data to our angular app here.
                            // data['someData'] = "Some Data";
                        };
                    });
                }
            });
        }
    }
}

Error

[21:30:54 Error] Microsoft.AspNetCore.NodeServices
ERROR HttpErrorResponse {
  headers:
   HttpHeaders { normalizedNames: Map {}, lazyUpdate: null, headers: Map {} },
  status: 0,
  statusText: 'Unknown Error',
  url: null,
  ok: false,
  name: 'HttpErrorResponse',
  message: 'Http failure response for (unknown url): 0 Unknown Error',
  error:
   ProgressEvent {
     type: 'error',
     target:
      XMLHttpRequest {
        onloadstart: null,
        onprogress: null,
        onabort: null,
        onerror: null,
        onload: null,
        ontimeout: null,
        onloadend: null,
        _listeners: [Object],
        onreadystatechange: null,
        _anonymous: undefined,
        readyState: 4,
        response: null,
        responseText: '',
        responseType: 'text',
        responseURL: '',
        status: 0,
        statusText: '',
        timeout: 0,
        upload: [XMLHttpRequestUpload],
        _method: 'GET',
        _url: [Url],
        _sync: false,
        _headers: [Object],
        _loweredHeaders: [Object],
        _mimeOverride: null,
        _request: null,
        _response: null,
        _responseParts: null,
        _responseHeaders: null,
        _aborting: null,
        _error: null,
        _loadedBytes: 0,
        _totalBytes: 0,
        _lengthComputable: false },
     currentTarget:
      XMLHttpRequest {
        onloadstart: null,
        onprogress: null,
        onabort: null,
        onerror: null,
        onload: null,
        ontimeout: null,
        onloadend: null,
        _listeners: [Object],
        onreadystatechange: null,
        _anonymous: undefined,
        readyState: 4,
        response: null,
        responseText: '',
        responseType: 'text',
        responseURL: '',
        status: 0,
        statusText: '',
        timeout: 0,
        upload: [XMLHttpRequestUpload],
        _method: 'GET',
        _url: [Url],
        _sync: false,
        _headers: [Object],
        _loweredHeaders: [Object],
        _mimeOverride: null,
        _request: null,
        _response: null,
        _responseParts: null,
        _responseHeaders: null,
        _aborting: null,
        _error: null,
        _loadedBytes: 0,
        _totalBytes: 0,
        _lengthComputable: false },
     lengthComputable: false,
     loaded: 0,
     total: 0 } }

[21:30:54 Error] Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware
An unhandled exception has occurred while executing the request.
Microsoft.AspNetCore.NodeServices.HostingModels.NodeInvocationException: Http failure response for (unknown url): 0 Unknown Error

   at Microsoft.AspNetCore.NodeServices.HostingModels.HttpNodeInstance.InvokeExportAsync[T](NodeInvocationInfo invocationInfo, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.NodeServices.HostingModels.OutOfProcessNodeInstance.InvokeExportAsync[T](CancellationToken cancellationToken, String moduleName, String exportNameOrNull, Object[] args)
   at Microsoft.AspNetCore.NodeServices.NodeServicesImpl.InvokeExportWithPossibleRetryAsync[T](String moduleName, String exportedFunctionName, Object[] args, Boolean allowRetry, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.NodeServices.NodeServicesImpl.InvokeExportWithPossibleRetryAsync[T](String moduleName, String exportedFunctionName, Object[] args, Boolean allowRetry, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Builder.SpaPrerenderingExtensions.<>c__DisplayClass0_0.<<UseSpaPrerendering>b__1>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIIndexMiddleware.Invoke(HttpContext httpContext)
   at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at IdentityServer4.Hosting.IdentityServerMiddleware.Invoke(HttpContext context, IEndpointRouter router, IUserSession session, IEventService events) in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Hosting\IdentityServerMiddleware.cs:line 72
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Cors.Infrastructure.CorsMiddleware.Invoke(HttpContext context)
   at IdentityServer4.Hosting.BaseUrlMiddleware.Invoke(HttpContext context) in C:\local\identity\server4\IdentityServer4\src\IdentityServer4\Hosting\BaseUrlMiddleware.cs:line 36
   at NWebsec.AspNetCore.Middleware.Middleware.MiddlewareBase.Invoke(HttpContext context)
   at NWebsec.AspNetCore.Middleware.Middleware.MiddlewareBase.Invoke(HttpContext context)
   at NWebsec.AspNetCore.Middleware.Middleware.MiddlewareBase.Invoke(HttpContext context)
   at NWebsec.AspNetCore.Middleware.Middleware.MiddlewareBase.Invoke(HttpContext context)
   at NWebsec.AspNetCore.Middleware.Middleware.MiddlewareBase.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

[21:30:54 Error] Microsoft.AspNetCore.NodeServices
ERROR { TypeError: Converting circular structure to JSON
    at JSON.stringify (<anonymous>)
    at readableObjectToString (C:\Users\SpaierInstinct\Documents\Projects\VetHelp\src\Web\Client\node_modules\zone.js\dist\zone-node.js:652:63)
    at resolvePromise (C:\Users\SpaierInstinct\Documents\Projects\VetHelp\src\Web\Client\node_modules\zone.js\dist\zone-node.js:814:69)
    at resolvePromise (C:\Users\SpaierInstinct\Documents\Projects\VetHelp\src\Web\Client\node_modules\zone.js\dist\zone-node.js:771:17)
    at C:\Users\SpaierInstinct\Documents\Projects\VetHelp\src\Web\Client\node_modules\zone.js\dist\zone-node.js:873:17
    at ZoneDelegate.invokeTask (C:\Users\SpaierInstinct\Documents\Projects\VetHelp\src\Web\Client\node_modules\zone.js\dist\zone-node.js:421:31)
    at Object.onInvokeTask (C:\Users\SpaierInstinct\Documents\Projects\VetHelp\src\Web\Client\node_modules\@angular\core\bundles\core.umd.js:3804:37)
    at ZoneDelegate.invokeTask (C:\Users\SpaierInstinct\Documents\Projects\VetHelp\src\Web\Client\node_modules\zone.js\dist\zone-node.js:420:36)
    at Zone.runTask (C:\Users\SpaierInstinct\Documents\Projects\VetHelp\src\Web\Client\node_modules\zone.js\dist\zone-node.js:188:47)
    at drainMicroTaskQueue (C:\Users\SpaierInstinct\Documents\Projects\VetHelp\src\Web\Client\node_modules\zone.js\dist\zone-node.js:595:35)
  rejection:
   HttpErrorResponse {
     headers:
      HttpHeaders { normalizedNames: Map {}, lazyUpdate: null, headers: Map {} },
     status: 0,
     statusText: 'Unknown Error',
     url: null,
     ok: false,
     name: 'HttpErrorResponse',
     message: 'Http failure response for (unknown url): 0 Unknown Error',
     error:
      ProgressEvent {
        type: 'error',
        target: [XMLHttpRequest],
        currentTarget: [XMLHttpRequest],
        lengthComputable: false,
        loaded: 0,
        total: 0 } },
  promise:
   ZoneAwarePromise {
     __zone_symbol__state: 0,
     __zone_symbol__value:
      HttpErrorResponse {
        headers: [HttpHeaders],
        status: 0,
        statusText: 'Unknown Error',
        url: null,
        ok: false,
        name: 'HttpErrorResponse',
        message: 'Http failure response for (unknown url): 0 Unknown Error',
        error: [ProgressEvent] } },
  zone:
   Zone {
     _properties: { isAngularZone: true },
     _parent:
      Zone {
        _properties: {},
        _parent: null,
        _name: '<root>',
        _zoneDelegate: [ZoneDelegate] },
     _name: 'angular',
     _zoneDelegate:
      ZoneDelegate {
        _taskCounts: [Object],
        zone: [Circular],
        _parentDelegate: [ZoneDelegate],
        _forkZS: null,
        _forkDlgt: null,
        _forkCurrZone: [Zone],
        _interceptZS: null,
        _interceptDlgt: null,
        _interceptCurrZone: [Zone],
        _invokeZS: [Object],
        _invokeDlgt: [ZoneDelegate],
        _invokeCurrZone: [Circular],
        _handleErrorZS: [Object],
        _handleErrorDlgt: [ZoneDelegate],
        _handleErrorCurrZone: [Circular],
        _scheduleTaskZS: [Object],
        _scheduleTaskDlgt: [ZoneDelegate],
        _scheduleTaskCurrZone: [Circular],
        _invokeTaskZS: [Object],
        _invokeTaskDlgt: [ZoneDelegate],
        _invokeTaskCurrZone: [Circular],
        _cancelTaskZS: [Object],
        _cancelTaskDlgt: [ZoneDelegate],
        _cancelTaskCurrZone: [Circular],
        _hasTaskZS: [Object],
        _hasTaskDlgt: [ZoneDelegate],
        _hasTaskDlgtOwner: [Circular],
        _hasTaskCurrZone: [Circular] } },
  task:
   ZoneTask {
     _zone:
      Zone {
        _properties: [Object],
        _parent: [Zone],
        _name: 'angular',
        _zoneDelegate: [ZoneDelegate] },
     runCount: 0,
     _zoneDelegates: null,
     _state: 'notScheduled',
     type: 'microTask',
     source: 'Promise.then',
     data:
      ZoneAwarePromise {
        __zone_symbol__state: 0,
        __zone_symbol__value: [HttpErrorResponse] },
     scheduleFn: undefined,
     cancelFn: null,
     callback: [Function],
     invoke: [Function] } }
Spaier commented 6 years ago

Url is absolute: https://localhost:44300/api/ClientConfiguration(via ServerInterceptor). https://httpbin.org/get worked. Prerendering works with anything but localhost.

jimmyjlli commented 6 years ago

Have you tried running as http instead of https? The self-signed cert may be causing issue.

speige commented 6 years ago

I had the same issue due to SSL. One option is to ignore these errors by adding this to your main.server.ts file:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
michael-vasyliv commented 5 years ago

I have the same error, is there any solution to fix it? (SSR doesn't work when I try to get data from back-end on server)

mkArtakMSFT commented 5 years ago

Thanks for contacting us. We're closing this issue as this doesn't align with our long-term plans in this area. You can read more details about our vision for this area at https://github.com/aspnet/AspNetCore/issues/12890.