我正在建立一个从后端读取数据的网站。该数据即时计算并以缓冲方式发送回客户端。即一旦计算出第一个块,就会将其发送到客户端,然后计算下一个块并将其发送给客户端。整个过程发生在同一个HTTP请求中。客户端不应该等待完成响应完成,而是在发送后立即自己处理每个块。通常可以使用XHR进度处理程序(例如How to get progress from XMLHttpRequest)来消费此类响应。
如何使用RxJS和Observables在Angular2中使用HttpModule消耗这样的响应?
编辑:peeskillet在下面给出了一个非常详细的答案。此外,我还进行了一些挖掘,找到了feature request for the HttpModule
of Angular和StackOverflow question with another approach on how to solve it。
答案 0 :(得分:5)
注意:以下答案仅为POC。它旨在教育Http的体系结构,并提供简单的工作POC实现。我们应该查看XHRConnection
的来源,了解在实施此项目时您应该考虑的其他内容。
当试图实现这一点时,我认为没有任何方法可以直接进入XHR。似乎我们需要提供使用Http
所涉及的一些组件的自定义实现。我们应该考虑的三个主要组成部分是
Connection
ConnectionBackend
Http
Http
将ConnectionBackend
作为其构造函数的参数。在发出请求时,例如get
,Http
会创建与ConnectionBackend.createConnection
的连接,并返回Observable
Connection
属性(从{{1}返回的属性1}})。在最简化的(简化)视图中,它看起来像这样
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;
}
}
,这是POC。为简洁而省略了导入,但在大多数情况下,所有内容都可以从Connection
导入,@angular/http
可以从Observable/Observer
导入。
rxjs/{Type}
以下是我们刚订阅XHR 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();
};
});
}
}
事件。由于progress
会发出整个连接文本,我们只需要XHR.responseText
来获取块,然后通过substring
发出每个chuck。
对于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
可以在Http
source。
@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
代替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
我设置了一个后端端点,它每隔半秒就会在10个块中吐出@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));
}
}
。这就是结果
某处似乎有一个错误。只有 9 :-)。我认为这与服务器端有关。