mirror of
https://gitea.invidious.io/iv-org/shard-kemal.git
synced 2024-08-15 00:53:36 +00:00
HeadRequestHandler: run GET handler and don't return the body (#655)
This commit is contained in:
parent
84ea6627ac
commit
8ebe171279
7 changed files with 115 additions and 8 deletions
|
@ -29,7 +29,7 @@ describe "Config" do
|
||||||
config = Kemal.config
|
config = Kemal.config
|
||||||
config.add_handler CustomTestHandler.new
|
config.add_handler CustomTestHandler.new
|
||||||
Kemal.config.setup
|
Kemal.config.setup
|
||||||
config.handlers.size.should eq(7)
|
config.handlers.size.should eq(8)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "toggles the shutdown message" do
|
it "toggles the shutdown message" do
|
||||||
|
|
37
spec/head_request_handler_spec.cr
Normal file
37
spec/head_request_handler_spec.cr
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
require "./spec_helper"
|
||||||
|
|
||||||
|
describe "Kemal::HeadRequestHandler" do
|
||||||
|
it "implicitly handles GET endpoints, with Content-Length header" do
|
||||||
|
get "/" do
|
||||||
|
"hello"
|
||||||
|
end
|
||||||
|
request = HTTP::Request.new("HEAD", "/")
|
||||||
|
client_response = call_request_on_app(request)
|
||||||
|
client_response.body.should eq("")
|
||||||
|
client_response.headers["Content-Length"].should eq("5")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "prefers explicit HEAD endpoint if specified" do
|
||||||
|
Kemal::RouteHandler::INSTANCE.add_route("HEAD", "/") { "hello" }
|
||||||
|
get "/" do
|
||||||
|
raise "shouldn't be called!"
|
||||||
|
end
|
||||||
|
request = HTTP::Request.new("HEAD", "/")
|
||||||
|
client_response = call_request_on_app(request)
|
||||||
|
client_response.body.should eq("")
|
||||||
|
client_response.headers["Content-Length"].should eq("5")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "gives compressed Content-Length when gzip enabled" do
|
||||||
|
gzip true
|
||||||
|
get "/" do
|
||||||
|
"hello"
|
||||||
|
end
|
||||||
|
headers = HTTP::Headers{"Accept-Encoding" => "gzip"}
|
||||||
|
request = HTTP::Request.new("HEAD", "/", headers)
|
||||||
|
client_response = call_request_on_app(request)
|
||||||
|
client_response.body.should eq("")
|
||||||
|
client_response.headers["Content-Encoding"].should eq("gzip")
|
||||||
|
client_response.headers["Content-Length"].should eq("25")
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,7 +13,7 @@ describe "Macros" do
|
||||||
it "adds a custom handler" do
|
it "adds a custom handler" do
|
||||||
add_handler CustomTestHandler.new
|
add_handler CustomTestHandler.new
|
||||||
Kemal.config.setup
|
Kemal.config.setup
|
||||||
Kemal.config.handlers.size.should eq 7
|
Kemal.config.handlers.size.should eq 8
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -150,7 +150,7 @@ describe "Macros" do
|
||||||
it "adds HTTP::CompressHandler to handlers" do
|
it "adds HTTP::CompressHandler to handlers" do
|
||||||
gzip true
|
gzip true
|
||||||
Kemal.config.setup
|
Kemal.config.setup
|
||||||
Kemal.config.handlers[4].should be_a(HTTP::CompressHandler)
|
Kemal.config.handlers[5].should be_a(HTTP::CompressHandler)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -103,6 +103,7 @@ module Kemal
|
||||||
unless @default_handlers_setup && @router_included
|
unless @default_handlers_setup && @router_included
|
||||||
setup_init_handler
|
setup_init_handler
|
||||||
setup_log_handler
|
setup_log_handler
|
||||||
|
setup_head_request_handler
|
||||||
setup_error_handler
|
setup_error_handler
|
||||||
setup_static_file_handler
|
setup_static_file_handler
|
||||||
setup_custom_handlers
|
setup_custom_handlers
|
||||||
|
@ -129,6 +130,11 @@ module Kemal
|
||||||
@handler_position += 1
|
@handler_position += 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private def setup_head_request_handler
|
||||||
|
HANDLERS.insert(@handler_position, Kemal::HeadRequestHandler::INSTANCE)
|
||||||
|
@handler_position += 1
|
||||||
|
end
|
||||||
|
|
||||||
private def setup_error_handler
|
private def setup_error_handler
|
||||||
if @always_rescue
|
if @always_rescue
|
||||||
@error_handler ||= Kemal::ExceptionHandler.new
|
@error_handler ||= Kemal::ExceptionHandler.new
|
||||||
|
|
60
src/kemal/head_request_handler.cr
Normal file
60
src/kemal/head_request_handler.cr
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
require "http/server/handler"
|
||||||
|
|
||||||
|
module Kemal
|
||||||
|
class HeadRequestHandler
|
||||||
|
include HTTP::Handler
|
||||||
|
|
||||||
|
INSTANCE = new
|
||||||
|
|
||||||
|
private class NullIO < IO
|
||||||
|
@original_output : IO
|
||||||
|
@out_count : Int32
|
||||||
|
@response : HTTP::Server::Response
|
||||||
|
|
||||||
|
def initialize(@response)
|
||||||
|
@closed = false
|
||||||
|
@original_output = @response.output
|
||||||
|
@out_count = 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def read(slice : Bytes)
|
||||||
|
raise NotImplementedError.new("read")
|
||||||
|
end
|
||||||
|
|
||||||
|
def write(slice : Bytes) : Nil
|
||||||
|
@out_count += slice.bytesize
|
||||||
|
end
|
||||||
|
|
||||||
|
def close : Nil
|
||||||
|
return if @closed
|
||||||
|
@closed = true
|
||||||
|
|
||||||
|
# Matching HTTP::Server::Response#close behavior:
|
||||||
|
# Conditionally determine based on status if the `content-length` header should be added automatically.
|
||||||
|
# See https://tools.ietf.org/html/rfc7230#section-3.3.2.
|
||||||
|
status = @response.status
|
||||||
|
set_content_length = !(status.not_modified? || status.no_content? || status.informational?)
|
||||||
|
|
||||||
|
if !@response.headers.has_key?("Content-Length") && set_content_length
|
||||||
|
@response.content_length = @out_count
|
||||||
|
end
|
||||||
|
|
||||||
|
@original_output.close
|
||||||
|
end
|
||||||
|
|
||||||
|
def closed? : Bool
|
||||||
|
@closed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(context) : Nil
|
||||||
|
if context.request.method == "HEAD"
|
||||||
|
# Capture and count bytes of response body generated on HEAD requests without actually sending the body back.
|
||||||
|
capture_io = NullIO.new(context.response)
|
||||||
|
context.response.output = capture_io
|
||||||
|
end
|
||||||
|
|
||||||
|
call_next(context)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,11 +5,12 @@
|
||||||
require "mime"
|
require "mime"
|
||||||
|
|
||||||
# Adds given `Kemal::Handler` to handlers chain.
|
# Adds given `Kemal::Handler` to handlers chain.
|
||||||
# There are 5 handlers by default and all the custom handlers
|
# There are 6 handlers by default and all the custom handlers
|
||||||
# goes between the first 4 and the last `Kemal::RouteHandler`.
|
# goes between the first 5 and the last `Kemal::RouteHandler`.
|
||||||
#
|
#
|
||||||
# - `Kemal::InitHandler`
|
# - `Kemal::InitHandler`
|
||||||
# - `Kemal::LogHandler`
|
# - `Kemal::LogHandler`
|
||||||
|
# - `Kemal::HeadRequestHandler`
|
||||||
# - `Kemal::ExceptionHandler`
|
# - `Kemal::ExceptionHandler`
|
||||||
# - `Kemal::StaticFileHandler`
|
# - `Kemal::StaticFileHandler`
|
||||||
# - Here goes custom handlers
|
# - Here goes custom handlers
|
||||||
|
|
|
@ -17,11 +17,9 @@ module Kemal
|
||||||
process_request(context)
|
process_request(context)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Adds a given route to routing tree. As an exception each `GET` route additionaly defines
|
# Adds a given route to routing tree.
|
||||||
# a corresponding `HEAD` route.
|
|
||||||
def add_route(method : String, path : String, &handler : HTTP::Server::Context -> _)
|
def add_route(method : String, path : String, &handler : HTTP::Server::Context -> _)
|
||||||
add_to_radix_tree method, path, Route.new(method, path, &handler)
|
add_to_radix_tree method, path, Route.new(method, path, &handler)
|
||||||
add_to_radix_tree("HEAD", path, Route.new("HEAD", path) { }) if method == "GET"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Looks up the route from the Radix::Tree for the first time and caches to improve performance.
|
# Looks up the route from the Radix::Tree for the first time and caches to improve performance.
|
||||||
|
@ -34,6 +32,11 @@ module Kemal
|
||||||
|
|
||||||
route = @routes.find(lookup_path)
|
route = @routes.find(lookup_path)
|
||||||
|
|
||||||
|
if verb == "HEAD" && !route.found?
|
||||||
|
# On HEAD requests, implicitly fallback to running the GET handler.
|
||||||
|
route = @routes.find(radix_path("GET", path))
|
||||||
|
end
|
||||||
|
|
||||||
if route.found?
|
if route.found?
|
||||||
@cached_routes.clear if @cached_routes.size == CACHED_ROUTES_LIMIT
|
@cached_routes.clear if @cached_routes.size == CACHED_ROUTES_LIMIT
|
||||||
@cached_routes[lookup_path] = route
|
@cached_routes[lookup_path] = route
|
||||||
|
|
Loading…
Reference in a new issue