mirror of
https://gitea.invidious.io/iv-org/shard-kemal.git
synced 2024-08-15 00:53:36 +00:00
Add RFC7233 support (#299)
Add RFC7233 support a.k.a "Range" headers support
This commit is contained in:
parent
b07dd04372
commit
0543142a10
3 changed files with 101 additions and 1 deletions
|
@ -12,7 +12,9 @@ private def handle(request, fallthrough = true)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe Kemal::StaticFileHandler do
|
describe Kemal::StaticFileHandler do
|
||||||
file_text = File.read "#{__DIR__}/static/dir/test.txt"
|
file = File.open "#{__DIR__}/static/dir/test.txt"
|
||||||
|
file_size = file.size
|
||||||
|
file_text = file.to_s
|
||||||
|
|
||||||
it "should serve a file with content type and etag" do
|
it "should serve a file with content type and etag" do
|
||||||
response = handle HTTP::Request.new("GET", "/dir/test.txt")
|
response = handle HTTP::Request.new("GET", "/dir/test.txt")
|
||||||
|
@ -99,4 +101,33 @@ describe Kemal::StaticFileHandler do
|
||||||
response.headers["Allow"].should eq("GET, HEAD")
|
response.headers["Allow"].should eq("GET, HEAD")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should send part of files when requested (RFC7233)" do
|
||||||
|
%w(POST PUT DELETE HEAD).each do |method|
|
||||||
|
headers = HTTP::Headers{"Range" => "0-100"}
|
||||||
|
response = handle HTTP::Request.new(method, "/dir/test.txt", headers)
|
||||||
|
response.status_code.should_not eq(206)
|
||||||
|
response.headers.has_key?("Content-Range").should eq(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
%w(GET).each do |method|
|
||||||
|
headers = HTTP::Headers{"Range" => "0-100"}
|
||||||
|
response = handle HTTP::Request.new(method, "/dir/test.txt", headers)
|
||||||
|
response.status_code.should eq(206 || 200)
|
||||||
|
if response.status_code == 206
|
||||||
|
response.headers.has_key?("Content-Range").should eq true
|
||||||
|
match = response.headers["Content-Range"].match(/bytes (\d+)-(\d+)\/(\d+)/)
|
||||||
|
match.should_not be nil
|
||||||
|
if match
|
||||||
|
start_range = match[1].to_i {0}
|
||||||
|
end_range = match[2].to_i {0}
|
||||||
|
range_size = match[3].to_i {0}
|
||||||
|
|
||||||
|
range_size.should eq file_size
|
||||||
|
(end_range < file_size).should eq true
|
||||||
|
(start_range < end_range).should eq true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
14
src/kemal/response.cr
Normal file
14
src/kemal/response.cr
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
class HTTP::Server::Response
|
||||||
|
class Output
|
||||||
|
def close
|
||||||
|
|
||||||
|
unless response.wrote_headers? && !response.headers.has_key?("Content-Range")
|
||||||
|
response.content_length = @out_count
|
||||||
|
end
|
||||||
|
|
||||||
|
ensure_headers_written
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -56,6 +56,9 @@ module Kemal
|
||||||
request_headers = context.request.headers
|
request_headers = context.request.headers
|
||||||
filesize = File.size(file_path)
|
filesize = File.size(file_path)
|
||||||
File.open(file_path) do |file|
|
File.open(file_path) do |file|
|
||||||
|
if context.request.headers.has_key?("Range") && context.request.method == "GET"
|
||||||
|
next multipart(file, context)
|
||||||
|
end
|
||||||
if request_headers.includes_word?("Accept-Encoding", "gzip") && config.is_a?(Hash) && config["gzip"] == true && filesize > minsize && Utils.zip_types(file_path)
|
if request_headers.includes_word?("Accept-Encoding", "gzip") && config.is_a?(Hash) && config["gzip"] == true && filesize > minsize && Utils.zip_types(file_path)
|
||||||
context.response.headers["Content-Encoding"] = "gzip"
|
context.response.headers["Content-Encoding"] = "gzip"
|
||||||
Zlib::Deflate.gzip(context.response) do |deflate|
|
Zlib::Deflate.gzip(context.response) do |deflate|
|
||||||
|
@ -76,6 +79,58 @@ module Kemal
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def multipart(file, env)
|
||||||
|
#See http://httpwg.org/specs/rfc7233.html
|
||||||
|
fileb = file.size
|
||||||
|
|
||||||
|
range = env.request.headers["Range"]
|
||||||
|
match = range.match(/bytes=(\d{1,})-(\d{0,})/)
|
||||||
|
|
||||||
|
startb = 0
|
||||||
|
endb = 0
|
||||||
|
|
||||||
|
if match
|
||||||
|
if match.size >= 2
|
||||||
|
startb = match[1].to_i { 0 }
|
||||||
|
end
|
||||||
|
|
||||||
|
if match.size >= 3
|
||||||
|
endb = match[2].to_i { 0 }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if endb == 0
|
||||||
|
endb = fileb
|
||||||
|
end
|
||||||
|
|
||||||
|
if startb < endb && endb <= fileb
|
||||||
|
env.response.status_code = 206
|
||||||
|
env.response.content_length = endb - startb
|
||||||
|
env.response.headers["Accept-Ranges"] = "bytes"
|
||||||
|
env.response.headers["Content-Range"] = "bytes #{startb}-#{endb - 1}/#{fileb}" # MUST
|
||||||
|
|
||||||
|
if startb > 1024
|
||||||
|
skipped = 0
|
||||||
|
# file.skip only accepts values less or equal to 1024 (buffer size, undocumented)
|
||||||
|
until skipped + 1024 > startb
|
||||||
|
file.skip(1024)
|
||||||
|
skipped += 1024
|
||||||
|
end
|
||||||
|
if skipped - startb > 0
|
||||||
|
file.skip(skipped - startb)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
file.skip(startb)
|
||||||
|
end
|
||||||
|
|
||||||
|
IO.copy(file, env.response, endb - startb)
|
||||||
|
else
|
||||||
|
env.response.content_length = fileb
|
||||||
|
env.response.status_code = 200 # Range not satisfable, see 4.4 Note
|
||||||
|
IO.copy(file, env.response)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def etag(context, file_path)
|
def etag(context, file_path)
|
||||||
etag = %{W/"#{File.lstat(file_path).mtime.epoch.to_s}"}
|
etag = %{W/"#{File.lstat(file_path).mtime.epoch.to_s}"}
|
||||||
context.response.headers["ETag"] = etag
|
context.response.headers["ETag"] = etag
|
||||||
|
|
Loading…
Reference in a new issue