Files
Angular-Docs-Cn/packages/platform-server/src/http.ts
T
Adam Plumer 9edea0bb75 feat(platform-server): use absolute URLs from Location for HTTP (#37071)
Currently, requests from the server that do not use absolute URLs
fail because the server does not have the same fallback mechanism
that browser XHR does. This adds that mechanism by pulling the
full URL out of the document.location object, if available.

PR Close #37071
2020-05-13 16:00:02 -07:00

144 lines
4.4 KiB
TypeScript

/**
* @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, Injector, Provider} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {HttpEvent, HttpRequest, HttpHandler, HttpBackend, XhrFactory, ɵHttpInterceptingHandler as HttpInterceptingHandler} from '@angular/common/http';
import {Observable, Observer, Subscription} from 'rxjs';
// @see https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#URI-syntax
const isAbsoluteUrl = /^[a-zA-Z\-\+.]+:\/\//;
const FORWARD_SLASH = '/';
@Injectable()
export class ServerXhr implements XhrFactory {
build(): XMLHttpRequest {
return new xhr2.XMLHttpRequest();
}
}
export abstract class ZoneMacroTaskWrapper<S, R> {
wrap(request: S): Observable<R> {
return new Observable((observer: Observer<R>) => {
let task: Task = null!;
let scheduled: boolean = false;
let sub: Subscription|null = null;
let savedResult: any = null;
let savedError: any = null;
const scheduleTask = (_task: Task) => {
task = _task;
scheduled = true;
const delegate = this.delegate(request);
sub = delegate.subscribe(
res => savedResult = res,
err => {
if (!scheduled) {
throw new Error(
'An http observable was completed twice. This shouldn\'t happen, please file a bug.');
}
savedError = err;
scheduled = false;
task.invoke();
},
() => {
if (!scheduled) {
throw new Error(
'An http observable was completed twice. This shouldn\'t happen, please file a bug.');
}
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 for Http is 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(
'ZoneMacroTaskWrapper.subscribe', onComplete, {}, () => null, cancelTask);
scheduleTask(_task);
return () => {
if (scheduled && task) {
task.zone.cancelTask(task);
scheduled = false;
}
if (sub) {
sub.unsubscribe();
sub = null;
}
};
});
}
protected abstract delegate(request: S): Observable<R>;
}
export class ZoneClientBackend extends
ZoneMacroTaskWrapper<HttpRequest<any>, HttpEvent<any>> implements HttpBackend {
constructor(private backend: HttpBackend, private doc: Document) {
super();
}
handle(request: HttpRequest<any>): Observable<HttpEvent<any>> {
const href = this.doc.location.href;
if (!isAbsoluteUrl.test(request.url) && href) {
const urlParts = Array.from(request.url);
if (request.url[0] === FORWARD_SLASH && href[href.length - 1] === FORWARD_SLASH) {
urlParts.shift();
} else if (request.url[0] !== FORWARD_SLASH && href[href.length - 1] !== FORWARD_SLASH) {
urlParts.splice(0, 0, FORWARD_SLASH);
}
return this.wrap(request.clone({url: href + urlParts.join('')}));
}
return this.wrap(request);
}
protected delegate(request: HttpRequest<any>): Observable<HttpEvent<any>> {
return this.backend.handle(request);
}
}
export function zoneWrappedInterceptingHandler(
backend: HttpBackend, injector: Injector, doc: Document) {
const realBackend: HttpBackend = new HttpInterceptingHandler(backend, injector);
return new ZoneClientBackend(realBackend, doc);
}
export const SERVER_HTTP_PROVIDERS: Provider[] = [
{provide: XhrFactory, useClass: ServerXhr}, {
provide: HttpHandler,
useFactory: zoneWrappedInterceptingHandler,
deps: [HttpBackend, Injector, DOCUMENT]
}
];