78

Update 20140702:

(but I'm marking one of the other answers as accepted instead of my own, as it got me halfway there, and to reward the effort)


It appears that setting a HTTP request header is not possible through links with <a href="...">, and can only be done using XMLHttpRequest.

However, the URL linked to is a file that should be downloaded (browser should not navigate to its URL), and I am not sure is this can be done using AJAX.

Additionally, the file being returned is a binary file, and AJAX is not intended for that.

How would one go about triggering a file download with a HTTP request that has a custom header added to it?

edit: fix broken link

Community
  • 1
  • 1
bguiz
  • 27,371
  • 47
  • 154
  • 243
  • Why do you need a custom request header? You'll have to proxy this server-side. – Brad Jul 01 '14 at 01:49
  • that header is set by the server, not by JS. you can however easily ajax the binary file (except in old IE) and init a download of the blob/string in js. – dandavis Jul 01 '14 at 01:49
  • 1
    @dandavis I need to set a custom header (containing a token) from the client - i.e. the HTTP GET request that the client makes to the server needs to contain the header, otherwise the server will simply return an error code. – bguiz Jul 01 '14 at 02:26
  • @dandavis Coudl you please elaborate on how to AJAX the binary file and init a download of the blob/ string using Javascript? (old IE is not a priority for me at the moment) – bguiz Jul 01 '14 at 02:27
  • 2
    xhr since v2 has supported xhr.responseType = 'blob'; you use url=window.URL.createObjectURL(xhr.response) to turn the blob into a url, set the href of an anchor to that url, set the anchors's download attrib to (ex) 'thefilename.ext', and call anchor.click() to move the file to the client's download folder. you can then call anchor.remove() to cleanup. – dandavis Jul 01 '14 at 02:33
  • 2
    Why don't you just use the `download` attribute if you have a link to a file? – Derek 朕會功夫 Jul 01 '14 at 02:35
  • @dandavis That sounds good. Would you mind posting that as an answer? – bguiz Jul 01 '14 at 03:16
  • @Derek朕會功夫 How would I make that work in conjunction with adding a header to the HTTP request? – bguiz Jul 01 '14 at 03:17
  • @bguiz - Looks like I missed the "custom header" part. However if you don't need to include a custom header, the `download` attribute is enough to download any file. – Derek 朕會功夫 Jul 01 '14 at 03:53
  • Your detailed answer is now a broken link (and the link should have been put in the answer not the question). – Robin Green Nov 11 '15 at 09:05

5 Answers5

62

There are two ways to download a file where the HTTP request requires that a header be set.

The credit for the first goes to @guest271314, and credit for the second goes to @dandavis.

The first method is to use the HTML5 File API to create a temporary local file, and the second is to use base64 encoding in conjunction with a data URI.

The solution I used in my project uses the base64 encoding approach for small files, or when the File API is not available, otherwise using the the File API approach.

Solution:

        var id = 123;

        var req = ic.ajax.raw({
            type: 'GET',
            url: '/api/dowloads/'+id,
            beforeSend: function (request) {
                request.setRequestHeader('token', 'token for '+id);
            },
            processData: false
        });

        var maxSizeForBase64 = 1048576; //1024 * 1024

        req.then(
            function resolve(result) {
                var str = result.response;

                var anchor = $('.vcard-hyperlink');
                var windowUrl = window.URL || window.webkitURL;
                if (str.length > maxSizeForBase64 && typeof windowUrl.createObjectURL === 'function') {
                    var blob = new Blob([result.response], { type: 'text/bin' });
                    var url = windowUrl.createObjectURL(blob);
                    anchor.prop('href', url);
                    anchor.prop('download', id+'.bin');
                    anchor.get(0).click();
                    windowUrl.revokeObjectURL(url);
                }
                else {
                    //use base64 encoding when less than set limit or file API is not available
                    anchor.attr({
                        href: 'data:text/plain;base64,'+FormatUtils.utf8toBase64(result.response),
                        download: id+'.bin',
                    });
                    anchor.get(0).click();
                }

            }.bind(this),
            function reject(err) {
                console.log(err);
            }
        );

Note that I'm not using a raw XMLHttpRequest, and instead using ic-ajax, and should be quite similar to a jQuery.ajax solution.

Note also that you should substitute text/bin and .bin with whatever corresponds to the file type being downloaded.

The implementation of FormatUtils.utf8toBase64 can be found here

bguiz
  • 27,371
  • 47
  • 154
  • 243
  • 1
    Also worth mentioning is the point you make in your excellent [blog post](http://blog.bguiz.com/post/90559955303/file-download-with-http-request-header/), about implementing this server side by creating a temporary download path that does not require a header after the server receives a regular request with the proper headers included. – Michael.Lumley Mar 31 '15 at 14:00
  • Look like that 404's now, I must have broken some aliases when publishing recently. Here's the updated link: http://blog.bguiz.com/2014/07/02/file-download-with-http-request-header/ – bguiz Apr 06 '15 at 15:25
  • Looks like you moved it again, here's the link: http://blog.bguiz.com/2014/07/03/file-download-with-http-request-header. Thanks for the detailed answer! – user3255061 Jun 27 '16 at 13:55
  • 4
    it works, still not very happy, because the file is downloaded silently first and does not appear in the downloads while downloading. – Martin Staufcik Sep 30 '16 at 02:13
  • could you see this link https://stackoverflow.com/questions/48189365/pass-request-header-authorization-with-anchor-tag – Varun Sharma Jan 10 '18 at 14:16
  • Does FileSaver or StreamSaver solve problem of large files? https://github.com/eligrey/FileSaver.js/ https://github.com/jimmywarting/StreamSaver.js – nishantbhardwaj2002 Mar 02 '18 at 05:55
16

Try

html

<!-- placeholder , 
    `click` download , `.remove()` options ,
     at js callback , following js 
-->
<a>download</a>

js

        $(document).ready(function () {
            $.ajax({
                // `url` 
                url: '/echo/json/',
                type: "POST",
                dataType: 'json',
                // `file`, data-uri, base64
                data: {
                    json: JSON.stringify({
                        "file": "data:text/plain;base64,YWJj"
                    })
                },
                // `custom header`
                headers: {
                    "x-custom-header": 123
                },
                beforeSend: function (jqxhr) {
                    console.log(this.headers);
                    alert("custom headers" + JSON.stringify(this.headers));
                },
                success: function (data) {
                    // `file download`
                    $("a")
                        .attr({
                        "href": data.file,
                        "download": "file.txt"
                    })
                        .html($("a").attr("download"))
                        .get(0).click();
                    console.log(JSON.parse(JSON.stringify(data)));
                },
                error: function (jqxhr, textStatus, errorThrown) {
                  console.log(textStatus, errorThrown)
                }
            });
        });

jsfiddle http://jsfiddle.net/guest271314/SJYy3/

Jaeeun Lee
  • 3,056
  • 11
  • 40
  • 60
guest271314
  • 1
  • 15
  • 104
  • 177
  • 2
    @ guest271314 Thanks for your answer, this works well for smaller files, but not so well for larger ones (> 1mb). I'm marking your solution as accepted, because it helped so much. My [actual solution can be found here](http://stackoverflow.com/a/24523253/194982) – bguiz Jul 02 '14 at 05:09
15

I'm adding another option. The answers above were very useful for me, but I wanted to use jQuery instead of ic-ajax (it seems to have a dependency with Ember when I tried to install through bower). Keep in mind that this solution only works on modern browsers.

In order to implement this on jQuery I used jQuery BinaryTransport. This is a nice plugin to read AJAX responses in binary format.

Then you can do this to download the file and send the headers:

$.ajax({
    url: url,
    type: 'GET',
    dataType: 'binary',
    headers: headers,
    processData: false,
    success: function(blob) {
        var windowUrl = window.URL || window.webkitURL;
        var url = windowUrl.createObjectURL(blob);
        anchor.prop('href', url);
        anchor.prop('download', fileName);
        anchor.get(0).click();
        windowUrl.revokeObjectURL(url);
    }
});

The vars in the above script mean:

  • url: the URL of the file
  • headers: a Javascript object with the headers to send
  • fileName: the filename the user will see when downloading the file
  • anchor: it is a DOM element that is needed to simulate the download that must be wrapped with jQuery in this case. For example $('a.download-link').
dgaviola
  • 2,421
  • 2
  • 26
  • 37
  • 2
    Very clean and simple solution! +1 – Theo Orphanos Jul 15 '16 at 20:01
  • Simple and straightforward. and this might be helpful var anchor = $(""); anchor.css("display", "none"); $("body").append(anchor); anchor.prop('href', url); anchor.prop('download', fileName); anchor.get(0).click(); windowUrl.revokeObjectURL(url); anchor.remove(); – Soohwan Park Nov 20 '19 at 02:14
9

i want to post my solution here which was done AngularJS, ASP.NET MVC. The code illustrates how to download file with authentication.

WebApi method along with helper class:

[RoutePrefix("filess")]
class FileController: ApiController
{
    [HttpGet]
    [Route("download-file")]
    [Authorize(Roles = "admin")]
    public HttpResponseMessage DownloadDocument([FromUri] int fileId)
    {
        var file = "someFile.docx"// asking storage service to get file path with id
        return Request.ReturnFile(file);
    }
}

static class DownloadFIleFromServerHelper
{
    public static HttpResponseMessage ReturnFile(this HttpRequestMessage request, string file)
    {
        var result = request.CreateResponse(HttpStatusCode.OK);

        result.Content = new StreamContent(new FileStream(file, FileMode.Open, FileAccess.Read));
        result.Content.Headers.Add("x-filename", Path.GetFileName(file)); // letters of header names will be lowercased anyway in JS.
        result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
        {
            FileName = Path.GetFileName(file)
        };

        return result;
    }
}

Web.config file changes to allow sending file name in custom header.

<configuration>
    <system.webServer>
        <httpProtocol>
            <customHeaders>
                <add name="Access-Control-Allow-Methods" value="POST,GET,PUT,PATCH,DELETE,OPTIONS" />
                <add name="Access-Control-Allow-Headers" value="Authorization,Content-Type,x-filename" />
                <add name="Access-Control-Expose-Headers" value="Authorization,Content-Type,x-filename" />
                <add name="Access-Control-Allow-Origin" value="*" />

Angular JS Service Part:

function proposalService($http, $cookies, config, FileSaver) {
        return {
                downloadDocument: downloadDocument
        };

    function downloadFile(documentId, errorCallback) {
    $http({
        url: config.apiUrl + "files/download-file?documentId=" + documentId,
        method: "GET",
        headers: {
            "Content-type": "application/json; charset=utf-8",
            "Authorization": "Bearer " + $cookies.get("api_key")
        },
        responseType: "arraybuffer"  
        })
    .success( function(data, status, headers) {
        var filename = headers()['x-filename'];

        var blob = new Blob([data], { type: "application/octet-binary" });
        FileSaver.saveAs(blob, filename);
    })
    .error(function(data, status) {
        console.log("Request failed with status: " + status);
        errorCallback(data, status);
    });
};
};

Module dependency for FileUpload: angular-file-download (gulp install angular-file-download --save). Registration looks like below.

var app = angular.module('cool',
[
    ...
    require('angular-file-saver'),
])
. // other staff.
Siarhei Kuchuk
  • 5,296
  • 1
  • 28
  • 31
8

Pure jQuery.

$.ajax({
  type: "GET",
  url: "https://example.com/file",
  headers: {
    'Authorization': 'Bearer eyJraWQiFUDA.......TZxX1MGDGyg'
  },
  xhrFields: {
    responseType: 'blob'
  },
  success: function (blob) {
    var windowUrl = window.URL || window.webkitURL;
    var url = windowUrl.createObjectURL(blob);
    var anchor = document.createElement('a');
    anchor.href = url;
    anchor.download = 'filename.zip';
    anchor.click();
    anchor.parentNode.removeChild(anchor);
    windowUrl.revokeObjectURL(url);
  },
  error: function (error) {
    console.log(error);
  }
});
Eugene Maysyuk
  • 2,977
  • 25
  • 24