Reputation: 44393
I receive a multipart response body in Angular but the application is not handling the response correctly. It turns out the HttpClient in Angular is not able to parse multipart response bodies correctly (see this issue on GitHub).
The HttpClient
simply returns the raw multipart response inside the body of the HttpResponse
object while I would like to have the application/json
block from the multipart response accessible inside my HttpResponse
object.
How can I handle multipart response inside Angular correctly?
Upvotes: 2
Views: 3744
Reputation: 44393
I made a quick and dirty solution heavily inspired by this other post on stackoverflow and created an http interceptor class that shows how parsing multipart responses can be done.
The interceptor returns the the first 'application/json' part as the response body from a multipart response (multipart/mixed
, multipart/form-data
or multipart/related
).
Through a map additional parsers for other content types can be easily added to the class.
I will share this code here it might be an inspiration others:
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root',
})
export class MultipartInterceptService implements HttpInterceptor {
private parserMap = {
'application/json': JSON.parse,
};
private parseMultipart(multipart: string, boundary: string): any {
const dataArray: string[] = multipart.split(`--${boundary}`);
dataArray.shift();
dataArray.forEach((dataBlock) => {
const rows = dataBlock.split(/\r?\n/).splice(1, 4);
if (rows.length < 1) {
return;
}
const headers = rows.splice(0, 2);
const body = rows.join('');
if (headers.length > 1) {
const pattern = /Content-Type: ([a-z\/+]+)/g;
const match = pattern.exec(headers[0]);
if (match === null) {
throw Error('Unable to find Content-Type header value');
}
const contentType = match[1];
if (this.parserMap.hasOwnProperty(contentType) === true) {
return this.parserMap[contentType](body);
}
}
});
return false;
}
private parseResponse(response: HttpResponse<any>): HttpResponse<any> {
const contentTypeHeaderValue = response.headers.get('Content-Type');
const body = response.body;
const contentTypeArray = contentTypeHeaderValue.split(';');
const contentType = contentTypeArray[0];
switch (contentType) {
case 'multipart/related':
case 'multipart/mixed':
case 'multipart/form-data':
const boundary = contentTypeArray[1].split('boundary=')[1];
const parsed = this.parseMultipart(body, boundary);
if (parsed === false) {
throw Error('Unable to parse multipart response');
}
return response.clone({ body: parsed });
default:
return response;
}
}
// intercept request and add parse custom response
public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(request)
.pipe(
map((response: HttpResponse<any>) => {
if (response instanceof HttpResponse) {
return this.parseResponse(response);
}
}),
);
}
}
The code reads the boundary from the Content-Type
response header and splits the response body in blocks using this boundary. Then it tries to parse each part and returns the first successfully parsed application/json
block from the response.
You would need your own parser or change the logic in case you are interested in returning another code block or in case you want to combine several code blocks in the final response. This would need some customization of the code.
NOTE: This code is experimental and limited tested so far from production ready, be careful when using it.
Upvotes: 4