mirror of
https://github.com/faye/websocket-driver-ruby.git
synced 2025-11-01 13:59:38 +00:00
140 lines
3.9 KiB
Ruby
140 lines
3.9 KiB
Ruby
module WebSocket
|
|
class Driver
|
|
|
|
class Client < Hybi
|
|
VALID_SCHEMES = %w[ws wss]
|
|
|
|
def self.generate_key
|
|
Base64.strict_encode64(SecureRandom.random_bytes(16))
|
|
end
|
|
|
|
attr_reader :status, :headers
|
|
|
|
def initialize(socket, options = {})
|
|
super
|
|
|
|
@ready_state = -1
|
|
@key = Client.generate_key
|
|
@accept = Hybi.generate_accept(@key)
|
|
@http = HTTP::Response.new
|
|
|
|
uri = URI.parse(@socket.url)
|
|
unless VALID_SCHEMES.include?(uri.scheme)
|
|
raise URIError, "#{ socket.url } is not a valid WebSocket URL"
|
|
end
|
|
|
|
path = (uri.path == '') ? '/' : uri.path
|
|
@pathname = path + (uri.query ? '?' + uri.query : '')
|
|
|
|
@headers['Host'] = Driver.host_header(uri)
|
|
@headers['Upgrade'] = 'websocket'
|
|
@headers['Connection'] = 'Upgrade'
|
|
@headers['Sec-WebSocket-Key'] = @key
|
|
@headers['Sec-WebSocket-Version'] = VERSION
|
|
|
|
if @protocols.size > 0
|
|
@headers['Sec-WebSocket-Protocol'] = @protocols * ', '
|
|
end
|
|
|
|
if uri.user
|
|
auth = Base64.strict_encode64([uri.user, uri.password] * ':')
|
|
@headers['Authorization'] = 'Basic ' + auth
|
|
end
|
|
end
|
|
|
|
def version
|
|
"hybi-#{ VERSION }"
|
|
end
|
|
|
|
def proxy(origin, options = {})
|
|
Proxy.new(self, origin, options)
|
|
end
|
|
|
|
def start
|
|
return false unless @ready_state == -1
|
|
@socket.write(handshake_request)
|
|
@ready_state = 0
|
|
true
|
|
end
|
|
|
|
def parse(chunk)
|
|
return if @ready_state == 3
|
|
return super if @ready_state > 0
|
|
|
|
@http.parse(chunk)
|
|
return fail_handshake('Invalid HTTP response') if @http.error?
|
|
return unless @http.complete?
|
|
|
|
validate_handshake
|
|
return if @ready_state == 3
|
|
|
|
open
|
|
parse(@http.body)
|
|
end
|
|
|
|
private
|
|
|
|
def handshake_request
|
|
extensions = @extensions.generate_offer
|
|
@headers['Sec-WebSocket-Extensions'] = extensions if extensions
|
|
|
|
start = "GET #{ @pathname } HTTP/1.1"
|
|
headers = [start, @headers.to_s, '']
|
|
headers.join("\r\n")
|
|
end
|
|
|
|
def fail_handshake(message)
|
|
message = "Error during WebSocket handshake: #{ message }"
|
|
@ready_state = 3
|
|
emit(:error, ProtocolError.new(message))
|
|
emit(:close, CloseEvent.new(ERRORS[:protocol_error], message))
|
|
end
|
|
|
|
def validate_handshake
|
|
@status = @http.code
|
|
@headers = Headers.new(@http.headers)
|
|
|
|
unless @http.code == 101
|
|
return fail_handshake("Unexpected response code: #{ @http.code }")
|
|
end
|
|
|
|
upgrade = @http['Upgrade'] || ''
|
|
connection = @http['Connection'] || ''
|
|
accept = @http['Sec-WebSocket-Accept'] || ''
|
|
protocol = @http['Sec-WebSocket-Protocol'] || ''
|
|
|
|
if upgrade == ''
|
|
return fail_handshake("'Upgrade' header is missing")
|
|
elsif upgrade.downcase != 'websocket'
|
|
return fail_handshake("'Upgrade' header value is not 'WebSocket'")
|
|
end
|
|
|
|
if connection == ''
|
|
return fail_handshake("'Connection' header is missing")
|
|
elsif connection.downcase != 'upgrade'
|
|
return fail_handshake("'Connection' header value is not 'Upgrade'")
|
|
end
|
|
|
|
unless accept == @accept
|
|
return fail_handshake('Sec-WebSocket-Accept mismatch')
|
|
end
|
|
|
|
unless protocol == ''
|
|
if @protocols.include?(protocol)
|
|
@protocol = protocol
|
|
else
|
|
return fail_handshake('Sec-WebSocket-Protocol mismatch')
|
|
end
|
|
end
|
|
|
|
begin
|
|
@extensions.activate(@headers['Sec-WebSocket-Extensions'])
|
|
rescue ::WebSocket::Extensions::ExtensionError => error
|
|
return fail_handshake(error.message)
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|