From d2fadb58f95d111aba3ce65bc4afbee2743f4d2c Mon Sep 17 00:00:00 2001 From: Luna Date: Tue, 11 Jun 2019 15:38:07 -0300 Subject: [PATCH 1/5] add support for error codes on websockets copied from https://github.com/onyxframework/http/blob/541dfc9da58595fd0d6ece3b0f6e0a1df6c990ba/src/onyx-http/middleware/router/websocket_handler.cr#L36 --- src/kemal/websocket.cr | 52 +++++++++++++++++++++++++++++++++- src/kemal/websocket_handler.cr | 20 +++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/kemal/websocket.cr b/src/kemal/websocket.cr index 2b65f8e..a35b91a 100644 --- a/src/kemal/websocket.cr +++ b/src/kemal/websocket.cr @@ -1,3 +1,5 @@ +require "http" + module Kemal # Takes 2 parameters: *path* and a *handler* to specify # what action to be done if the route is matched. @@ -7,8 +9,56 @@ module Kemal def initialize(@path : String, &@proc : HTTP::WebSocket, HTTP::Server::Context -> Void) end + def error(code : Int16, message : String) + err = WebsocketError.new(code, message) + raise err + end + def call(context : HTTP::Server::Context) - super + if websocket_upgrade_request?(context.request) + response = context.response + + version = context.request.headers["Sec-WebSocket-Version"]? + unless version == ::HTTP::WebSocket::Protocol::VERSION + response.headers["Sec-WebSocket-Version"] = ::HTTP::WebSocket::Protocol::VERSION + raise UpgradeRequired.new + end + + key = context.request.headers["Sec-WebSocket-Key"]? + raise BadRequest.new("Sec-WebSocket-Key header is missing") unless key + + accept_code = ::HTTP::WebSocket::Protocol.key_challenge(key) + + response.status_code = 101 + response.headers["Upgrade"] = "websocket" + response.headers["Connection"] = "Upgrade" + response.headers["Sec-WebSocket-Accept"] = accept_code + + response.upgrade do |io| + socket = ::HTTP::WebSocket.new(io) + @proc.call(socket, context) + socket.run + rescue error : Exception + if error.is_a?(WebsocketError) + # TODO + context.response.status_code = 500 + code = error.code.to_i16 + message = error.status_message + else + context.response.status_code = error.code + code = 1011_i16 + message = "Exception" + end + + raw = uninitialized UInt8[2] + IO::ByteFormat::BigEndian.encode(code, raw.to_slice) + socket.not_nil!.close(String.new(raw.to_slice) + message) + + raise error unless error.is_a?(WebsocketError) + end + else + raise UpgradeRequired.new + end end end end diff --git a/src/kemal/websocket_handler.cr b/src/kemal/websocket_handler.cr index addbecf..469979a 100644 --- a/src/kemal/websocket_handler.cr +++ b/src/kemal/websocket_handler.cr @@ -1,4 +1,24 @@ module Kemal + class WebsocketError < Exception + property code : Int32 = 1011 + property status_message : String = "websocket error" + + def initialize(@code, @status_message : String) + end + end + + class UpgradeRequired < WebsocketError + def initialize + super(426, "Upgrade required") + end + end + + class BadRequest < WebsocketError + def initialize(status_message : String) + super(400, status_message) + end + end + class WebSocketHandler include HTTP::Handler From bcf875b1d8e4950d2bd5a047c886bee1d444d62e Mon Sep 17 00:00:00 2001 From: Luna Date: Tue, 11 Jun 2019 18:00:36 -0300 Subject: [PATCH 2/5] websocket: default to error code 500 on any websocket error --- src/kemal/websocket.cr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/kemal/websocket.cr b/src/kemal/websocket.cr index a35b91a..28f58d3 100644 --- a/src/kemal/websocket.cr +++ b/src/kemal/websocket.cr @@ -39,13 +39,13 @@ module Kemal @proc.call(socket, context) socket.run rescue error : Exception + # TODO: check if 500 is what we're supposed to give + context.response.status_code = 500 + if error.is_a?(WebsocketError) - # TODO - context.response.status_code = 500 code = error.code.to_i16 message = error.status_message else - context.response.status_code = error.code code = 1011_i16 message = "Exception" end From 50dfd25450c357344f01dc9e966ffa40aa9f281a Mon Sep 17 00:00:00 2001 From: Luna Date: Thu, 20 Feb 2020 20:59:34 -0300 Subject: [PATCH 3/5] add head method to dsl --- src/kemal/dsl.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/kemal/dsl.cr b/src/kemal/dsl.cr index 15b3742..154662e 100644 --- a/src/kemal/dsl.cr +++ b/src/kemal/dsl.cr @@ -6,8 +6,8 @@ # - WebSocket(ws) # - before_* # - error -HTTP_METHODS = %w(get post put patch delete options) -FILTER_METHODS = %w(get post put patch delete options all) +HTTP_METHODS = %w(get post put patch delete options head) +FILTER_METHODS = %w(get post put patch delete options head all) {% for method in HTTP_METHODS %} def {{method.id}}(path : String, &block : HTTP::Server::Context -> _) From 2077cfb8b93dda2155415376f07b31c050001de1 Mon Sep 17 00:00:00 2001 From: Luna Date: Thu, 20 Feb 2020 21:17:50 -0300 Subject: [PATCH 4/5] route_handler: remove auto-HEAD --- src/kemal/route_handler.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kemal/route_handler.cr b/src/kemal/route_handler.cr index 216616a..d256403 100644 --- a/src/kemal/route_handler.cr +++ b/src/kemal/route_handler.cr @@ -21,7 +21,7 @@ module Kemal # a corresponding `HEAD` route. 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("HEAD", path, Route.new("HEAD", path) { }) if method == "GET" + # add_to_radix_tree("HEAD", path, Route.new("HEAD", path) { }) if method == "GET" end # Looks up the route from the Radix::Tree for the first time and caches to improve performance. From 9d7d72377ec63aeb97c0104171e987439f90332d Mon Sep 17 00:00:00 2001 From: Luna Date: Thu, 3 Sep 2020 22:46:19 -0300 Subject: [PATCH 5/5] don't overwrite content-length if its already there we shouldn't overwrite something that the client likely did it willlingly --- src/kemal/ext/response.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kemal/ext/response.cr b/src/kemal/ext/response.cr index 6fd4c01..fbfc4e0 100644 --- a/src/kemal/ext/response.cr +++ b/src/kemal/ext/response.cr @@ -2,7 +2,7 @@ class HTTP::Server::Response class Output def close # ameba:disable Style/NegatedConditionsInUnless - unless response.wrote_headers? && !response.headers.has_key?("Content-Range") + unless response.wrote_headers? && !response.headers.has_key?("Content-Range") && !response.headers.has_key?("Content-Length") response.content_length = @out_count end