注意:以下答案只是一个 POC。它旨在教育 Http 的体系结构,并提供一个简单的工作 POC 实现。应该查看XHRConnection 的源代码,了解在实现此功能时还应考虑的其他事项。
在尝试实现这一点时,我看不到任何直接利用 XHR 的方法。似乎我们可能只需要提供使用Http 所涉及的一些组件的自定义实现。我们应该考虑的三个主要组成部分是
Connection
ConnectionBackend
Http
Http 将ConnectionBackend 作为其构造函数的参数。发出请求时,例如使用get,Http 将创建与ConnectionBackend.createConnection 的连接,并返回Connection 的Observable 属性(从createConnection 返回)。在最精简(简化)的视图中,它看起来像这样
class XHRConnection implements Connection {
response: Observable<Response>;
constructor( request, browserXhr) {
this.response = new Observable((observer: Observer<Response>) => {
let xhr = browserXhr.create();
let onLoad = (..) => {
observer.next(new Response(...));
};
xhr.addEventListener('load', onLoad);
})
}
}
class XHRBackend implements ConnectionBackend {
constructor(private browserXhr) {}
createConnection(request): XHRConnection {
return new XHRConnection(request, this.broswerXhr).response;
}
}
class Http {
constructor(private backend: ConnectionBackend) {}
get(url, options): Observable<Response> {
return this.backend.createConnection(createRequest(url, options)).response;
}
}
所以知道了这个架构,我们可以尝试实现类似的东西。
对于Connection,这里是 POC。为简洁起见省略了导入,但在大多数情况下,所有内容都可以从 @angular/http 导入,Observable/Observer 可以从 rxjs/{Type} 导入。
export class Chunk {
data: string;
}
export class ChunkedXHRConnection implements Connection {
request: Request;
response: Observable<Response>;
readyState: ReadyState;
chunks: Observable<Chunk>;
constructor(req: Request, browserXHR: BrowserXhr, baseResponseOptions?: ResponseOptions) {
this.request = req;
this.chunks = new Observable<Chunk>((chunkObserver: Observer<Chunk>) => {
let _xhr: XMLHttpRequest = browserXHR.build();
let previousLen = 0;
let onProgress = (progress: ProgressEvent) => {
let text = _xhr.responseText;
text = text.substring(previousLen);
chunkObserver.next({ data: text });
previousLen += text.length;
console.log(`chunk data: ${text}`);
};
_xhr.addEventListener('progress', onProgress);
_xhr.open(RequestMethod[req.method].toUpperCase(), req.url);
_xhr.send(this.request.getBody());
return () => {
_xhr.removeEventListener('progress', onProgress);
_xhr.abort();
};
});
}
}
这里我们只是订阅 XHR progress 事件。由于XHR.responseText 喷出整个连接文本,我们只需substring 获取块,并通过Observer 发出每个卡盘。
对于XHRBackend,我们有以下内容(没什么特别的)。同样,一切都可以从@angular/http 导入;
@Injectable()
export class ChunkedXHRBackend implements ConnectionBackend {
constructor(
private _browserXHR: BrowserXhr, private _baseResponseOptions: ResponseOptions,
private _xsrfStrategy: XSRFStrategy) {}
createConnection(request: Request): ChunkedXHRConnection {
this._xsrfStrategy.configureRequest(request);
return new ChunkedXHRConnection(request, this._browserXHR, this._baseResponseOptions);
}
}
对于Http,我们将对其进行扩展,添加一个getChunks 方法。如果需要,您可以添加更多方法。
@Injectable()
export class ChunkedHttp extends Http {
constructor(protected backend: ChunkedXHRBackend, protected defaultOptions: RequestOptions) {
super(backend, defaultOptions);
}
getChunks(url, options?: RequestOptionsArgs): Observable<Chunk> {
return this.backend.createConnection(
new Request(mergeOptions(this.defaultOptions, options, RequestMethod.Get, url))).chunks;
}
}
mergeOptions 方法可以在Http source 中找到。
现在我们可以为它创建一个模块。用户应直接使用ChunkedHttp 而不是Http。但是因为不尝试覆盖Http 令牌,如果需要,您仍然可以使用Http。
@NgModule({
imports: [ HttpModule ],
providers: [
{
provide: ChunkedHttp,
useFactory: (backend: ChunkedXHRBackend, options: RequestOptions) => {
return new ChunkedHttp(backend, options);
},
deps: [ ChunkedXHRBackend, RequestOptions ]
},
ChunkedXHRBackend
]
})
export class ChunkedHttpModule {
}
我们导入 HttpModule 是因为它提供了我们需要注入的其他服务,但如果我们不需要,我们不想重新实现这些服务。
要测试只需将ChunkedHttpModule 导入AppModule。另外为了测试我使用了以下组件
@Component({
selector: 'app',
encapsulation: ViewEncapsulation.None,
template: `
<button (click)="onClick()">Click Me!</button>
<h4 *ngFor="let chunk of chunks">{{ chunk }}</h4>
`,
styleUrls: ['./app.style.css']
})
export class App {
chunks: string[] = [];
constructor(private http: ChunkedHttp) {}
onClick() {
this.http.getChunks('http://localhost:8080/api/resource')
.subscribe(chunk => this.chunks.push(chunk.data));
}
}
我设置了一个后端端点,它每半秒吐出 10 个块 "Message #x"。这就是结果
某处似乎存在错误。只有九个 :-)。我认为它与服务器端有关。