Permalink
Browse files

* Improved (I hope!) the speed of send_file when dealing with ranges

* Now returns correct HTTP status codes
* Added tests!
  • Loading branch information...
1 parent b100e44 commit 47917b1fb8981c65a052eddce326de6dbeb078ea @jphastings committed Oct 10, 2010
Showing with 46 additions and 7 deletions.
  1. +21 −7 lib/sinatra/base.rb
  2. +25 −0 test/static_test.rb
View
28 lib/sinatra/base.rb
@@ -175,11 +175,16 @@ def send_file(path, opts={})
end
sf = StaticFile.open(path, 'rb')
if (env['HTTP_RANGE'] =~ /^bytes=(\d+-\d+(?:,\d+-\d+)*)$/)
- response['Content-Range'] = "bytes #{$1}/#{response['Content-Length']}"
sf.ranges = $1.split(',').collect{|range| range.split('-').collect{|n| n.to_i}}
- response['Content-Length'] = sf.ranges.dup.inject(0){|total,range| total + range[1] - range[0] }.to_s
+ sf.ranges.each do |range|
+ halt 416 if range[1] < range[0]
+ end
+ response['Content-Range'] = "bytes #{$1}/#{response['Content-Length']}"
+ response['Content-Length'] = sf.ranges.dup.inject(0){|total,range| total + range[1] - range[0] + 1 }.to_s
+ halt 206, sf
+ else
+ halt sf
end
- halt sf
rescue Errno::ENOENT
not_found
end
@@ -192,11 +197,20 @@ class StaticFile < ::File #:nodoc:
attr_accessor :ranges
def each
- (@ranges || [[0,nil]]).each do |range|
- self.pos = range[0]
- while buf = read([8192,(range[1] - self.pos rescue nil)].compact.min)
+ if @ranges
+ @ranges.each do |range|
+ self.pos = range[0]
+ length = range[1] - range[0] + 1
+ while buf = read([8192,length.abs].min)
+ yield buf
+ length -= buf.length
+ break if (length -= 8192) + 8192 <= 0
+ end
+ end
+ else
+ rewind
+ while buf = read(8192)
yield buf
- break if self.pos >= range[1]
end
end
end
View
25 test/static_test.rb
@@ -90,4 +90,29 @@ class StaticTest < Test::Unit::TestCase
get "/../#{File.basename(__FILE__)}"
assert not_found?
end
+
+ it 'deals correctly with incompletable range requests' do
+ request = Rack::MockRequest.new(@app)
+ response = request.get("/#{File.basename(__FILE__)}", 'HTTP_RANGE' => "bytes=45-40")
+
+ assert_equal 416,response.status, "Ranges with final position < initial position should give HTTP/1.1 416 Requested Range Not Satisfiable"
+ end
+
+ it 'accepts and returns byte ranges correctly' do
+ [[[42,88]],[[1,5],[6,85]]].each do |ranges|
+ request = Rack::MockRequest.new(@app)
+ response = request.get("/#{File.basename(__FILE__)}", 'HTTP_RANGE' => "bytes=#{ranges.map{|r| r.join('-')}.join(',')}")
+
+ file = File.read(__FILE__)
+ should_be = ''
+ ranges.each do |range|
+ should_be += file[range[0]..range[1]]
+ end
+
+ assert_equal 206,response.status, "Should be HTTP/1.1 206 Partial content"
+ assert_equal should_be, response.body
+ assert_equal should_be.length.to_s, response['Content-Length'], "Length given was not the same as Content-Length reported"
+ assert_equal "bytes #{ranges.map{|r| r.join('-')}.join(',')}/#{File.size(__FILE__)}", response['Content-Range'],"Content-Range header was not correct"
+ end
+ end
end

0 comments on commit 47917b1

Please sign in to comment.