Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

send_file now copes with 'Range: bytes=100-400,500-600' headers

Signed-off-by: Konstantin Haase <konstantin.mailinglists@googlemail.com>
  • Loading branch information...
commit 480b1e8ebe579acf87c572a74ca45b6205616987 1 parent 89af8c1
@jphastings jphastings authored rkh committed
Showing with 55 additions and 5 deletions.
  1. +30 −5 lib/sinatra/base.rb
  2. +25 −0 test/static_test.rb
View
35 lib/sinatra/base.rb
@@ -161,8 +161,18 @@ def send_file(path, opts={})
elsif opts[:disposition] == 'inline'
response['Content-Disposition'] = 'inline'
end
-
- halt StaticFile.open(path, 'rb')
+ sf = StaticFile.open(path, 'rb')
+ if (env['HTTP_RANGE'] =~ /^bytes=(\d+-\d+(?:,\d+-\d+)*)$/)
+ sf.ranges = $1.split(',').collect{|range| range.split('-').collect{|n| n.to_i}}
+ 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
rescue Errno::ENOENT
not_found
end
@@ -171,10 +181,25 @@ def send_file(path, opts={})
# generated iteratively in 8K chunks.
class StaticFile < ::File #:nodoc:
alias_method :to_path, :path
+
+ attr_accessor :ranges
+
def each
- rewind
- while buf = read(8192)
- yield buf
+ 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
+ end
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
Please sign in to comment.
Something went wrong with that request. Please try again.