6

I am using AngularJs with a REST API. I don't have the hand on the REST API. I can store digital object with the API by sending a REST request. I can get it also with a GET request. The requests needs to have some specific headers.

My goal is to give the user a "download and save as" link. For now on the click event i make the request :

    this.file = function (file) {
        var url = config.domain + 'file/' + file;

        var methods = resource(url, null, { 
            'get': {
                method:'GET', 
                headers:{   'Authorization' : user.auth, 
                            'secret-key' : user.secretkey}
            }
            transformResponse : function(data, headersGetter){
                                    return {content:data}; //transform octet stream into text, angular returns an array containing 1 character per element.
                                },
        });
        return methods;
    };

in the return body I have the file content (see below). I would like to download it. How is it possible ? Notice that I can't store the file as a URL.

Would it be possible to open a window wich make the rest call with the good headers and save the file ?

EDIT

I need the solution to be able to work well with a 50Mo File.

example of a PDF file content I have :

%PDF-1.7
£´ÅÖçø
2 0 obj
[/ICCBased 3 0 R]
endobj
3 0 obj
<<
/Filter /FlateDecode 
/Length 2596 
/N 3 
>>
stream
xwTSÙϽ7½PÐkhRH
½H.*1   JÀ"6DTpDQ¦2(à£C±"Q±ëDÔqpId­ß¼yïÍß÷~k½ÏÝgï}ÖºüÂLX    ¡XáçÅg`ðlàp³³BøF|Ølø½º         ùû*Ó?Áÿ¹Y"1PçòøÙ\É8=W%·Oɶ4MÎ0JÎ"Y2Vsò,[|öe9ó2<ËsÎâeðäÜ'ã9¾`çø¹2¾&ctI@Æoä±|N6(Ü.æsSdl-c(2-        ãyàHÉ_ðÒ/XÌÏËÅÎÌZ.$§&\SáÏÏMçÅÌ07#â1ØYárfÏüYym²";Ø8980m-m¾(Ô]ü÷v^îDøÃöW~
°¦eµÙúmi]ëP»ýÍ`/²¾u}qº|^RÄâ,g+«ÜÜ\Kk)/èïúC_|ÏR¾Ýïåaxó8t1C^7nfz¦DÄÈÎâpùæøþuü$¾/ED˦L         Lµ[ÈB@øøÃþ¤Ù¹ÚøÐX¥!@~(*     {d+Ðï}ÆGùÍÑûÏþ}W¸LþÈ$cGD2¸QÎìüZ4 E@ê@èÀ¶À¸àA(q`1àD µ ­`'¨u     46ptcà48.Ë`ÜR0)ð
Ì@ÈRt CȲXäCP%CBH@ë R¨ªê¡fè[è(tº
C· Qhúz#0   ¦ÁZ°l³`O8ÁÉð28.·Àp|îOÃàX
?§:¢0ÂFBx$  !«¤i@Ú¤¹H§È[EE1PLÊ⢡V¡6£ªQP¨>ÔUÔ(j
õMFk¢ÍÑÎèt,:.FW Ðè³èô8ú¡c1L&³³³Ó9Æa¦±X¬:Öë
År°bl1¶
{{{;}#âtp¶8_\<N+ÄU
[.....]
BastienSander
  • 1,718
  • 4
  • 24
  • 50
  • What I don't understand is why? Why do you need to send it back as text instead of adding an attachment header? Do your client edit the files? (I assume you have SPA clients running in the browser.) You can use data URLs for streaming PDF if you want to display it in browser. http://stackoverflow.com/questions/12848616/streaming-pdf-into-iframe-using-dataurl-works-only-in-chrome – inf3rno Jun 02 '14 at 10:08
  • http://stackoverflow.com/questions/10473932/browser-html-force-download-of-image-from-src-dataimage-jpegbase64 This is how you can force a download from the browser. If you edit your binary files from there. But adding `/files/xxx?aspect=attachment` as a representation with forced download is much better if you store them on the server. – inf3rno Jun 02 '14 at 10:11
  • @inf3rno I didn't precised it but the thing is I cannot use data-url since this is not the way the files are stored. – BastienSander Jun 02 '14 at 10:41
  • I actually have some headers returned but because it is a rest call proceed by angular the browser doesn't receive directly the response. – BastienSander Jun 02 '14 at 10:44
  • As far as I know angular is a client side js lib, which sends ajax requests to the REST API, so the browser should receive the response directly. – inf3rno Jun 02 '14 at 12:24
  • Maybe I don't understand something but actually The response of an ajax request is received into a JS variable but not by the browser as a HTTP request would be. So even with the rights headers etc it would not force the download since only javascript interpret the result. Am I wrong ? – BastienSander Jun 02 '14 at 12:51
  • Ohh, yes, I was wrong. You can do something like this instead: `var iframe = document.createElement("iframe"); document.body.appendChild(iframe); iframe.contentDocument.location.href = "/files/x.pdf?aspect=attachment";`. This is still a GET, so it is RESTful. – inf3rno Jun 02 '14 at 14:08
  • Or just simply `document.location.href = "/resources/xxxx?aspect=attachment"`. It won't navigate away if you send back the file with the `Content-Disposition: attachment; filename=x.pdf` header. – inf3rno Jun 02 '14 at 14:15
  • 2
    The problem is that I need to give specific headers in the REST request as I said in the my question. It is impossible to do so with a simple HTTP request... – BastienSander Jun 02 '14 at 14:17
  • I guess security headers... :S – inf3rno Jun 02 '14 at 14:20
  • Yes. I really can't access the data without those headers... So It is mandatory to use REST request... – BastienSander Jun 02 '14 at 14:21
  • http://stackoverflow.com/questions/12955266/ajax-call-to-download-file-returned-from-restful-service You really cannot download it with AJAX, it's a security issue... – inf3rno Jun 02 '14 at 14:23
  • The only way to store security information in the URL. You have to sign your request. First you have to send the file name and the credentials and maybe other meta data to a security service, after that it will send back a signature. You append the user id and that signature to the file download URL and check it on the server before the file download. The token should work only by a single request. – inf3rno Jun 02 '14 at 14:31
  • Other approach can be cert based access control. – inf3rno Jun 02 '14 at 14:36
  • As I said, I don't have any hand on the API. I will reconsider how to do... – BastienSander Jun 02 '14 at 14:47
  • Then I think the only way to send those files through the server side of your REST client domain and attach them from there. Interesting question btw... Pls tell us if you have a solution. – inf3rno Jun 02 '14 at 14:48
  • Yes I tell the solution i choosed. Which choice I will make to solve the issue... – BastienSander Jun 02 '14 at 14:53
  • Can you use FileSaver.js for this purpose? (https://github.com/eligrey/FileSaver.js) It can save file which is stored in javascript in Blob. – dIsoVi Jun 03 '14 at 09:37
  • Yes but it is not stored in blob. It is directly the file – BastienSander Jun 03 '14 at 09:54
  • What about adding "content-type: application/pdf" to the header. ah! No hands on the API... ugly problem. Maybe creating a document with that content and object.contentType... – miguel-svq Jun 03 '14 at 11:13
  • @BastienSander sorry, but I don't understand what do you mean by directly a file. I guess it is string, so you can easily create Blob from string. – dIsoVi Jun 03 '14 at 12:03
  • One character per element in the array seem pretty nice binary, https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data Probably you don't need to transform it – miguel-svq Jun 03 '14 at 12:11
  • Did you mean by "I can't store the file as an URL", that you cannot use something like this? http://stackoverflow.com/questions/4485182/force-download-for-blob-created-with-filewriter-in-javascript If not, then I guess I misunderstood... Here is a working example how to force download with the file API: https://github.com/eligrey/FileSaver.js – inf3rno Jun 09 '14 at 14:32

4 Answers4

5

I think you could using blob, something like

var content=...the content of your request;
var mypdf = new Blob(content, {type : 'application/pdf'});

and check answer from "panzi" in this other question Using HTML5/Javascript to generate and save a file

(One character per element in the array seem pretty nice binary. Probably you don't need to transform it. https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data )

Community
  • 1
  • 1
miguel-svq
  • 2,136
  • 9
  • 11
  • 1
    I tried this, and with the result of a readAsBinaryString, i get : Uncaught TypeError: Failed to construct 'Blob': The 1st argument is neither an array, nor does it have indexed properties. – BastienSander Jun 03 '14 at 12:18
  • content should be an array, so in your case var mypdf = new Blob([content], {type : 'application/pdf'}); – dIsoVi Jun 03 '14 at 12:20
  • Check the developer.mozilla.org page. Content have to be an array, you have different ways to do it depending on your target browsers. – miguel-svq Jun 03 '14 at 12:21
  • it works with text/plain, but doesn't with zip (file couldn't be open as archive) and neither with pdf (empty content) – BastienSander Jun 04 '14 at 13:59
  • Check `oReq.onload = function (oEvent) {` in the first example on the developer.mozilla.org link. That's the array (it's a Uint8Array) you have to provide to the blob to create the binary file. – miguel-svq Jun 04 '14 at 14:09
  • use [content] instead of content – dandavis Jun 06 '14 at 20:26
  • It's giving corrupted pdf – vivex Jan 11 '16 at 06:24
2

Maybe you could do something like this?

var a = document.createElement('a');
a.href = 'data:attachment/pdf,' + encodeURI(data);
a.target = '_blank';
a.download = 'filename.pdf';
a.click();

You'd just have to make sure that data was in the correct format, which IIRC is base64.

Adam K Dean
  • 7,387
  • 10
  • 47
  • 68
2

you can use this instead of above code :

var url = config.domain + 'file/' + file;
   var parameters = "Authorization=" + user.auth +
                    "&secret-key=" + user.secretkey;
   var reportParameters = url + encodeURIComponent(parameters);
   window.location.assign(reportParameters);
Mukund Kumar
  • 21,413
  • 18
  • 59
  • 79
2

Thanks everybody for helping find a solution.

I am not able to find a satisfying solution in javascript and client side application. I am going to make a proxy class communicating with the API.

This will send the REST request with security headers and will give as response the file content.

This class will be called by a HTTP GET request, so the 'save as' process will be managed easily thanks to right response headers.

BastienSander
  • 1,718
  • 4
  • 24
  • 50
  • 1
    Take a look at this blog post, it works well for the small file sizes. Troublesome for large files though: http://blog.bguiz.com/post/90559955303/file-download-with-http-request-header/ – Majid Oct 01 '15 at 18:50