-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
WebSocket#close accepts code and reason arguments #7483
Conversation
I'm also thinking of parsing on socket close frame as well: crystal/src/http/web_socket.cr Line 138 in 60760a5
So a developer would write socket.on_close do |reason, code|
puts "Closed websocket with #{code} #{reason}"
end |
Yes, we need to also have close frame decoding, but similar to my last comment, please: ws.on_close do |code, reason|
# ..
end |
Gonna work on it tomorrow |
Please help me writing |
Hi @vladfaust, following the http negotiation spec I believe this should be acceptable. This passes for the existing 0.27.2 implementation: it "calls close handler with code and reason" do
address_chan = Channel(Socket::IPAddress).new
spawn do
http_ref = nil
ws_handler = HTTP::WebSocketHandler.new do |ws, ctx|
ws.on_message do
# ws.close(1000, "goodbye")
ws.close("goodbye")
http_ref.not_nil!.close
end
ws.send("hello")
end
http_server = http_ref = HTTP::Server.new([ws_handler])
address = http_server.bind_unused_port
address_chan.send(address)
http_server.listen
end
listen_address = address_chan.receive
ws = HTTP::WebSocket.new("ws://#{listen_address}")
ws.on_message do
ws.send("hello")
end
# ws.on_close do |code, reason|
ws.on_close do |message|
# code.should eq 1000
# reason.should eq "goodbye"
message.should eq "goodbye"
end
ws.run
end Edits for your new spec as commented. Seeing as there aren't individual specs like this for the other handlers, maybe it should just be added to the spec I linked. I'm not sure though. I would add whatever you think is best so that we can be sure it works, and then spec style can be addressed in review 👍 |
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.
Just a few other details, the rest I think is okay. I'll leave the rest to the core team, thank you!
send(Bytes.empty, Opcode::CLOSE) | ||
end | ||
def close | ||
send(Bytes.empty, Opcode::CLOSE) |
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.
Just my opinion, but I think def close
should send 1000 Normal Closure
(delegate close(1000)
) per RFC 6455 Sec. 7.4.1
Please document this detail if you accept it
Edit: On second thought, maybe disregard. There is the reserved 1005 close code which is not explicitly sent, but is implicitly received when a client sends a close frame with no body. 🤷♂️
end | ||
|
||
# Closes the websocket with *code* and *reason*. | ||
def close(code : Int16, reason : String? = nil) |
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.
Actually, shouldn't code
be UInt16
everywhere?
a 2-byte unsigned integer (in network byte order) representing a status code with value /code/
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.
@Sija see in the discussion above
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.
Can't find it, which discussion?
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.
reason = @current_message.gets_to_end | ||
@on_close.try &.call(code, reason) | ||
close(code, reason) unless closed? | ||
rescue IO::EOFError |
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.
Shouldn't this be ensure
?
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.
Makes sense
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.
🏓
send(Bytes.empty, Opcode::CLOSE) | ||
end | ||
|
||
def close(code : Int16, reason : String? = nil) |
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.
ditto
Can't code have a default value other than nil? |
@asterite In my suggestions I considered close code 1005, which is a special case; it is never meant to be sent by clients, but receiving clients interpret a body-less close frame as close 1005. I suppose we could do def close(code : UInt16 = 1005, reason : String? = nil)
if code == 1005
send(Bytes.empty, Opcode::CLOSE)
else
# send body
end
end but then this is confusing (to someone unfamiliar with ws protocol), because reason will never be sent: ws.close(1005, "reason")
# or
ws.close(reason: "reason") I think two methods is a cleaner separation of these semantics. |
Reading https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Properties |
@bew IMO using an enum here would be too limiting. Docs especially state that some codes are:
or
|
I don't think an enum would be bad. Enums are not strict in accepting integer values by default, so I don't see how it would be limiting. Basically, this proposal is #7247 but for websockets (see the |
FYI: I'm not very pushy about that because |
The first two bytes of the message in a WebSocket Close frame is a numeric code. Make sure to always encode this correctly, to avoid being in violation of the websocket spec. Also automatically decode this for clients. This is a breaking change since on_close now takes two arguments: close code and message, but this change was unable to be made in a backwards compatible way. Closes crystal-lang#6469 Closes crystal-lang#7483
The first two bytes of the message in a WebSocket Close frame is a numeric code. Make sure to always encode this correctly, to avoid being in violation of the websocket spec. Also automatically decode this for clients. This is a breaking change since on_close now takes two arguments: close code and message, but this change was unable to be made in a backwards compatible way. Closes crystal-lang#6469 Closes crystal-lang#7483
Fixes #6469
I saw WebSocket#close specs and they are pretty simple, but I'm absolutely not sure how to convertString.new(bytes[2, bytes[1]]).should eq(message)
line to test both reason and code arguments.Also see https://developer.mozilla.org/ru/docs/Web/API/WebSocket/close.