` 标记中渲染出这个视图,并为客户端创建一个完成的 HTML 页面。
Finally, the server returns the rendered page to the client.
最后,服务器就会把渲染好的页面返回给客户端。
### Working around the browser APIs
### 使用浏览器 API
Because a Universal app doesn't execute in the browser, some of the browser APIs and capabilities may be missing on the server.
由于 Universal 应用并没有运行在浏览器中,因此该服务器上可能会缺少浏览器的某些 API 和其它能力。
For example, server-side applications can't reference browser-only global objects such as `window`, `document`, `navigator`, or `location`.
比如,服务端应用不能引用浏览器独有的全局对象,比如 `window`、`document`、`navigator` 或 `location`。
Angular provides some injectable abstractions over these objects, such as [`Location`](api/common/Location)
or [`DOCUMENT`](api/common/DOCUMENT); it may substitute adequately for these APIs.
If Angular doesn't provide it, it's possible to write new abstractions that delegate to the browser APIs while in the browser
and to an alternative implementation while on the server (aka shimming).
Angular 提供了一些这些对象的可注入的抽象层,比如 [`Location`](api/common/Location) 或 [`DOCUMENT`](api/common/DOCUMENT),它可以作为你所调用的 API 的等效替身。
如果 Angular 没有提供它,你也可以写一个自己的抽象层,当在浏览器中运行时,就把它委托给浏览器 API,当它在服务器中运行时,就提供一个符合要求的代用实现(也叫垫片 - shimming)。
Similarly, without mouse or keyboard events, a server-side app can't rely on a user clicking a button to show a component.
The app must determine what to render based solely on the incoming client request.
This is a good argument for making the app [routable](guide/router).
同样,由于没有鼠标或键盘事件,因此 Universal 应用也不能依赖于用户点击某个按钮来显示每个组件。
Universal 应用必须仅仅根据客户端过来的请求决定要渲染的内容。
把该应用做成[可路由的](guide/router),就是一种好方案。
{@a http-urls}
### Using absolute URLs for server requests
### 在 HTTP 中使用绝对地址
The tutorial's `HeroService` and `HeroSearchService` delegate to the Angular `HttpClient` module to fetch application data.
These services send requests to _relative_ URLs such as `api/heroes`.
In a Universal app, HTTP URLs must be _absolute_(for example, `https://my-server.com/api/heroes`).
This means you need to change your services to make requests with absolute URLs when running on the server and with relative
URLs when running in the browser.
教程中的 `HeroService` 和 `HeroSearchService` 都委托了 Angular 的 `HttpClient` 模块来获取应用数据。
那些服务都把请求发送到了*相对* URL,比如 `api/heroes`。
在 Universal 应用中,HTTP 的 URL 必须是*绝对地址*(比如 `https://my-server.com/api/heroes`),
只有这样,Universal 的 Web 服务器才能处理那些请求。
这意味着当运行在服务端时,你要修改你的服务,来使用绝对 URL发起请求,而在浏览器中,则使用相对 URL。
One solution is to provide the full URL to your application on the server, and write an interceptor that can retrieve this
value and prepend it to the request URL. If you're using the `ngExpressEngine`, as shown in the example in this guide, half
the work is already done. We'll assume this is the case, but it's trivial to provide the same functionality.
解决方案之一是在服务器上运行时提供完整的 URL,并且写拦截器来获取这个值,并把它追加到请求 URL 的前部。假设你在使用 `ngExpressEngine`(就像本章的例子一样),大约一半儿的工作就已经就绪了。这里我们就基于此假设,但即使要自行实现同样的功能也很简单。
Start by creating an [HttpInterceptor](api/common/http/HttpInterceptor).
我们从创建 [HttpInterceptor](api/common/http/HttpInterceptor) 开始:
import {Injectable, Inject, Optional} from '@angular/core';
import {HttpInterceptor, HttpHandler, HttpRequest, HttpHeaders} from '@angular/common/http';
import {Request} from 'express';
import {REQUEST} from '@nguniversal/express-engine/tokens';
@Injectable()
export class UniversalInterceptor implements HttpInterceptor {
constructor(@Optional() @Inject(REQUEST) protected request: Request) {}
intercept(req: HttpRequest, next: HttpHandler) {
let serverReq: HttpRequest = req;
if (this.request) {
let newUrl = `${this.request.protocol}://${this.request.get('host')}`;
if (!req.url.startsWith('/')) {
newUrl += '/';
}
newUrl += req.url;
serverReq = req.clone({url: newUrl});
}
return next.handle(serverReq);
}
}
Next, provide the interceptor in the providers for the server `AppModule`.
接下来,在服务端的 `AppModule` (app.server.module.ts) 的 `providers` 中提供这个拦截器:
import {HTTP_INTERCEPTORS} from '@angular/common/http';
import {UniversalInterceptor} from './universal-interceptor';
@NgModule({
...
providers: [{
provide: HTTP_INTERCEPTORS,
useClass: UniversalInterceptor,
multi: true
}],
})
export class AppServerModule {}
Now, on every HTTP request made on the server, this interceptor will fire and replace the request URL with the absolute
URL provided in the Express `Request` object.
现在,当服务器发起每个 HTTP 请求时,该拦截器都会被触发,并把请求的 URL 替换为由 Express 的 `Request` 对象给出的绝对地址。
{@a universal-engine}
### Universal template engine
### Universal 模板引擎
The important bit in the `server.ts` file is the `ngExpressEngine()` function.
`server.ts` 文件中最重要的部分是 `ngExpressEngine()` 函数:
The `ngExpressEngine()` function is a wrapper around Universal's `renderModule()` function which turns a client's
requests into server-rendered HTML pages.
`ngExpressEngine()` 是对 Universal 的 `renderModule()` 函数的封装。它会把客户端请求转换成服务端渲染的 HTML 页面。
你还要在某个适用于你服务端技术栈的*模板引擎*中调用这个函数。
* The first parameter is `AppServerModule`.
It's the bridge between the Universal server-side renderer and the Angular application.
第一个参数是 `AppServerModule`。
它是 Universal 服务端渲染器和你的应用之间的桥梁。
* The second parameter, `extraProviders`, is optional. It lets you specify dependency providers that apply only when
running on this server.
You can do this when your app needs information that can only be determined by the currently running server instance.
One example could be the running server's *origin*, which could be used to [calculate absolute HTTP URLs](#http-urls) if
not using the `Request` token as shown above.
第二个参数 `extraProviders` 是可选的。它能让你指定一些在服务端运行时特有的服务提供商。
只有当你的应用需要一些运行在服务器中才需要的信息时,才需要这么做。
比如这个运行中的服务器的*源*地址,当像前面例子中那样无法使用 `Request` 令牌时,可用它来[计算 HTTP URL 的绝对地址](#http-urls)。
The `ngExpressEngine()` function returns a `Promise` callback that resolves to the rendered page.
It's up to the engine to decide what to do with that page.
This engine's `Promise` callback returns the rendered page to the web server,
which then forwards it to the client in the HTTP response.
`ngExpressEngine()` 函数返回了一个会解析成渲染好的页面的*承诺(Promise)*。
接下来你的引擎要决定拿这个页面做点什么。
在*这个引擎*的 `Promise` 回调函数中,把渲染好的页面返回给了 Web 服务器,然后服务器通过 HTTP 响应把它转发给了客户端。
**Note:** These wrappers help hide the complexity of the `renderModule()` function. There are more wrappers
for different backend technologies at the [Universal repository](https://github.com/angular/universal).
**注意:** 这个包装器帮助隐藏了 `renderModule()` 的复杂性。
在 [Universal 代码库中](https://github.com/angular/universal)还有更多针对其它后端技术的包装器。
### Filtering request URLs
### 过滤请求的 URL
NOTE: the basic behavior described below is handled automatically when using the NgUniversal Express schematic, this
is helpful when trying to understand the underlying behavior or replicate it without using the schematic.
注意:当使用 NgUniversal Express 原理图时,将自动处理稍后描述的基本行为。当你要尝试理解其底层行为或在不使用原理图的情况下自行实现它时,这一节会很有用。
The web server must distinguish _app page requests_ from other kinds of requests.
Web 服务器必须把*对应用页面的请求*和其它类型的请求区分开。
It's not as simple as intercepting a request to the root address `/`.
The browser could ask for one of the application routes such as `/dashboard`, `/heroes`, or `/detail:12`.
In fact, if the app were only rendered by the server, _every_ app link clicked would arrive at the server
as a navigation URL intended for the router.
这可不像拦截对根路径 `/` 的请求那么简单。
浏览器可以请求应用中的任何一个路由地址,比如 `/dashboard`、`/heroes` 或 `/detail:12`。
事实上,如果应用*只*会通过服务器渲染,那么应用中点击的*任何一个*链接都会发到服务器,就像导航时的地址会发到路由器一样。
Fortunately, application routes have something in common: their URLs lack file extensions.
(Data requests also lack extensions but they're easy to recognize because they always begin with `/api`.)
All static asset requests have a file extension (such as `main.js` or `/node_modules/zone.js/dist/zone.js`).
幸运的是,应用的路由具有一些共同特征:它们的 URL 一般不带文件扩展名。
(数据请求也可能缺少扩展名,但是它们很容易识别出来,因为它们总是以 `/api` 开头,所有的静态资源的请求都会带有一个扩展名,比如 `main.js` 或 `/node_modules/zone.js/dist/zone.js`)。
Because we use routing, we can easily recognize the three types of requests and handle them differently.
由于使用了路由,所以我们可以轻松的识别出这三种类型的请求,并分别处理它们。
1. **Data request**: request URL that begins `/api`.
**数据请求**:请求的 URL 用 `/api` 开头
2. **App navigation**: request URL with no file extension.
**应用导航**:请求的 URL 不带扩展名
3. **Static asset**: all other requests.
**静态资源**:所有其它请求。
A Node Express server is a pipeline of middleware that filters and processes requests one after the other.
You configure the Node Express server pipeline with calls to `app.get()` like this one for data requests.
Node Express 服务器是一系列中间件构成的管道,它会挨个对 URL 请求进行过滤和处理。
你可以调用 `app.get()` 来配置 Express 服务器的管道,就像下面这个数据请求一样:
**Note:** This sample server doesn't handle data requests.
**注意:**这个范例服务器不会处理数据请求。
The tutorial's "in-memory web API" module, a demo and development tool, intercepts all HTTP calls and
simulates the behavior of a remote data server.
In practice, you would remove that module and register your web API middleware on the server here.
本教程的“内存 Web API” 模块(一个演示及开发工具)拦截了所有 HTTP 调用,并且模拟了远端数据服务器的行为。
在实践中,你应该移除这个模块,并且在服务器上注册你的 Web API 中间件。
The following code filters for request URLs with no extensions and treats them as navigation requests.
下列代码会过滤出不带扩展名的 URL,并把它们当做导航请求进行处理。
### Serving static files safely
### 安全的提供静态文件
A single `app.use()` treats all other URLs as requests for static assets
such as JavaScript, image, and style files.
单独的 `app.use()` 会处理所有其它 URL,比如对 JavaScript 、图片和样式表等静态资源的请求。
To ensure that clients can only download the files that they are permitted to see, put all client-facing asset files in
the `/dist` folder and only honor requests for files from the `/dist` folder.
要保证客户端只能下载那些*允许*他们访问的文件,你应该把所有面向客户端的资源文件都放在 `/dist` 目录下,并且只允许客户端请求来自 `/dist` 目录下的文件。
The following Node Express code routes all remaining requests to `/dist`, and returns a `404 - NOT FOUND` error if the
file isn't found.
下列 Express 代码会把剩下的所有请求都路由到 `/dist` 目录下,如果文件未找到,就会返回 `404 - NOT FOUND`。