fix(http): Use the response content-type to set the blob type. (#52840)

When downloading a PDF with the fetch client, the blob had no content. It couldn't be displayed in an iframe. This commit fixes this.

Relate to: https://stackoverflow.com/questions/77470626/possible-bug-in-httpclient-when-using-the-blob-data-type

PR Close #52840
This commit is contained in:
Matthieu Riegler
2023-11-13 00:23:02 +01:00
committed by Jessica Janiuk
parent 6a1d4ed667
commit 7c066a4af4
2 changed files with 31 additions and 9 deletions
+5 -4
View File
@@ -145,7 +145,8 @@ export class FetchBackend implements HttpBackend {
// Combine all chunks.
const chunksAll = this.concatChunks(chunks, receivedLength);
try {
body = this.parseBody(request, chunksAll);
const contentType = response.headers.get('Content-Type') ?? '';
body = this.parseBody(request, chunksAll, contentType);
} catch (error) {
// Body loading or parsing failed
observer.error(new HttpErrorResponse({
@@ -193,8 +194,8 @@ export class FetchBackend implements HttpBackend {
}
}
private parseBody(request: HttpRequest<any>, binContent: Uint8Array): string|ArrayBuffer|Blob
|object|null {
private parseBody(request: HttpRequest<any>, binContent: Uint8Array, contentType: string): string
|ArrayBuffer|Blob|object|null {
switch (request.responseType) {
case 'json':
// stripping the XSSI when present
@@ -203,7 +204,7 @@ export class FetchBackend implements HttpBackend {
case 'text':
return new TextDecoder().decode(binContent);
case 'blob':
return new Blob([binContent]);
return new Blob([binContent], {type: contentType});
case 'arraybuffer':
return binContent.buffer;
}
+26 -5
View File
@@ -217,6 +217,16 @@ describe('FetchBackend', async () => {
expect(res.body!.data).toBe('some data');
});
it('handles a blob with a mime type', async () => {
const promise = trackEvents(backend.handle(TEST_POST.clone({responseType: 'blob'})));
const type = 'aplication/pdf';
fetchMock.mockFlush(HttpStatusCode.Ok, 'OK', new Blob(), {'Content-Type': type});
const events = await promise;
expect(events.length).toBe(2);
const res = events[1] as HttpResponse<Blob>;
expect(res.body?.type).toBe(type);
});
it('emits unsuccessful responses via the error path', done => {
backend.handle(TEST_POST).subscribe({
error: (err: HttpErrorResponse) => {
@@ -400,12 +410,18 @@ export class MockFetchFactory extends FetchFactory {
return this.promise;
}
mockFlush(status: number, statusText: string, body?: string): void {
mockFlush(
status: number, statusText: string, body?: string|Blob, headers?: Record<string, string>):
void {
this.clearWarningTimeout?.();
this.response.setupBodyStream(body);
const response =
new Response(this.response.stream, {statusText, headers: this.response.headers});
if (typeof body === 'string') {
this.response.setupBodyStream(body);
} else {
this.response.setBody(body);
}
const response = new Response(
this.response.stream,
{statusText, headers: {...this.response.headers, ...(headers ?? {})}});
// Have to be set outside the constructor because it might throw
// RangeError: init["status"] must be in the range of 200 to 599, inclusive
@@ -470,6 +486,11 @@ class MockFetchResponse {
},
});
public setBody(body: any) {
this.sub$.next(body);
this.sub$.complete();
}
public setupBodyStream(body?: string) {
if (body && this.progress.length) {
this.headers['content-length'] = `${body.length}`;