I am developing a ruby CGI video processing tool and want to redirect the user to another location upon click on the start-ffmpeg-for-an-hour-long-encoding-spree button.
Here is the code:
@cgi.out("status" => "302", "loca开发者_如何学JAVAtion" => @job.report_url) {''}
@cgi.out{''}
@job.start
Doing like this works fine with Safari 5.0.5. But Firefox waits for the script to finish his work before redirecting. And, if your script takes longer to finish than Apache's timeout, it may well be never happening.
I was hoping for a kinf of cgi.close() method. Which exists! But is a CGI::Session method and has nothing to do for me.
Here is another similar question... But not a duplicate! As i need to use stdin, stdout and stderr after the redirect: Returning a response with Ruby CGI before script is finished?
So, how can i send a full CGI response before doing some other tasks with the same script?
The usual way to approach this kind of problem is to seperate the application into two processes.
- The cgi part
- The processing part
Often the processing part is a single instance of an application waiting for messages from numerous cgi parts. The cgi part just submits requests to the processing part. The communication can be done however you like, but is often done using a job/message queue.
This way as soon as the job is submitted to the queue you can redirect the user and the processing part will eventually get around to transcoding your video.
Another advantage is that a heap of simultaneous requests will not overload your machine with dozens of concurrent transcoding operations. You can also move the processing part to one or more machines relatively easily (depending on the form of communication you choose)/
A quick web search will show you dozens of examples.
Some browsers (like Firefox) fill a buffer before processing data.
In home tests, I did the trick by sending 4096 spaces:
@cgi.out("status" => "302", "location" => @job.report_url) { ' ' * 4096 }
@job.start
UPDATE: Here is my complete test code:
#!/usr/bin/ruby
require 'cgi'
# change the line below to test; e.g.: buf = ''
buf = ' ' * 4096
cgi = CGI.new
cgi.out('status' => '302', 'location' => 'http://www.example.com') { buf }
sleep 10
puts 'end'
Obviously, the 'end'
never appears, as browser has been redirected before.
When buf
is empty, Firefox waits for 10s before redirect. When it's "full" (ie, 4K of spaces), the browser redirects immediately. Tested with Firefox 4.0 in Ubuntu 10.04, and Firefox 4.0.1 in Windows Seven.
Well, finally got it.
But it looks more like a hack than a real solution:
@cgi.out("status" => "303", "Connection" => "close", "Content-Length" => 1, "Location" => @job.report_url) {' '}
@job.start
There are two keys things to make it work, both are needed:
- Set
Content-Lenght
to something greater than 0. 1 Works well with one space of content. - Set
Connection
toclose
. This is awkward because this may theoretically works perfectly fine in an HTTP 1.1 Keep Alive connection. But this seems to trigger the page rendering in Firefox 4.
I've switched to a more correct 303 See Other
response after the job submission's POST request (HTTP 303). But this has no effect, it works also well with a 302 response.
Instead of send an empty buffer, try setting Content-Length header to 0. For the off request processing, Kernel#fork should help.
After a little bit of testing. This is what is need to be done. Content-Length is set properly by cgi.out. You only need to pass an empty string to set it to 0.
Also, as i stated in a comment, closing stdout effectively close the connection server side. I tested this code with lighttpd + ree, with firefox and chrome.
require 'cgi'
cgi = CGI.new
cgi.out('status' => '302', 'location' => 'http://www.google.com') { "" }
$stdout.close
# here we do a very very long task
sleep 30
exit 0
精彩评论