Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions spec/lucky/format_integration_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ private class TestReportsAction < TestAction
end
end

private class TestExplicitExtensionAction < TestAction
accepted_formats [:html, :json], default: :html

class_property? reached = false

get "/explicit_extension/path.json" do
TestExplicitExtensionAction.reached = true
plain_text "matched explicit json route"
end
end

describe "Format Integration" do
it "handles URL format extensions correctly" do
# Test CSV format from URL extension
Expand Down Expand Up @@ -66,6 +77,23 @@ describe "Format Integration" do
result.should be_nil
end

it "matches a route that is explicitly declared with a known extension" do
TestExplicitExtensionAction.reached = false
context = build_context(path: "/explicit_extension/path.json")

Lucky::RouteHandler.new.call(context)

TestExplicitExtensionAction.reached?.should be_true
end

it "does not set a url format when the explicit extension route matches" do
context = build_context(path: "/explicit_extension/path.json")

Lucky::RouteHandler.new.call(context)

context._url_format.should be_nil
end

it "supports multiple format extensions" do
Lucky::MimeType.extract_format_from_path("/reports/123.html").should eq(Lucky::Format::Html)
Lucky::MimeType.extract_format_from_path("/users/456.json").should eq(Lucky::Format::Json)
Expand Down
20 changes: 20 additions & 0 deletions spec/lucky/maximum_request_size_handler_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ describe Lucky::MaximumRequestSizeHandler do
end
context.response.status.should eq(HTTP::Status::PAYLOAD_TOO_LARGE)
end

it "honors the request body limit for actions declared with an explicit extension" do
context = build_request_context_with_body("/__max_request_size/explicit.json", 50_000, "POST")
Lucky::MaximumRequestSizeHandler.temp_config(
enabled: true,
max_size: 10_000,
) do
run_request_size_handler(context)
end
context.response.status.should eq(HTTP::Status::OK)
end
end
end

Expand Down Expand Up @@ -98,5 +109,14 @@ private class SmallUploadAction < Lucky::Action
end
end

private class ExplicitExtensionUploadAction < Lucky::Action
set_request_body_limit 50_000

def call : Lucky::Response
plain_text "ok"
end
end

Lucky.router.add :post, "/__max_request_size/large", LargeUploadAction
Lucky.router.add :post, "/__max_request_size/small", SmallUploadAction
Lucky.router.add :post, "/__max_request_size/explicit.json", ExplicitExtensionUploadAction
11 changes: 7 additions & 4 deletions src/lucky/maximum_request_size_handler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,13 @@ class Lucky::MaximumRequestSizeHandler
method = context.request.method
path = context.request.path

if Lucky::MimeType.extract_format_from_path(path)
path = path.sub(/^([^?]*)\.[a-zA-Z0-9]+(\?.*)?$/, "\\1\\2")
# Match the full path first; only fall back to the format-stripped path so
# actions declared with an explicit extension keep their request_body_limit.
Lucky.router.find_action(method, path) || begin
if Lucky::MimeType.extract_format_from_path(path)
stripped = path.sub(/^([^?]*)\.[a-zA-Z0-9]+(\?.*)?$/, "\\1\\2")
Lucky.router.find_action(method, stripped)
end
end

Lucky.router.find_action(method, path)
end
end
27 changes: 19 additions & 8 deletions src/lucky/route_handler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,36 @@ require "colorize"
class Lucky::RouteHandler
include HTTP::Handler

# Assuming a request path comes in for `/reports.json`,
# we find and actions with an explicit extension set like `/reports.json`. If none are found,
# then we look for `/reports` that accepts the json mime type.
def call(context : HTTP::Server::Context)
original_path = context.request.path
method = context.request.method
handler = Lucky.router.find_action(method, original_path)

# Extract format from URL path and strip it for route matching
if url_format = Lucky::MimeType.extract_format_from_path(original_path)
context._url_format = url_format
# Create a modified request with format-stripped path for route matching
path_without_format = original_path.sub(/^([^?]*)\.[a-zA-Z0-9]+(\?.*)?$/, "\\1\\2")
lookup_request_path = path_without_format
else
lookup_request_path = original_path
if handler.nil? || format_absorbed_by_param?(handler, original_path)
context._url_format = url_format
path_without_format = original_path.sub(/^([^?]*)\.[a-zA-Z0-9]+(\?.*)?$/, "\\1\\2")
handler = Lucky.router.find_action(method, path_without_format)
end
end

handler = Lucky.router.find_action(context.request.method, lookup_request_path)
if handler
Lucky::Log.dexter.debug { {handled_by: handler.payload.to_s} }
handler.payload.new(context, handler.params).perform_action
else
call_next(context)
end
end

private def format_absorbed_by_param?(match : LuckyRouter::Match(Lucky::Action.class), path : String) : Bool
if extension_match = path.match(/^[^?]*\.([a-zA-Z0-9]+)(?:\?|$)/)
extension = extension_match[1]
match.params.values.any?(&.ends_with?(".#{extension}"))
else
false
end
end
end