Rails can't handle binary file upload?

Hi,

I am trying to upload local file to a rails-app through HTTP
without multipart encoding. Doing so, I expect to get some
performance boost on transferring large files. I am stucked
Rails can't handle binary file upload, while it's OK with plain
text files. Let me show a sample application skeleton:

config/routes.rb:

map.connect 'upload', :controller => 'application', :action => 'upload'

app/controllers/application.rb:

class ApplicationController < ActionController::Base
def upload
   render :text => request.raw_post
end
end

Uploading plain file succeeds as following:

$ curl -v -T 42.txt http://localhost/upload
* About to connect() to localhost port 80
* Trying 127.0.0.1... * connected
* Connected to localhost (127.0.0.1) port 80

PUT /upload HTTP/1.1

User-Agent: curl/7.13.1 (powerpc-apple-darwin8.0) libcurl/7.13.1
OpenSSL/0.9.7i zlib/1.2.3
Host: localhost
Pragma: no-cache
Accept: */*
Content-Length: 3
Expect: 100-continue

< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Date: Mon, 25 Sep 2006 18:51:11 GMT
< Server: Mongrel 0.3.13.4
* Added cookie _session_id="b64a0dde1c857bd0d94ea95869f90294" for
domain localhost, path /, expire 0
< Set-Cookie: _session_id=b64a0dde1c857bd0d94ea95869f90294;
domain=localhost; path=/
< Status: 200 OK
< Cache-Control: no-cache
< Content-Type: text/html; charset=UTF-8
< Content-Length: 3
42
* Connection #0 to host localhost left intact
* Closing connection #0

Uploading binary file fails like this:

$ curl -v -T photo1.jpg http://localhost/upload
* About to connect() to localhost port 80
* Trying 127.0.0.1... * connected
* Connected to localhost (127.0.0.1) port 80

PUT /upload HTTP/1.1

User-Agent: curl/7.13.1 (powerpc-apple-darwin8.0) libcurl/7.13.1
OpenSSL/0.9.7i zlib/1.2.3
Host: localhost
Pragma: no-cache
Accept: */*
Content-Length: 60591
Expect: 100-continue

< HTTP/1.1 100 Continue
< HTTP/1.1 200 OK
< Date: Mon, 25 Sep 2006 18:52:15 GMT
< Content-Type: text/html
< Content-Length: 380
Status: 500 Internal Server Error
Content-Type: text/html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<body>
<h1>Application error (Apache)</h1>
<p>Change this error message for exceptions thrown outside of an
action (like in Dispatcher setups or broken Ruby code) in
public/500.html</p>
</body>
* Connection #0 to host localhost left intact
* Closing connection #0
</html>

I've written small cgi application to prove it might perform better,
and it doesn't show any problem. So I would like to apply my idea
to Rails application too. Comments are appreciated.

Rails probably can't, but you could go straight to source and use a Mongrel handler. Here's the shell to get you started:

class BinaryUploadHandler < Mongrel::HttpHandler

  def process(req, resp)
    # req.body at this point is a IO object, either StringIO or Tmpfile
  end
end

uri "/uploadstuff", :handler => BinaryUploadHandler.new

Put that in a mongrel.conf and start mongrel with:

mongrel_rails start -e production -S mongrel.conf

Then you're job is to fill in the process(req,resp) method to take the req.body and either move it or copy it to where you want. You also have access to headers and other goodies and can do responses.

Check out the code to mongrel_upload_progress (gem install mongrel_upload_progress) for a good example of doing this and also tracking the upload. You could probably just modify that gem and be done with 80% of the work already.

Hi, Zed.

I had already implemented with standalone mongrel-powered server
which rely on a number of existing rails models. However, I'd like to
handle file-upload on my rails-app, because well... here is rails list!? :slight_smile:

Another question: mongrel can handle 100 Expect request?
FYI, here's my sample CGI application:

if ENV['HTTP_EXPECT'] == '100-Continue'
  puts "Status: HTTP 100 Continue"
  content = STDIN.read(ENV['CONTENT_LENGTH'].to_i)
  File.open('upload.data', 'w').write(content)
end
puts "Content-Type: text/html"
puts
puts "Done."