views:

258

answers:

2

What I want to do is use a CGI script (written in Ruby) to read a binary file off of the filesystem (audio, specifically) and stream it to the browser.

This is how I am doing that so far,

require 'config'

ENV['REQUEST_URI'] =~ /\?(.*)$/
f= $config[:mdir] + '/' + $1.url_decode
f = File.open f, 'rb'

print "Content-Type: audio/mpeg\r\n"#TODO: Make it guess mime type
print "\r\n"

#Outputs file
while blk = f.read(4096)
    $stdout.puts blk
    $stdout.flush
end

f.close

It's not perfect, there are security holes (exposing the whole filesystem..), but it just isn't working right. It's reading the right file, and as far as I can tell, outputting it in 4k blocks like it should. Using Safari, if I go to the URL, it will show a question mark on the audio player. If I use wget to download it, it appears to work and is about the right size, but is corrupted. It begins playing fine, then crackles, and then stops.

How should I go about doing this? Do I need to Base-64 encode, if can I do that without loading the whole file into memory in one go?

Btw, this is only going to be used over local area network, and I want easy setup, so I'm not interested in a dedicated streaming server.

+1  A: 

puts is for writing "lines" of "text", and therefore appends a newline to whatever you give it (turning your mpegs into garbage). You want syswrite.

Jonathan Feinberg
`IO#syswrite` is pretty lowlevel. You have to check whether the complete block was written. I would use `IO#print` instead.
johannes
+2  A: 

You could just use IO#print instead of IO#puts but this has some disadvantages.

Don't do the file handling in ruby

Ruby is not good in doing stupid tasks very fast. With this code, you will probably not be able to fill your bandwith.

No CGI

All you want to do is expose a part of the filestystem via http, here are some options on how to do it

  • Set your document root to the folder you want to expose.
  • Make a symlink to the folder you want to expose.
  • Write some kind of rule in your webserver config to map certain url's to a folder

Use any of these and the http server will do for you what you want.

X-Sendfile

Some http servers honor a special header called X-Sendfile. If you do

print "X-Sendfile: #{file}"

the server will use the specified file as body. In lighty this works only through fastcgi, so you would need a fastcgi wrapper like: http://cgit.stbuehler.de/gitosis/fcgi-cgi/ . I don't know about apache and others.

Use a c extension

C extensions are good at doing stupid tasks fast. I wrote a c extension which does nothing, but reading from one IO and writing it to another IO. With this c extension you can fill a GIGABIT Lan through ruby: git://johannes.krude.de/io2io

Use it like this:

IO2IO.do(file, STDOUT)
johannes
I'm not exposing the whole filesystem. Well, I am, if I add a .., but I plan on fixing that.
Jeffrey Aylesworth
It seems I didn't read your code carefully enough. I fixed my answer according to your needs.
johannes