From gary at devgurus.com Mon Jan 20 16:15:59 2014
From: gary at devgurus.com (Gary Smith)
Date: Tue, 21 Jan 2014 11:15:59 +1100
Subject: [Melbourne-pm] Forcing a download
Message-ID: <52DDBC3F.7020003@devgurus.com>
Hi All,
As this is my first post to the list I'll do a quick bio. I've been
using Perl since v3 - that was for my ISP business where I wrote our
in-house accounting system for dialup clients in Perl back in the late
90's. Since then I'va also used it to do the heavy lifting on a hit
counter, sorting 6-8mil hits per day into a cluster of MySQL servers and
various other smaller jobs.
Fast forward to today and I'm working on a project that is
Catalyst/Postgresql/Starman based and I've struck a problem that is
quite irritating as it's a simple task that looks right but doesn't act
as it should. I'm creating a file and then I want it to pop up a dialog
to download that file. The request is posted via jquery/ajax, the file
is generated and it then sends it back to the browser with some
appropriate headers. Here's the code:
Javascript/JQuery
===========
Pretty straight forward - it posts to the function that is going to
create the export file.
The Catalyst subroutine
===============
sub myexport_export :Chained('object') PathPart('myexport_export') Args(0) {
my ( $self, $c ) = @_;
$self->check_some stuff($c);
my $result = try {
$self->_do_myexport($c);
}
catch {
$c->log->error("things_export: $_");
$c->response->code('500');
$c->response->body("$_");
};
my ($myexport_fh, $myexport_fname) = tempfile();
$myexport_fh->print($result);
close($myexport_fh);
if (-e $myexport_fname and -s $myexport_fname) {
open $myexport_fh, "<". $myexport_fname;
$c->response->header('Content-Type' => 'text/csv');
$c->response->header('Content-Disposition' =>
qq[attachment;filename="export.csv"]);
$c->response->body($myexport_fh);
unlink($myexport_fname);
}
else {
# TODO: Error display here!
}
}
There's a call to _do_myexport in there that extracts and returns some
data, and then we create the tempfile and (hopefully) send it back to
the browser.
The Result
=======
Chrome is telling me that the Content-Type, Length and Disposition
headers are being sent back, but the browser isn't popping up a download
dialog:
1.
Request URL:
http://10.211.55.5:5000/myexport/id/210/myexport_export
2.
Request Method:
POST
3.
Status Code:
200 OK
4. Request Headersview source
1.
Accept:
*/*
2.
Accept-Encoding:
gzip,deflate,sdch
3.
Accept-Language:
en-US,en;q=0.8
4.
Connection:
keep-alive
5.
Content-Length:
6
6.
Content-Type:
application/x-www-form-urlencoded; charset=UTF-8
7.
Cookie:myexport_session=9b8eca81032e0dc3c906d701974ff7e42d841a9f
8.
Host:
10.211.55.5:5000
9.
Origin:
http://10.211.55.5:5000
10.
Referer:
http://10.211.55.5:5000/myexport/id/210/my_button_page
11.
User-Agent:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77
Safari/537.36
12.
X-Requested-With:
XMLHttpRequest
5. Form Dataview sourceview URL encoded
1.
id:
210
6. Response Headersview source
1.
Connection:
keep-alive
2.
Content-Disposition:
attachment;filename="export.csv"
3.
Content-Length:
124
4.
Content-Type:
text/csv
5.
Date:
Tue, 21 Jan 2014 00:01:17 GMT
6.
Set-Cookie:
myexport_session=9b8eca81032e0dc3c906d701974ff7e42d841a9f;
path=/; expires=Tue, 21-Jan-2014 02:01:17 GMT; HttpOnly
7.
X-Catalyst:
5.90053
I have a nasty feeling that I'm missing something obvious here but I've
gone over this so many times that I think/hope a fresh set of eyes will
help find the problem. The data does get sent back to the browser but as
data rather than a file to download.
Regards,
Gary Smith
1.
-------------- next part --------------
An HTML attachment was scrubbed...
URL:
From tjc at wintrmute.net Mon Jan 20 16:50:06 2014
From: tjc at wintrmute.net (Toby Wintermute)
Date: Tue, 21 Jan 2014 11:50:06 +1100
Subject: [Melbourne-pm] Forcing a download
In-Reply-To: <52DDBC3F.7020003@devgurus.com>
References: <52DDBC3F.7020003@devgurus.com>
Message-ID:
On 21 January 2014 11:15, Gary Smith wrote:
> Hi All,
>
> As this is my first post to the list I'll do a quick bio. I've been using
> Perl since v3 - that was for my ISP business where I wrote our in-house
> accounting system for dialup clients in Perl back in the late 90's. Since
> then I'va also used it to do the heavy lifting on a hit counter, sorting
> 6-8mil hits per day into a cluster of MySQL servers and various other
> smaller jobs.
>
> Fast forward to today and I'm working on a project that is
> Catalyst/Postgresql/Starman based and I've struck a problem that is quite
> irritating as it's a simple task that looks right but doesn't act as it
> should. I'm creating a file and then I want it to pop up a dialog to
> download that file. The request is posted via jquery/ajax, the file is
> generated and it then sends it back to the browser with some appropriate
> headers. Here's the code:
Hi Gary,
I noticed some issues in your code; I don't know if any of them are
actually causing the issue you're facing, but it can't hurt to fix
them just in case.
1) If _do_my_export() fails inside the try/catch block, you'll log the
errors but then continue executing the rest of the code that relies
upon $result being set. (except it won't be what you expect -- it'll
be set to the return value from $c->response->body)
2) In some error cases, you'll never clean up your temporary files.
Try using File::Temp->new, which will automatically clean up the file
once the reference goes out of scope.
3) Put the second half of your routine into the try/catch block as
well, and die out of it if you fail to open the file, or the file is
zero bytes, rather than fall through to that empty TODO block. That
way you can get free error handling for it -- and it may well explain
why your code isn't working properly if that's where the error is.
However.. I don't think browsers like accepting file downloads via
ajax requests like that.
Can you try this instead: Make the ajax post query as you are now, but
instead of returning the file data, return a URL that, when accessed,
returns the file data. Then on the webpage, redirect the page to that
URL.
ie. in your $.post() method, the success handler would be something like
function(data) {
if (data.success) { window.document.href = data.url;}
else { show_message_to_user(data.error_message); }
}
Cheers,
Toby
From gary at devgurus.com Mon Jan 20 20:13:00 2014
From: gary at devgurus.com (Gary Smith)
Date: Tue, 21 Jan 2014 15:13:00 +1100
Subject: [Melbourne-pm] Forcing a download
In-Reply-To:
References: <52DDBC3F.7020003@devgurus.com>
Message-ID: <52DDF3CC.5050600@devgurus.com>
Hi Toby,
Thanks for the tips - I'd like to say 'hey, I'm a little rusty but I
would have picked up those issues' ... but I'd be lying other than the
rusty part, so I really appreciate your feedback :). You were right
about the ajax call. I was able to change the code so that it redirects
directly to the URL that provides the download and it's now working
perfectly.
Regards,
Gary
On 21/01/2014 11:50 am, Toby Wintermute wrote:
> On 21 January 2014 11:15, Gary Smith wrote:
>> Hi All,
>>
>> As this is my first post to the list I'll do a quick bio. I've been using
>> Perl since v3 - that was for my ISP business where I wrote our in-house
>> accounting system for dialup clients in Perl back in the late 90's. Since
>> then I'va also used it to do the heavy lifting on a hit counter, sorting
>> 6-8mil hits per day into a cluster of MySQL servers and various other
>> smaller jobs.
>>
>> Fast forward to today and I'm working on a project that is
>> Catalyst/Postgresql/Starman based and I've struck a problem that is quite
>> irritating as it's a simple task that looks right but doesn't act as it
>> should. I'm creating a file and then I want it to pop up a dialog to
>> download that file. The request is posted via jquery/ajax, the file is
>> generated and it then sends it back to the browser with some appropriate
>> headers. Here's the code:
> Hi Gary,
> I noticed some issues in your code; I don't know if any of them are
> actually causing the issue you're facing, but it can't hurt to fix
> them just in case.
>
> 1) If _do_my_export() fails inside the try/catch block, you'll log the
> errors but then continue executing the rest of the code that relies
> upon $result being set. (except it won't be what you expect -- it'll
> be set to the return value from $c->response->body)
>
> 2) In some error cases, you'll never clean up your temporary files.
> Try using File::Temp->new, which will automatically clean up the file
> once the reference goes out of scope.
>
> 3) Put the second half of your routine into the try/catch block as
> well, and die out of it if you fail to open the file, or the file is
> zero bytes, rather than fall through to that empty TODO block. That
> way you can get free error handling for it -- and it may well explain
> why your code isn't working properly if that's where the error is.
>
> However.. I don't think browsers like accepting file downloads via
> ajax requests like that.
> Can you try this instead: Make the ajax post query as you are now, but
> instead of returning the file data, return a URL that, when accessed,
> returns the file data. Then on the webpage, redirect the page to that
> URL.
> ie. in your $.post() method, the success handler would be something like
> function(data) {
> if (data.success) { window.document.href = data.url;}
> else { show_message_to_user(data.error_message); }
> }
>
> Cheers,
> Toby
-------------- next part --------------
An HTML attachment was scrubbed...
URL: