Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node + Angular : download a file with HTTP request

I tried 400 combinations of syntaxes and headers, I can't figure out how to make a HTTP call from Angular to retrieve a file from my NodeJS server.

Found on Stackoverflow and tried, to no avail :

Download file from http post request - Angular 6

How download a file from HttpClient

Download a file from NodeJS Server using Express

How do I download a file with Angular2

It can't be a simple <a download> tag, or a public express.static() folder, because access to the file is restricted and I need to pass a JWT token along (in Node, I have an Express authentication middleware that will reject the request if no token is provided in the headers or if it is invalid).

The file is a GZIP : ./dumps/dump.gz and weighs 812 Kb.

I do manage to download the file, but whatever I try, it weighs 1.4 MB or 94 bytes (wrong size) and can't be opened (7zip can't open file downloads/dump.gz as archive).

What I have tried Angular-side (multiple attempts) :

import { saveAs } from 'file-saver';

let headers = new Headers({
    "Authorization": "Bearer " + user.jwt, // I need this in the headers

    "Content-Type" : "application/octet-stream", // Tried with and without, "application/gzip", "application/json", no difference

    "responseType": "blob" as "json", // Tried with and without, "text", "json", no difference

    "Access-Control-Expose-Headers" : "Content-Disposition" // Tried with and without, no difference
})

this.http
    .get("/download/dump", { headers })
    .toPromise()
    .then(res => {

        const blob = new Blob([res["_body"]] , { type: "application/octet-stream;"} );  // Error : body is not a blob or an array buffer
        // const blob = new Blob([res["_body"]]); // Same result
        // const blob = new Blob([res.blob()]); // Error : body is not a blob or an array buffer

        saveAs(blob, "dump.gz"); // Saves a big corrupted file

        // window.URL.createObjectURL(new Blob(blob, {type: 'blob'})); Saves a 94 byte corrupted file. Tried {type: 'gzip'}, same thing
    })
    .catch(err => console.error("download error = ", err))

What I have tried Node-side (multiple attempts) :

EDIT

Node has been innocented as I could retrieve the file directly from Chrome after disabling authentication. So, the back-end works and the issue is in Angular.

app.get( "/download/dump", authenticate, (req:Request, res:Response) => {
    const file = path.resolve(__dirname, `./dumps/dump.gz`);

    res
        .set({ // Tried with and without headers, doesn't seem to do anything
            "Content-Disposition" : "attachment",  // Tried with and without
            "filename" : "dump.gz", // Tried with and without
            "filename*" : "dump.gz",  // Tried with and without
            "Content-Encoding" : "gzip",  // Tried with and without
            "Content-Type" : "application/gzip"  // Tried with and without, "application/text", "application/json", no difference 
        })
        .sendFile(file); // getting a big corrupted file
        // .download(file); // Same result (big corrupted file)
})
like image 520
Jeremy Thille Avatar asked May 20 '19 10:05

Jeremy Thille


1 Answers

Assuming that you are using the new HttpClient from angular (available since angular 4), this should work

front

import { saveAs } from 'file-saver';
import {HttpHeaders} from "@angular/common/http";

let headers = new HttpHeaders({
    "Authorization": "Bearer " + user.jwt, // Auth header
    //No other headers needed
});

this.http
    .get("/download/dump", { headers, responseType: "blob" }) //set response Type properly (it is not part of headers)
    .toPromise()
    .then(blob => {
        saveAs(blob, "dump.gz"); 
    })
    .catch(err => console.error("download error = ", err))

backend

app.get( "/download/dump", authenticate, (req:Request, res:Response) => {
    const file = path.resolve(__dirname, `./dumps/dump.gz`);
    //No need for special headers
    res.download(file); 
})
like image 105
David Avatar answered Oct 14 '22 03:10

David