Skip to content

Commit

Permalink
fix(http): Use the response content-type to set the blob type. (#…
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
JeanMeche authored and jessicajaniuk committed Nov 13, 2023
1 parent 6a1d4ed commit 7c066a4
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 9 deletions.
9 changes: 5 additions & 4 deletions packages/common/http/src/fetch.ts
Expand Up @@ -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({
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
Expand Down
31 changes: 26 additions & 5 deletions packages/common/http/test/fetch_spec.ts
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}`;
Expand Down

0 comments on commit 7c066a4

Please sign in to comment.