feat(platform-server): support @angular/http from @angular/platform-server
This change installs HttpModule with ServerModule, and overrides bindings to service Http requests made from the server with the 'xhr2' NPM package. Outgoing requests are wrapped in a Zone macro-task, so they will be tracked within the Angular zone and cause the isStable API to show 'false' until they return. This is essential for Universal support of server-side HTTP.
This commit is contained in:

committed by
Igor Minar

parent
30380d010b
commit
9559d3e949
127
modules/@angular/platform-server/src/http.ts
Normal file
127
modules/@angular/platform-server/src/http.ts
Normal file
@ -0,0 +1,127 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
const xhr2: any = require('xhr2');
|
||||
|
||||
import {Injectable, Provider} from '@angular/core';
|
||||
import {BrowserXhr, Connection, ConnectionBackend, Http, ReadyState, Request, RequestOptions, Response, XHRBackend, XSRFStrategy} from '@angular/http';
|
||||
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {Observer} from 'rxjs/Observer';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
|
||||
@Injectable()
|
||||
export class ServerXhr implements BrowserXhr {
|
||||
build(): XMLHttpRequest { return new xhr2.XMLHttpRequest(); }
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ServerXsrfStrategy implements XSRFStrategy {
|
||||
configureRequest(req: Request): void {}
|
||||
}
|
||||
|
||||
export class ZoneMacroTaskConnection implements Connection {
|
||||
response: Observable<Response>;
|
||||
lastConnection: Connection;
|
||||
|
||||
constructor(public request: Request, backend: XHRBackend) {
|
||||
this.response = new Observable((observer: Observer<Response>) => {
|
||||
let task: Task = null;
|
||||
let scheduled: boolean = false;
|
||||
let sub: Subscription = null;
|
||||
let savedResult: any = null;
|
||||
let savedError: any = null;
|
||||
|
||||
const scheduleTask = (_task: Task) => {
|
||||
task = _task;
|
||||
scheduled = true;
|
||||
|
||||
this.lastConnection = backend.createConnection(request);
|
||||
sub = (this.lastConnection.response as Observable<Response>)
|
||||
.subscribe(
|
||||
res => savedResult = res,
|
||||
err => {
|
||||
if (!scheduled) {
|
||||
throw new Error('invoke twice');
|
||||
}
|
||||
savedError = err;
|
||||
scheduled = false;
|
||||
task.invoke();
|
||||
},
|
||||
() => {
|
||||
if (!scheduled) {
|
||||
throw new Error('invoke twice');
|
||||
}
|
||||
scheduled = false;
|
||||
task.invoke();
|
||||
});
|
||||
};
|
||||
|
||||
const cancelTask = (_task: Task) => {
|
||||
if (!scheduled) {
|
||||
return;
|
||||
}
|
||||
scheduled = false;
|
||||
if (sub) {
|
||||
sub.unsubscribe();
|
||||
sub = null;
|
||||
}
|
||||
};
|
||||
|
||||
const onComplete = () => {
|
||||
if (savedError !== null) {
|
||||
observer.error(savedError);
|
||||
} else {
|
||||
observer.next(savedResult);
|
||||
observer.complete();
|
||||
}
|
||||
};
|
||||
|
||||
// MockBackend is currently synchronous, which means that if scheduleTask is by
|
||||
// scheduleMacroTask, the request will hit MockBackend and the response will be
|
||||
// sent, causing task.invoke() to be called.
|
||||
const _task = Zone.current.scheduleMacroTask(
|
||||
'ZoneMacroTaskConnection.subscribe', onComplete, {}, () => null, cancelTask);
|
||||
scheduleTask(_task);
|
||||
|
||||
return () => {
|
||||
if (scheduled && task) {
|
||||
task.zone.cancelTask(task);
|
||||
scheduled = false;
|
||||
}
|
||||
if (sub) {
|
||||
sub.unsubscribe();
|
||||
sub = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
get readyState(): ReadyState {
|
||||
return !!this.lastConnection ? this.lastConnection.readyState : ReadyState.Unsent;
|
||||
}
|
||||
}
|
||||
|
||||
export class ZoneMacroTaskBackend implements ConnectionBackend {
|
||||
constructor(private backend: XHRBackend) {}
|
||||
|
||||
createConnection(request: any): ZoneMacroTaskConnection {
|
||||
return new ZoneMacroTaskConnection(request, this.backend);
|
||||
}
|
||||
}
|
||||
|
||||
export function httpFactory(xhrBackend: XHRBackend, options: RequestOptions) {
|
||||
const macroBackend = new ZoneMacroTaskBackend(xhrBackend);
|
||||
return new Http(macroBackend, options);
|
||||
}
|
||||
|
||||
export const SERVER_HTTP_PROVIDERS: Provider[] = [
|
||||
{provide: Http, useFactory: httpFactory, deps: [XHRBackend, RequestOptions]},
|
||||
{provide: BrowserXhr, useClass: ServerXhr},
|
||||
{provide: XSRFStrategy, useClass: ServerXsrfStrategy},
|
||||
];
|
@ -8,8 +8,11 @@
|
||||
|
||||
import {PlatformLocation} from '@angular/common';
|
||||
import {platformCoreDynamic} from '@angular/compiler';
|
||||
import {APP_BOOTSTRAP_LISTENER, InjectionToken, Injector, NgModule, PLATFORM_INITIALIZER, PlatformRef, Provider, RENDERER_V2_DIRECT, RendererV2, RootRenderer, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
import {APP_BOOTSTRAP_LISTENER, Injectable, InjectionToken, Injector, NgModule, PLATFORM_INITIALIZER, PlatformRef, Provider, RENDERER_V2_DIRECT, RendererV2, RootRenderer, createPlatformFactory, isDevMode, platformCore} from '@angular/core';
|
||||
import {HttpModule} from '@angular/http';
|
||||
import {BrowserModule, DOCUMENT} from '@angular/platform-browser';
|
||||
|
||||
import {SERVER_HTTP_PROVIDERS} from './http';
|
||||
import {ServerPlatformLocation} from './location';
|
||||
import {Parse5DomAdapter, parseDocument} from './parse5_adapter';
|
||||
import {PlatformState} from './platform_state';
|
||||
@ -86,9 +89,8 @@ export const INITIAL_CONFIG = new InjectionToken<PlatformConfig>('Server.INITIAL
|
||||
*/
|
||||
@NgModule({
|
||||
exports: [BrowserModule],
|
||||
providers: [
|
||||
SERVER_RENDER_PROVIDERS,
|
||||
]
|
||||
imports: [HttpModule],
|
||||
providers: [SERVER_RENDER_PROVIDERS, SERVER_HTTP_PROVIDERS],
|
||||
})
|
||||
export class ServerModule {
|
||||
}
|
||||
|
Reference in New Issue
Block a user