diff --git a/spec/context_spec.cr b/spec/context_spec.cr index ca03520..a2d9c6c 100644 --- a/spec/context_spec.cr +++ b/spec/context_spec.cr @@ -55,6 +55,7 @@ describe "Context" do before_get_context_test: env.get("before_get_context_test"), } end + request = HTTP::Request.new("GET", "/") io = IO::Memory.new response = HTTP::Server::Response.new(io) diff --git a/spec/middleware/filters_spec.cr b/spec/middleware/filters_spec.cr index 41853be..1271c78 100644 --- a/spec/middleware/filters_spec.cr +++ b/spec/middleware/filters_spec.cr @@ -9,7 +9,7 @@ describe "Kemal::FilterHandler" do filter_middleware._add_route_filter("GET", "/greetings", :before) { test_filter.modified = "true" } kemal = Kemal::RouteHandler::INSTANCE - kemal.add_route "GET", "/greetings" { test_filter.modified } + kemal.add_http_route "GET", "/greetings" { test_filter.modified } test_filter.modified.should eq("false") request = HTTP::Request.new("GET", "/greetings") @@ -27,8 +27,8 @@ describe "Kemal::FilterHandler" do filter_middleware._add_route_filter("GET", "/greetings", :before) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } kemal = Kemal::RouteHandler::INSTANCE - kemal.add_route "GET", "/greetings" { test_filter.modified } - kemal.add_route "POST", "/greetings" { test_filter.modified } + kemal.add_http_route "GET", "/greetings" { test_filter.modified } + kemal.add_http_route "POST", "/greetings" { test_filter.modified } test_filter.modified.should eq("false") @@ -55,8 +55,8 @@ describe "Kemal::FilterHandler" do filter_middleware._add_route_filter("POST", "/greetings", :before) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } kemal = Kemal::RouteHandler::INSTANCE - kemal.add_route "GET", "/greetings" { test_filter.modified } - kemal.add_route "POST", "/greetings" { test_filter.modified } + kemal.add_http_route "GET", "/greetings" { test_filter.modified } + kemal.add_http_route "POST", "/greetings" { test_filter.modified } test_filter.modified.should eq("false") @@ -81,7 +81,7 @@ describe "Kemal::FilterHandler" do filter_middleware._add_route_filter("GET", "/greetings", :after) { test_filter.modified = "true" } kemal = Kemal::RouteHandler::INSTANCE - kemal.add_route "GET", "/greetings" { test_filter.modified } + kemal.add_http_route "GET", "/greetings" { test_filter.modified } test_filter.modified.should eq("false") request = HTTP::Request.new("GET", "/greetings") @@ -99,8 +99,8 @@ describe "Kemal::FilterHandler" do filter_middleware._add_route_filter("GET", "/greetings", :after) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } kemal = Kemal::RouteHandler::INSTANCE - kemal.add_route "GET", "/greetings" { test_filter.modified } - kemal.add_route "POST", "/greetings" { test_filter.modified } + kemal.add_http_route "GET", "/greetings" { test_filter.modified } + kemal.add_http_route "POST", "/greetings" { test_filter.modified } test_filter.modified.should eq("false") @@ -127,8 +127,8 @@ describe "Kemal::FilterHandler" do filter_middleware._add_route_filter("POST", "/greetings", :after) { test_filter.modified = test_filter.modified == "true" ? "false" : "true" } kemal = Kemal::RouteHandler::INSTANCE - kemal.add_route "GET", "/greetings" { test_filter.modified } - kemal.add_route "POST", "/greetings" { test_filter.modified } + kemal.add_http_route "GET", "/greetings" { test_filter.modified } + kemal.add_http_route "POST", "/greetings" { test_filter.modified } test_filter.modified.should eq("false") request = HTTP::Request.new("GET", "/greetings") @@ -158,9 +158,9 @@ describe "Kemal::FilterHandler" do filter_middleware._add_route_filter("ALL", "/greetings", :before) { test_filter_third.modified = test_filter_third.modified == "true" ? "false" : "true" } kemal = Kemal::RouteHandler::INSTANCE - kemal.add_route "GET", "/greetings" { test_filter.modified } - kemal.add_route "POST", "/greetings" { test_filter_second.modified } - kemal.add_route "PUT", "/greetings" { test_filter_third.modified } + kemal.add_http_route "GET", "/greetings" { test_filter.modified } + kemal.add_http_route "POST", "/greetings" { test_filter_second.modified } + kemal.add_http_route "PUT", "/greetings" { test_filter_third.modified } test_filter.modified.should eq("false") test_filter_second.modified.should eq("false") diff --git a/spec/param_parser_spec.cr b/spec/param_parser_spec.cr index 4c1992f..00ca690 100644 --- a/spec/param_parser_spec.cr +++ b/spec/param_parser_spec.cr @@ -23,7 +23,7 @@ describe "ParamParser" do it "parses url params" do kemal = Kemal::RouteHandler::INSTANCE - kemal.add_route "POST", "/hello/:hasan" do |env| + kemal.add_http_route "POST", "/hello/:hasan" do |env| "hello #{env.params.url["hasan"]}" end request = HTTP::Request.new("POST", "/hello/cemal") @@ -35,7 +35,7 @@ describe "ParamParser" do it "decodes url params" do kemal = Kemal::RouteHandler::INSTANCE - kemal.add_route "POST", "/hello/:email/:money/:spanish" do |env| + kemal.add_http_route "POST", "/hello/:email/:money/:spanish" do |env| email = env.params.url["email"] money = env.params.url["money"] spanish = env.params.url["spanish"] diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 6d2e291..fc9b9bf 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -81,5 +81,6 @@ end Spec.after_each do Kemal.config.clear - Kemal::RouteHandler::INSTANCE.tree = Radix::Tree(Route).new + Kemal::RouteHandler::INSTANCE.http_routes = Radix::Tree(Route).new + Kemal::RouteHandler::INSTANCE.ws_routes = Radix::Tree(String).new end diff --git a/spec/websocket_handler_spec.cr b/spec/websocket_handler_spec.cr index f90618b..759332d 100644 --- a/spec/websocket_handler_spec.cr +++ b/spec/websocket_handler_spec.cr @@ -25,4 +25,16 @@ describe "Kemal::WebSocketHandler" do io_with_context = create_ws_request_and_return_io(handler, request) io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-Websocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n") end + + it "fetches named url parameters" do + handler = Kemal::WebSocketHandler.new "/:id" { |s, c| c.params.url["id"] } + headers = HTTP::Headers{ + "Upgrade" => "websocket", + "Connection" => "Upgrade", + "Sec-WebSocket-Key" => "dGhlIHNhbXBsZSBub25jZQ==", + } + request = HTTP::Request.new("GET", "/1234", headers) + io_with_context = create_ws_request_and_return_io(handler, request) + io_with_context.to_s.should eq("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-Websocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n\r\n") + end end diff --git a/src/kemal/dsl.cr b/src/kemal/dsl.cr index 563c681..e622a37 100644 --- a/src/kemal/dsl.cr +++ b/src/kemal/dsl.cr @@ -10,7 +10,7 @@ FILTER_METHODS = %w(get post put patch delete options all) {% for method in HTTP_METHODS %} def {{method.id}}(path, &block : HTTP::Server::Context -> _) raise Kemal::Exceptions::InvalidPathStartException.new({{method}}, path) unless Kemal::Utils.path_starts_with_slash?(path) - Kemal::RouteHandler::INSTANCE.add_route({{method}}.upcase, path, &block) + Kemal::RouteHandler::INSTANCE.add_http_route({{method}}.upcase, path, &block) end {% end %} diff --git a/src/kemal/ext/context.cr b/src/kemal/ext/context.cr index 02114fd..ba8a834 100644 --- a/src/kemal/ext/context.cr +++ b/src/kemal/ext/context.cr @@ -13,7 +13,13 @@ class HTTP::Server end def params - @request.url_params ||= route_lookup.params + connection_type = @request.headers.fetch("Connection", nil) + @request.url_params ||= unless connection_type == "Upgrade" + route_lookup.params + else + ws_route_lookup.params + end + @params ||= if @request.param_parser @request.param_parser.not_nil! else @@ -34,6 +40,14 @@ class HTTP::Server route_lookup.found? end + def ws_route_lookup + Kemal::RouteHandler::INSTANCE.lookup_ws_route(@request.path) + end + + def ws_route_defined? + ws_route_lookup.found? + end + def get(name) @store[name] end diff --git a/src/kemal/route_handler.cr b/src/kemal/route_handler.cr index 4ed2aa9..eba1142 100644 --- a/src/kemal/route_handler.cr +++ b/src/kemal/route_handler.cr @@ -7,10 +7,12 @@ module Kemal include HTTP::Handler INSTANCE = new - property tree + property http_routes + property ws_routes def initialize - @tree = Radix::Tree(Route).new + @http_routes = Radix::Tree(Route).new + @ws_routes = Radix::Tree(String).new end def call(context) @@ -19,14 +21,22 @@ module Kemal # Adds a given route to routing tree. As an exception each `GET` route additionaly defines # a corresponding `HEAD` route. - def add_route(method, path, &handler : HTTP::Server::Context -> _) - add_to_radix_tree method, path, Route.new(method, path, &handler) - add_to_radix_tree("HEAD", path, Route.new("HEAD", path) { |ctx| "" }) if method == "GET" + def add_http_route(method, path, &handler : HTTP::Server::Context -> _) + add_to_http_radix_tree method, path, Route.new(method, path, &handler) + add_to_http_radix_tree("HEAD", path, Route.new("HEAD", path) { |ctx| "" }) if method == "GET" + end + + def add_ws_route(path) + add_to_ws_radix_tree path end # Check if a route is defined and returns the lookup def lookup_route(verb, path) - @tree.find radix_path(verb, path) + @http_routes.find radix_path(verb, path) + end + + def lookup_ws_route(path) + @ws_routes.find "/ws#{path}" end # Processes the route if it's a match. Otherwise renders 404. @@ -49,13 +59,18 @@ module Kemal end end - private def radix_path(method : String, path) + private def radix_path(method, path) "/#{method.downcase}#{path}" end - private def add_to_radix_tree(method, path, route) + private def add_to_http_radix_tree(method, path, route) node = radix_path method, path - @tree.add node, route + @http_routes.add node, route + end + + private def add_to_ws_radix_tree(path) + node = radix_path "ws", path + @ws_routes.add node, node end end end diff --git a/src/kemal/websocket_handler.cr b/src/kemal/websocket_handler.cr index b3907b0..2d9b396 100644 --- a/src/kemal/websocket_handler.cr +++ b/src/kemal/websocket_handler.cr @@ -4,10 +4,11 @@ module Kemal class WebSocketHandler < HTTP::WebSocketHandler def initialize(@path : String, &@proc : HTTP::WebSocket, HTTP::Server::Context -> Void) Kemal.config.add_handler self + Kemal::RouteHandler::INSTANCE.add_ws_route @path end def call(context) - return call_next(context) unless context.request.path.not_nil! == @path + return call_next(context) unless context.ws_route_defined? super end end