From 58c95590188fac80bc28464733fd942dfa109b4b Mon Sep 17 00:00:00 2001 From: Jeremy Woertink Date: Wed, 11 Nov 2020 14:07:34 -0800 Subject: [PATCH 1/5] initial files for websocket compatibility. --- src/lucky/routable.cr | 44 +++++++++++++++++++++++++++++++++ src/lucky/web_socket.cr | 9 +++++++ src/lucky/web_socket_action.cr | 19 ++++++++++++++ src/lucky/web_socket_handler.cr | 26 +++++++++++++++++++ 4 files changed, 98 insertions(+) create mode 100644 src/lucky/web_socket.cr create mode 100644 src/lucky/web_socket_action.cr create mode 100644 src/lucky/web_socket_handler.cr diff --git a/src/lucky/routable.cr b/src/lucky/routable.cr index 900a05450..0a150d4c3 100644 --- a/src/lucky/routable.cr +++ b/src/lucky/routable.cr @@ -58,6 +58,21 @@ module Lucky::Routable end {% end %} + # Define a route that responds to a WebSocket request + macro ws(path, &block) + {% unless path.starts_with?("/") %} + {% path.raise "Path must start with a slash. Example: '/#{path}'" %} + {% end %} + + {% if block.args.size != 1 %} + {% raise "ws takes a block with 1 arg." %} + {% end %} + + add_route({{method}}, {{ path }}, {{ @type.name.id }}) + + setup_ws_call_method(block) + end + # Define a route with a custom HTTP method. # # Use this method if you need to match a route with a custom HTTP method (verb). @@ -85,6 +100,35 @@ module Lucky::Routable setup_call_method({{ yield }}) end + # :nodoc: + macro setup_ws_call_method(&block) + + abstract def on_message(message) + abstract def on_close + + def call(socket : Lucky::WebSocket) + # Ensure clients_desired_format is cached by calling it + clients_desired_format + + %pipe_result = run_before_pipes + + %response = if %pipe_result.is_a?(Lucky::Response) + %pipe_result + else + {{ block.args.first }} = socket + {{ block.body }} + end + + %pipe_result = run_after_pipes + + if %pipe_result.is_a?(Lucky::Response) + %pipe_result + else + %response + end + end + end + # :nodoc: macro setup_call_method(body) def call diff --git a/src/lucky/web_socket.cr b/src/lucky/web_socket.cr new file mode 100644 index 000000000..28b47bcf5 --- /dev/null +++ b/src/lucky/web_socket.cr @@ -0,0 +1,9 @@ +module Lucky + class WebSocket < HTTP::WebSocketHandler + getter proc + + def initialize(@action : Lucky::Action.class, &@proc : HTTP::WebSocket, HTTP::Server::Context -> Void) + end + + end +end diff --git a/src/lucky/web_socket_action.cr b/src/lucky/web_socket_action.cr new file mode 100644 index 000000000..c9ed08d31 --- /dev/null +++ b/src/lucky/web_socket_action.cr @@ -0,0 +1,19 @@ +abstract class Lucky::WebSocketAction < Lucky::Action + getter websocket : Lucky::WebSocket + + def initialize(@context : HTTP::Server::Context, @route_params : Hash(String, String)) + @websocket = Lucky::WebSocket.new(self.class) do |ws| + ws.on_message {|message| on_message(message) } + ws.on_close { on_close } + call(ws) + end + end + + abstract def call(socket : Lucky::WebSocket) + + def call + raise <<-ERROR + WebSocketAction must define `call(socket)` + ERROR + end +end \ No newline at end of file diff --git a/src/lucky/web_socket_handler.cr b/src/lucky/web_socket_handler.cr new file mode 100644 index 000000000..430c45a3d --- /dev/null +++ b/src/lucky/web_socket_handler.cr @@ -0,0 +1,26 @@ +class WebSocketHandler + include HTTP::Handler + + def call(context : HTTP::Server::Context) + if ws_route_found?(context) && websocket_upgrade_request?(context) + websocket_action.payload.new(context, websocket_action.params).websocket.call(context) + else + call_next(context) + end + end + + private def ws_route_found?(context) + !!websocket_action + end + + memoize def websocket_action : LuckyRouter::Match(Lucky::Action.class)? + Lucky::Router.find_action(:ws, context.request.path) + end + + private def websocket_upgrade_request?(context) + return unless upgrade = context.request.headers["Upgrade"]? + return unless upgrade.compare("websocket", case_insensitive: true) == 0 + + context.request.headers.includes_word?("Connection", "Upgrade") + end +end From 3244366df825c6df82f53b4440503749411ae299 Mon Sep 17 00:00:00 2001 From: Jeremy Woertink Date: Wed, 11 Nov 2020 14:07:58 -0800 Subject: [PATCH 2/5] make sure this is formatted --- src/lucky/web_socket.cr | 1 - src/lucky/web_socket_action.cr | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lucky/web_socket.cr b/src/lucky/web_socket.cr index 28b47bcf5..995fa0a9b 100644 --- a/src/lucky/web_socket.cr +++ b/src/lucky/web_socket.cr @@ -4,6 +4,5 @@ module Lucky def initialize(@action : Lucky::Action.class, &@proc : HTTP::WebSocket, HTTP::Server::Context -> Void) end - end end diff --git a/src/lucky/web_socket_action.cr b/src/lucky/web_socket_action.cr index c9ed08d31..624e3212e 100644 --- a/src/lucky/web_socket_action.cr +++ b/src/lucky/web_socket_action.cr @@ -3,7 +3,7 @@ abstract class Lucky::WebSocketAction < Lucky::Action def initialize(@context : HTTP::Server::Context, @route_params : Hash(String, String)) @websocket = Lucky::WebSocket.new(self.class) do |ws| - ws.on_message {|message| on_message(message) } + ws.on_message { |message| on_message(message) } ws.on_close { on_close } call(ws) end @@ -16,4 +16,4 @@ abstract class Lucky::WebSocketAction < Lucky::Action WebSocketAction must define `call(socket)` ERROR end -end \ No newline at end of file +end From 992b68c380969ceb86b917db305ff45ed0a5a7c3 Mon Sep 17 00:00:00 2001 From: Jeremy Woertink Date: Fri, 13 Nov 2020 14:43:34 -0800 Subject: [PATCH 3/5] Fixing a few errors --- src/lucky/routable.cr | 2 +- src/lucky/web_socket_action.cr | 3 ++- src/lucky/web_socket_handler.cr | 38 +++++++++++++++++---------------- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/lucky/routable.cr b/src/lucky/routable.cr index 0a150d4c3..5147d314b 100644 --- a/src/lucky/routable.cr +++ b/src/lucky/routable.cr @@ -68,7 +68,7 @@ module Lucky::Routable {% raise "ws takes a block with 1 arg." %} {% end %} - add_route({{method}}, {{ path }}, {{ @type.name.id }}) + add_route(:ws, {{ path }}, {{ @type.name.id }}) setup_ws_call_method(block) end diff --git a/src/lucky/web_socket_action.cr b/src/lucky/web_socket_action.cr index 624e3212e..1c0e607bf 100644 --- a/src/lucky/web_socket_action.cr +++ b/src/lucky/web_socket_action.cr @@ -3,13 +3,14 @@ abstract class Lucky::WebSocketAction < Lucky::Action def initialize(@context : HTTP::Server::Context, @route_params : Hash(String, String)) @websocket = Lucky::WebSocket.new(self.class) do |ws| + ws.on_ping { ws.pong(@context.request.path) } ws.on_message { |message| on_message(message) } ws.on_close { on_close } call(ws) end end - abstract def call(socket : Lucky::WebSocket) + abstract def call(socket : HTTP::WebSocket) def call raise <<-ERROR diff --git a/src/lucky/web_socket_handler.cr b/src/lucky/web_socket_handler.cr index 430c45a3d..b85c7c68a 100644 --- a/src/lucky/web_socket_handler.cr +++ b/src/lucky/web_socket_handler.cr @@ -1,26 +1,28 @@ -class WebSocketHandler - include HTTP::Handler +module Lucky + class WebSocketHandler + include HTTP::Handler - def call(context : HTTP::Server::Context) - if ws_route_found?(context) && websocket_upgrade_request?(context) - websocket_action.payload.new(context, websocket_action.params).websocket.call(context) - else - call_next(context) + def call(context : HTTP::Server::Context) + if ws_route_found?(context) && websocket_upgrade_request?(context) + websocket_action.payload.new(context, websocket_action.params).websocket.call(context) + else + call_next(context) + end end - end - private def ws_route_found?(context) - !!websocket_action - end + private def ws_route_found?(context) + !!websocket_action + end - memoize def websocket_action : LuckyRouter::Match(Lucky::Action.class)? - Lucky::Router.find_action(:ws, context.request.path) - end + memoize def websocket_action : LuckyRouter::Match(Lucky::Action.class)? + Lucky::Router.find_action(:ws, context.request.path) + end - private def websocket_upgrade_request?(context) - return unless upgrade = context.request.headers["Upgrade"]? - return unless upgrade.compare("websocket", case_insensitive: true) == 0 + private def websocket_upgrade_request?(context) + return unless upgrade = context.request.headers["Upgrade"]? + return unless upgrade.compare("websocket", case_insensitive: true) == 0 - context.request.headers.includes_word?("Connection", "Upgrade") + context.request.headers.includes_word?("Connection", "Upgrade") + end end end From 53b3f2d2460be28899c3229e20e723f612403af4 Mon Sep 17 00:00:00 2001 From: Jeremy Woertink Date: Fri, 4 Dec 2020 10:52:46 -0800 Subject: [PATCH 4/5] trying to figure out how to structure the web sockets --- src/lucky/web_socket_action.cr | 17 +++++++++++------ src/lucky/web_socket_handler.cr | 9 +++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/lucky/web_socket_action.cr b/src/lucky/web_socket_action.cr index 1c0e607bf..2bc797c10 100644 --- a/src/lucky/web_socket_action.cr +++ b/src/lucky/web_socket_action.cr @@ -1,4 +1,7 @@ -abstract class Lucky::WebSocketAction < Lucky::Action +require "./*" + +abstract class Lucky::WebSocketAction + getter :context, :route_params getter websocket : Lucky::WebSocket def initialize(@context : HTTP::Server::Context, @route_params : Hash(String, String)) @@ -12,9 +15,11 @@ abstract class Lucky::WebSocketAction < Lucky::Action abstract def call(socket : HTTP::WebSocket) - def call - raise <<-ERROR - WebSocketAction must define `call(socket)` - ERROR - end + include Lucky::ActionDelegates + include Lucky::Exposable + include Lucky::Routable + include Lucky::Renderable + include Lucky::ParamHelpers + include Lucky::ActionPipes + end diff --git a/src/lucky/web_socket_handler.cr b/src/lucky/web_socket_handler.cr index b85c7c68a..7aba69e48 100644 --- a/src/lucky/web_socket_handler.cr +++ b/src/lucky/web_socket_handler.cr @@ -3,18 +3,15 @@ module Lucky include HTTP::Handler def call(context : HTTP::Server::Context) - if ws_route_found?(context) && websocket_upgrade_request?(context) - websocket_action.payload.new(context, websocket_action.params).websocket.call(context) + if (match = ws_route_found?(context)) && websocket_upgrade_request?(context) + action = match.payload.new(context, match.params) + action.websocket.call(context) else call_next(context) end end private def ws_route_found?(context) - !!websocket_action - end - - memoize def websocket_action : LuckyRouter::Match(Lucky::Action.class)? Lucky::Router.find_action(:ws, context.request.path) end From 07066d1ec9f53b7d835a6be9b9381cb044391a5d Mon Sep 17 00:00:00 2001 From: Jeremy Woertink Date: Mon, 24 May 2021 08:53:43 -0700 Subject: [PATCH 5/5] This setup requires a bit less modification to work. --- src/lucky/routable.cr | 8 ++--- src/lucky/web_socket.cr | 8 ----- src/lucky/web_socket_action.cr | 31 ++++++++----------- ...handler.cr => web_socket_route_handler.cr} | 17 +++++----- 4 files changed, 24 insertions(+), 40 deletions(-) delete mode 100644 src/lucky/web_socket.cr rename src/lucky/{web_socket_handler.cr => web_socket_route_handler.cr} (51%) diff --git a/src/lucky/routable.cr b/src/lucky/routable.cr index 88e95d4db..ba15a76b6 100644 --- a/src/lucky/routable.cr +++ b/src/lucky/routable.cr @@ -64,10 +64,6 @@ module Lucky::Routable {% path.raise "Path must start with a slash. Example: '/#{path}'" %} {% end %} - {% if block.args.size != 1 %} - {% raise "ws takes a block with 1 arg." %} - {% end %} - add_route(:ws, {{ path }}, {{ @type.name.id }}) setup_ws_call_method(block) @@ -106,7 +102,7 @@ module Lucky::Routable abstract def on_message(message) abstract def on_close - def call(socket : Lucky::WebSocket) + def call # Ensure clients_desired_format is cached by calling it clients_desired_format @@ -115,8 +111,8 @@ module Lucky::Routable %response = if %pipe_result.is_a?(Lucky::Response) %pipe_result else - {{ block.args.first }} = socket {{ block.body }} + send_text_response "", content_type: "plain/text" end %pipe_result = run_after_pipes diff --git a/src/lucky/web_socket.cr b/src/lucky/web_socket.cr deleted file mode 100644 index 995fa0a9b..000000000 --- a/src/lucky/web_socket.cr +++ /dev/null @@ -1,8 +0,0 @@ -module Lucky - class WebSocket < HTTP::WebSocketHandler - getter proc - - def initialize(@action : Lucky::Action.class, &@proc : HTTP::WebSocket, HTTP::Server::Context -> Void) - end - end -end diff --git a/src/lucky/web_socket_action.cr b/src/lucky/web_socket_action.cr index 2bc797c10..99dcce354 100644 --- a/src/lucky/web_socket_action.cr +++ b/src/lucky/web_socket_action.cr @@ -1,25 +1,20 @@ -require "./*" - -abstract class Lucky::WebSocketAction - getter :context, :route_params - getter websocket : Lucky::WebSocket +abstract class Lucky::WebSocketAction < Lucky::Action + @socket : HTTP::WebSocket? + @handler : HTTP::WebSocketHandler def initialize(@context : HTTP::Server::Context, @route_params : Hash(String, String)) - @websocket = Lucky::WebSocket.new(self.class) do |ws| - ws.on_ping { ws.pong(@context.request.path) } - ws.on_message { |message| on_message(message) } - ws.on_close { on_close } - call(ws) + @handler = HTTP::WebSocketHandler.new do |ws| + @socket = ws + ws.on_ping { ws.pong("PONG") } + call end end - abstract def call(socket : HTTP::WebSocket) - - include Lucky::ActionDelegates - include Lucky::Exposable - include Lucky::Routable - include Lucky::Renderable - include Lucky::ParamHelpers - include Lucky::ActionPipes + def perform_websocket_action + @handler.call(@context) + end + def socket : HTTP::WebSocket + @socket.not_nil! + end end diff --git a/src/lucky/web_socket_handler.cr b/src/lucky/web_socket_route_handler.cr similarity index 51% rename from src/lucky/web_socket_handler.cr rename to src/lucky/web_socket_route_handler.cr index 7aba69e48..35c29f92d 100644 --- a/src/lucky/web_socket_handler.cr +++ b/src/lucky/web_socket_route_handler.cr @@ -1,20 +1,21 @@ module Lucky - class WebSocketHandler + class WebSocketRouteHandler include HTTP::Handler def call(context : HTTP::Server::Context) - if (match = ws_route_found?(context)) && websocket_upgrade_request?(context) - action = match.payload.new(context, match.params) - action.websocket.call(context) + if websocket_upgrade_request?(context) + handler = Lucky::Router.find_action(:ws, context.request.path) + if handler + Lucky::Log.dexter.debug { {handled_by: handler.payload.to_s} } + handler.payload.new(context, handler.params).as(Lucky::WebSocketAction).perform_websocket_action + else + call_next(context) + end else call_next(context) end end - private def ws_route_found?(context) - Lucky::Router.find_action(:ws, context.request.path) - end - private def websocket_upgrade_request?(context) return unless upgrade = context.request.headers["Upgrade"]? return unless upgrade.compare("websocket", case_insensitive: true) == 0