如何在 Angular Universal 中获取完整的基本 URL(包括服务器、端口和协议)?

Posted

技术标签:

【中文标题】如何在 Angular Universal 中获取完整的基本 URL(包括服务器、端口和协议)?【英文标题】:How to get full base URL (including server, port and protocol) in Angular Universal? 【发布时间】:2017-09-10 05:56:58 【问题描述】:

我需要获取我的 Angular 2 应用程序的完整基本 URL(例如 http://localhost:5000 或 https://productionserver.com),以便我可以将其传递给应用程序上下文中的第 3 方服务。应用的位置取决于它是开发、各种暂存/测试环境还是生产环境,我想动态检测它,因此我不需要维护硬编码列表。

过去曾提出过similar question,但答案(即使用某些版本的window.location.hostname 或window.location.origin 属性)仅在浏览器呈现angular2 应用程序时有效。

我希望我的应用与 Angular Universal 一起使用,这意味着它需要在无法访问 DOM 对象(如 window.location)的服务器端呈现。

任何想法如何做到这一点?作为参考,使用asp.net core作为后端(使用默认的dotnet new angular模板)。

【问题讨论】:

Universal 中有 ORIGIN_URL 提供程序,github.com/angular/universal/blob/2.x/modules/platform-node/…。我建议检查它是否有效。无论如何,一个好习惯是提供包含主机名的环境变量/提供程序。 API 调用的主机可能不同,您迟早会做同样的事情。 尝试像@Inject(ORIGIN_URL) @Optional() origin一样注入它,并在服务器端调试它的含义。 感谢您的提示!我对 Angular 有点陌生,所以如果这很明显,请原谅我,但你知道我如何检查 ORIGIN_URL 是否会给我想要的东西吗?我尝试向它询问我的组件的构造函数,如下所示:constructor(@Inject(ORIGIN_URL) private _origin) 但我收到错误“无法解析 HomeComponent 的所有参数”。我尝试将它添加到我的 ngModule 装饰器的 Providers 数组中,但这似乎也没有改变错误。也许有一些我不知道的语法细节? 它应该被标记为可选,因为它在客户端不存在。 谢谢!看起来它在服务器端具有价值。我只是通过将它渲染到包装在 中的模板来解决这个问题,而客户端没有触及它。奇怪的是,尽管我的构造函数看起来像构造函数(@Optional() @Inject(ORIGIN_URL) origin),但客户端一直在抱怨同样的错误(“无法解析所有参数”)。 【参考方案1】:

你会发现所有来自 Http 请求的内容都不会被预渲染:这是因为 Universal 需要绝对 URL。

由于您的开发和生产服务器不会有相同的 URL,因此您自己管理它非常痛苦。

我的自动化解决方案:使用 Angular 4.3 的新 HttpClient 拦截器功能,结合 Express 引擎。

拦截器在服务器上下文中捕获所有请求以添加完整的 URL。

import  Injectable, Inject, Optional  from '@angular/core';
 import  HttpInterceptor, HttpHandler, HttpRequest  from'@angular/common/http';
 @Injectable()
 export class UniversalInterceptor implements HttpInterceptor 
  constructor(@Optional() @Inject('serverUrl') protected serverUrl: string) 
  intercept(req: HttpRequest<any>, next: HttpHandler) 
    const serverReq = !this.serverUrl ? req : req.clone(
      url: ``$this.serverUrl$req.url``
    );
    return next.handle(serverReq);
  

然后在您的 AppServerModule 中提供它:

import  NgModule  from '@angular/core';
import  ServerModule  from '@angular/platform-server';
import  HTTP_INTERCEPTORS  from '@angular/common/http';
import  AppModule  from './app.module';
import  AppComponent  from './app.component';
import  UniversalInterceptor  from './universal.interceptor';
@NgModule(
  imports: [
    AppModule,
    ServerModule
  ],
  providers: [
    provide: HTTP_INTERCEPTORS,
    useClass: UniversalInterceptor,
    /* Multi is important or you will delete all the other interceptors */
    multi: true
  ],
  bootstrap: [AppComponent]
)
export class AppServerModule 

现在您可以使用 Express 引擎将完整的 URL 传递给 Angular,只需更新您的 server.js :

 function angularRouter(req, res)  
  res.render('index', 
    req,
    res,
    providers: [
      provide: 'serverUrl',
      useValue: `$req.protocol://$req.get('host')`
    ]
  );

【讨论】:

【参考方案2】:

现在我正在使用 server.ts ngExpressEngine:

import  ngExpressEngine  from '@nguniversal/express-engine';

const AppServerModuleNgFactory, LAZY_MODULE_MAP = require('./dist/server/main.bundle');

    const provideModuleMap = require('@nguniversal/module-map-ngfactory-loader');

    app.engine('html', ngExpressEngine(
        bootstrap: AppServerModuleNgFactory,
        providers: [
            provideModuleMap(LAZY_MODULE_MAP)
        ]
    ));

之后我可以在 location.service.ts 中使用:

constructor(@Optional() @Inject(REQUEST) private request: any,
            @Optional() @Inject(RESPONSE) private response: any,
            @Inject(PLATFORM_ID) private platformId: Object)

  if (isPlatformServer(this.platformId))
  
    console.log(this.request.get('host’)); // host on the server
   else
  
    console.log(document.location.hostname); // host on the browser
  

【讨论】:

你能详细说明如何使用它吗?我想在我的角度代码中访问带有子域的主机 这在我的情况下不起作用。 this.request.get('host') 总是返回 127.0.0.1:4000 这里的例子:github.com/DmitriyIvchenko/angular/blob/…【参考方案3】:

我有一些 angular 5 和 angular universal 的工作代码

在 server.ts 中替换这个

app.engine('html', (_, options, callback) => 
    let engine = ngExpressEngine(
        bootstrap: AppServerModuleNgFactory,
        providers: [
             provide: 'request', useFactory: () => options.req, deps: [] ,
            provideModuleMap(LAZY_MODULE_MAP)
        ]
    );
    engine(_, options, callback);
);

在 Angular 方面,您可以使用以下代码获取主机

export class AppComponent 
    constructor(
        private injector: Injector,
        @Inject(PLATFORM_ID) private platformId: Object
    ) 
        console.log('hi, we\'re here!');
        if (isPlatformServer(this.platformId)) 
            let req = this.injector.get('request');
            console.log("locales from crawlers: " + req.headers["accept-language"]);
            console.log("host: " + req.get('host'));
            console.log("headers: ", req.headers);
         else 
            console.log('we\'re rendering from the browser, there is no request object.');
        
    

【讨论】:

当我在本地运行它时它工作正常,但在服务器上它给出了没有提供者请求的错误! .有解决方案吗?我正在开发 Angular 4 你的server.ts应该有问题 非常非常好。为我工作。 看起来这样可行:github.com/DmitriyIvchenko/angular/blob/…【参考方案4】:

感谢 estus 的帮助,我设法拼凑出一些可行的东西。

看起来大多数 Angular 通用模板实际上让服务器传递了一个名为“originUrl”的区域参数,服务器渲染方法使用该参数来提供可以注入其他方法的 ORIGIN_URL 令牌。我找不到任何有关此的文档,但您可以查看 an example here。

所以如果你写这样的东西......

export function getBaseUrl() 
    if (Zone.current.get("originUrl")) 
        return Zone.current.get('originUrl');
     else if (location) 
        return location.origin;
     else 
        return 'something went wrong!';
    

您应该能够在服务器和客户端上获取完整的原始 URL。

【讨论】:

Zone.current.get("originUrl") 在 Angular 5 中对我不起作用。 @xuhcc - 你找到解决方案了吗? @kris_IV - 是的,我按照@chintan adatiya 的建议添加了request 提供程序。 @xuhcc 解决方案对我不起作用 - 我在第一次请求时得到 host: 127.0.0.1:4000 但我需要域;/ @kris_IV 当您的 Angular 通用应用程序在反向代理后面运行时,可能会发生这种情况。尝试检查X-Forwarded-ForX-Real-IP 标头。

以上是关于如何在 Angular Universal 中获取完整的基本 URL(包括服务器、端口和协议)?的主要内容,如果未能解决你的问题,请参考以下文章

从 Angular Universal 应用程序中的 Express JS 获取值

如何在 Angular Universal 中检测屏幕分辨率?

如何在 Angular 5 (s-s-r with Universal) 中使用 Google Analytics 功能?

如何在 Angular Universal 中捕获服务器上的组件错误?

Angular Universal 正在为每个页面在页面源中呈现主页 html

如何正确启动 Angular Universal 到实时服务器