-
-
Notifications
You must be signed in to change notification settings - Fork 162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WebSockets #1305
base: main
Are you sure you want to change the base?
WebSockets #1305
Changes from 2 commits
58c9559
3244366
992b68c
53b3f2d
3f93318
07066d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 }}) | ||
jwoertink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My thought was by having these abstract, the websocket can proxy to them, and forces you to set these up. Websockets have a few other methods though.... are any of them as necessary? Is there ever a time you'd use a websocket and not use these? |
||
|
||
def call(socket : Lucky::WebSocket) | ||
jwoertink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module Lucky | ||
class WebSocket < HTTP::WebSocketHandler | ||
getter proc | ||
|
||
jwoertink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def initialize(@action : Lucky::Action.class, &@proc : HTTP::WebSocket, HTTP::Server::Context -> Void) | ||
jwoertink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we don't make |
||
call(ws) | ||
end | ||
end | ||
|
||
abstract def call(socket : Lucky::WebSocket) | ||
|
||
def call | ||
raise <<-ERROR | ||
WebSocketAction must define `call(socket)` | ||
jwoertink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ERROR | ||
end | ||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
class WebSocketHandler | ||
jwoertink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
jwoertink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
jwoertink marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would need
wss
too