I am doing an angular application where i have to hit a rest end point and download the file that is sent as the response but i am not understanding how to go about this. I have the response header like the following
Content-Disposition:attachment; filename="Config.zip" Content-Type:multipart/mixed;boundary=Boundary_25_1816124633_1519993185650 MIME-Version:1.0 Transfer-Encoding:chunked
and the response looks like
--Boundary_25_1816124633_1519993185650 Content-Type: application/json
{"config":[{},{},{}]} --Boundary_25_1816124633_1519993185650 Content-Type: application/octet-stream
PKMÛJAä;%RecurrenceEvent_CreateContract_1.jsoníYKoãF¾ÈxÝ0è÷Ã7Mb L&íÝK0ͦCD¬1ðß(J¤HÙ²¼yV'»ÙU¬®úªú«â· ö«åºv~\Í~ùöýw³Ù,È«ù
EDIT
this is my http call thats ging to the backend
return this.http.get(url).map(response => {
// TODO
});
How to download the zip file that is being attached ? Please help. I am stuck.
I am doing an angular application where i have to hit a rest end point and download the file that is sent as the response but i am not understanding how to go about this. I have the response header like the following
Content-Disposition:attachment; filename="Config.zip" Content-Type:multipart/mixed;boundary=Boundary_25_1816124633_1519993185650 MIME-Version:1.0 Transfer-Encoding:chunked
and the response looks like
--Boundary_25_1816124633_1519993185650 Content-Type: application/json
{"config":[{},{},{}]} --Boundary_25_1816124633_1519993185650 Content-Type: application/octet-stream
PKMÛJAä;%RecurrenceEvent_CreateContract_1.jsoníYKoãF¾ÈxÝ0è÷Ã7Mb L&íÝK0ͦCD¬1ðß(J¤HÙ²¼yV'»ÙU¬®úªú«â· ö«åºv~\Í~ùöýw³Ù,È«ù
EDIT
this is my http call thats ging to the backend
return this.http.get(url).map(response => {
// TODO
});
How to download the zip file that is being attached ? Please help. I am stuck.
Share Improve this question edited Mar 4, 2018 at 17:13 Vikhyath Maiya asked Mar 2, 2018 at 12:24 Vikhyath MaiyaVikhyath Maiya 3,1923 gold badges37 silver badges71 bronze badges 4- Please put what you have already tried. And also what do you want to do with the zip after you get it in angular? – Alex Michailidis Commented Mar 4, 2018 at 17:05
- what i have tried ! well i have got the response and i am not sure how to proceed , so i dont have anything to show you i guess ... once i get the zip i would just like to download the file using fileSaver .... – Vikhyath Maiya Commented Mar 4, 2018 at 17:07
- That seems to me that you have code, please post your code in the question. I am intrested in what options you passed in your ajax call. – Alex Michailidis Commented Mar 4, 2018 at 17:10
- Well its a get call, so havent passsed any, i dono if i have to as wel.. Edited my question, which shows the http call thats going ..Should it be anything else ... i am literally clueless how file downloads work !! – Vikhyath Maiya Commented Mar 4, 2018 at 17:14
4 Answers
Reset to default 12 +25const executeSaveAs = (content) => {
let blob = new Blob([content], {'type': "application/octet-stream"});
saveAs(blob, "downloaded_zip.zip"); // This is from https://github.com/eligrey/FileSaver.js
};
return this.http.get(url, {responseType: 'arraybuffer'}).pipe(executeSaveAs);
We need to set the expected response type, which we'd want to be 'arraybuffer'. Then, we do the usual stuff for FileSaver, that is creating a blob and passing it to the package's saveAs
function.
Edit
As per the comment, a clarification was asked for parsing the various parts of the multipart response.
Info about the Multipart content type
The boundary, as defined in the header, indicates the different parts of the response. Each of the parts are preceded by the line
--boundaryThatIsInTheHeader
Method
We can split the response as per the boundary. To do this, we must first parse the boundary from the header. RegEx to our rescue here:
let boundaryRegex = new RegExp(/boundary=(\S+)/g);
const header = `Content-Disposition:attachment; filename="Config.zip" Content-Type:multipart/mixed;boundary=Boundary_25_1816124633_1519993185650 MIME-Version:1.0 Transfer-Encoding:chunked`; // As in the question
const boundary = '--' + boundaryRegex.exec(header)[1]; // We obtain the boundary here
Now, we need to split the response with the boundary as the delimiter.
response.split(boundary);
The value returned for this specific response of the server would be
[ "", " Content-Type: application/json\n\n {\"config\":[{},{},{}]} ", " Content-Type: application/octet-stream\n\n PKMÛJAä;%RecurrenceEvent_CreateContract_1.jsoníYKoãF¾ÈxÝ0è÷Ã7Mb L&íÝK0ͦCD¬1ðß(J¤HÙ²¼yV'»ÙU¬®úªú«â· ö«åºv~Í~ùöýw³Ù,È«ù"]
Notice the second element of the array, that's the JSON. That's what we desired! We can now remove the extra data, i.e., content type, by using simple RegEx. If the format of the response is constant, we can also directly remove it as per the index. Same is the case for the zip file's content.
I believe multipart/mixed
and multipart/form-data
share some common structure.
(not exactly sure what the difference is.)form-data
is mostly used for sending forms to the server but could as well be used the other way around.
the fetch api has a method called .formData()
This is mainly relevant to service workers. If a user submits a form and a service worker intercepts the request, you could for example call formData() on it to obtain a key-value map, modify some fields, then send the form onwards to the server (or use it locally).
So if we can get the response and change the content-type
header to multipart/form-data
we could utilize the fetch api to read the content without having to parse it.
getExampleResponse(async res => {
// replace the content-type to multipart/form-data so fetch api can parse it
const type = res.headers.get('content-type')
res.headers.set('content-type', type.replace('mixed', 'form-data'))
// return the response as a formData
const fd = await res.formData()
// console.log(...fd)
console.log(JSON.parse(fd.get('json')))
console.log(fd.get('image'))
const file = fd.get('image')
const link = document.createElement('a')
const image = new Image
link.href = image.src = URL.createObjectURL(file)
link.innerText = 'download ' + (link.download = file.name)
// saveAs(file); // This is from https://github.com/eligrey/FileSaver.js
document.body.appendChild(image)
document.body.appendChild(link)
})
/*
Don't mind this, it's just an example response you are getting...
What you actually want is something like
function getExampleResponse(cb) {
fetch(url).then(cb)
}
*/
function getExampleResponse(e){var a=document.createElement("canvas"),d=a.getContext("2d");d.fillStyle="blue";d.fillRect(0,0,a.width,a.height);a.toBlob(function(b){var a={a:123};var c='--Boundary_25_1816124633_1519993185650\r\nContent-Disposition: form-data; name="json"\r\nContent-Type: application/json\r\n'+("Content-Length: "+JSON.stringify(a).length+"\r\n\r\n");c+=JSON.stringify(a)+"\r\n";c=c+'--Boundary_25_1816124633_1519993185650\r\nContent-Disposition: form-data; name="image"; filename="image.png"\r\nContent-Transfer-Encoding: binary\r\n'+
("Content-Type: "+b.type+"\r\n");c+="Content-Length: "+b.size+"\r\n\r\n";b=new Blob([c,b,"\r\n--Boundary_25_1816124633_1519993185650--\r\n"]);e(new Response(b,{headers:{"Content-Type":"multipart/mixed; boundary=Boundary_25_1816124633_1519993185650"}}))})};
Yes! I've been looking for this for so long, probably should have figured it out sooner. thought I'd add it the thread.
var boundIndex = res.headers['content-type'].indexOf('boundary=') + 'boundary='.length;
var bound = '--' + res.headers['content-type'].slice(boundIndex, res.headers['content-type'].length);
var emanData = res.data.split(bound);
My response included a JSON part and Zip part which can then be saved to disk.
Thanks!
You can use file-saver package to make it clearer in code. Then service method for fetching data from back-end:
getFile(fileName: string): Observable<Blob> {
return this.http.get(`${environment.apiServerUrl}/` + fileName, {responseType: 'blob'})
.catch(error => this.handleErrorResponse() // catch http error);
}
Then call function on browers event
this.service.getFile('filenameInServer.zip')
.subscribe(fileData => FileSaver.saveAs(fileData, 'sugestedFileNameInDownloadDialog.zip'));