Compare commits
10 commits
845ccb10ab
...
9d7d72377e
Author | SHA1 | Date | |
---|---|---|---|
9d7d72377e | |||
c3cd44205f | |||
3c2dcc73e5 | |||
2077cfb8b9 | |||
50dfd25450 | |||
ccac1cf651 | |||
dc548fcb83 | |||
1a8894eece | |||
bcf875b1d8 | |||
d2fadb58f9 |
5 changed files with 75 additions and 5 deletions
|
@ -6,8 +6,8 @@
|
||||||
# - WebSocket(ws)
|
# - WebSocket(ws)
|
||||||
# - before_*
|
# - before_*
|
||||||
# - error
|
# - error
|
||||||
HTTP_METHODS = %w(get post put patch delete options)
|
HTTP_METHODS = %w(get post put patch delete options head)
|
||||||
FILTER_METHODS = %w(get post put patch delete options all)
|
FILTER_METHODS = %w(get post put patch delete options head all)
|
||||||
|
|
||||||
{% for method in HTTP_METHODS %}
|
{% for method in HTTP_METHODS %}
|
||||||
def {{method.id}}(path : String, &block : HTTP::Server::Context -> _)
|
def {{method.id}}(path : String, &block : HTTP::Server::Context -> _)
|
||||||
|
|
|
@ -2,7 +2,7 @@ class HTTP::Server::Response
|
||||||
class Output
|
class Output
|
||||||
def close
|
def close
|
||||||
# ameba:disable Style/NegatedConditionsInUnless
|
# 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
|
response.content_length = @out_count
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ module Kemal
|
||||||
# a corresponding `HEAD` route.
|
# a corresponding `HEAD` route.
|
||||||
def add_route(method : String, path : String, &handler : HTTP::Server::Context -> _)
|
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 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
|
end
|
||||||
|
|
||||||
# Looks up the route from the Radix::Tree for the first time and caches to improve performance.
|
# Looks up the route from the Radix::Tree for the first time and caches to improve performance.
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
require "http"
|
||||||
|
|
||||||
module Kemal
|
module Kemal
|
||||||
# Takes 2 parameters: *path* and a *handler* to specify
|
# Takes 2 parameters: *path* and a *handler* to specify
|
||||||
# what action to be done if the route is matched.
|
# 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)
|
def initialize(@path : String, &@proc : HTTP::WebSocket, HTTP::Server::Context -> Void)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def error(code : Int16, message : String)
|
||||||
|
err = WebsocketError.new(code, message)
|
||||||
|
raise err
|
||||||
|
end
|
||||||
|
|
||||||
def call(context : HTTP::Server::Context)
|
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
|
||||||
|
# TODO: check if 500 is what we're supposed to give
|
||||||
|
context.response.status_code = 500
|
||||||
|
|
||||||
|
if error.is_a?(WebsocketError)
|
||||||
|
code = error.code.to_i16
|
||||||
|
message = error.status_message
|
||||||
|
else
|
||||||
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,24 @@
|
||||||
module Kemal
|
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
|
class WebSocketHandler
|
||||||
include HTTP::Handler
|
include HTTP::Handler
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue