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…
	
	Add table
		Add a link
		
	
		Reference in a new issue