Search code examples
ajaxweb-servicesgoogle-app-enginewebwebapp2

Allow user to download a generated pdf file after uploading a file


I'm trying to allow a user to upload a 15 MB file to my web, from there that file should be posted to a web service of mine, receive the response (a pdf file) and serve that to the user so he can download it.

However, I'm ending in a URL like this with no prompt to download anything, just a 404 error: http://localhost:10080/Download?file=%PDF-1.4%%EF%BF%BD%EF%B (etc)

Some points:

  • First I will compress the file
  • Because of the previous point, I'm using Ajax to post the file

Ajax code

    $("#file").on("change", function(evt) {

        var files = evt.target.files;

        /*  Create zip file representation */
        var zip = new JSZip();
        /* Name, content */
        zip.file("data.zip", files[0]);

        zip.generateAsync({
            compression: 'DEFLATE',
            type: 'blob'
        }).then(function(zc) { // Function called when the generation is complete
            /* Create file object to upload */
            var fileObj = new File([zc], "compressed-data");

            /* form data oject */
            var formData = new FormData();
            /* $('#file')[0].files[0] */
            formData.append('attachments', fileObj);

            $.ajax({
                type: "POST",
                url: $("form#data").attr("action"),
                data: formData,
                contentType: false,
                processData: false,
                success: function (returnValue, textStatus, jqXHR ) {
                    window.location = '/Download?file=' + returnValue;
                }
            })

Python Web Code

def post(self):
    attachments = self.request.POST.getall('attachments')
    #handle the attachment
    _attachments = [{'content': f.file.read(),
                     'filename': f.filename} for f in attachments]

    # Use the App Engine Requests adapter. This makes sure that Requests uses
    # URLFetch.
    requests_toolbelt.adapters.appengine.monkeypatch()

    #web service url
    url = 'http://localhost:8080'

    files = {'attachments': _attachments[0]["content"]}

    resp = requests.post(url, files=files)

    self.response.headers[b'Content-Type'] = b'application/pdf; charset=utf-8'
    self.response.headers[b'Content-Disposition'] = b'attachment; filename=report.pdf'
    self.response.out.write(resp.content)

Python Web Service Code

@app.route('/', methods=['POST'])
def hello():

    #the attached ZIPPED raw data
    f = request.files["attachments"]
    #to unzip it
    input_zip = ZipFile(f)
    #unzip
    data = [input_zip.read(name) for name in input_zip.namelist()]

    generator = pdfGenerator(io.BytesIO(data[0]))
    return generator.analyzeDocument()

The pdf generator uses Reportlab, writes the pdf over a io.BytesIO() and returns it with output = self.buff.getvalue()

1.- What am I doing wrong with the window location thing?

2.- Am I doing something wrong with the file type?

I'm two days into this, now I need help.

Thanks.


Solution

  • However, I'm ending in a URL like this with no prompt to download anything, just a 404 error: http://localhost:10080/Download?file=%PDF-1.4%%EF%BF%BD%EF%B (etc)

    That's what you're asking in window.location = '/Download?file=' + returnValue;, you redirect the user to /Download.

    When you call your service, you should ask for a Blob response. Then, use saveAs (or FileSaver.js, a polyfill) to trigger the download.

    As far as I know, $.ajax doesn't let you download binary content out of the box (it will try to decode your binary from UTF-8 and corrupt it). Either use a jQuery plugin (like jquery.binarytransport.js) or use a xhr directly.

    $.ajax({
        type: "POST",
        url: $("form#data").attr("action"),
        data: formData,
        contentType: false,
        processData: false,
        dataType: 'binary',                       // using jquery.binarytransport.js
        success: function (returnValue, textStatus, jqXHR ) {
            // Default response type is blob
            saveAs(returnValue, "result.pdf");    // using FileSaver.js
        }
    })