Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Making it compatible with Chrome WebSocket implementation and pywebso…
…cket.

Adding port number to Host header.
Letting server send header in a specific order, otherwise connection with Chrome WebSocket fails.
  • Loading branch information
Hiroshi Ichikawa committed Nov 28, 2009
1 parent 771f26e commit 2621fc6
Showing 1 changed file with 43 additions and 12 deletions.
55 changes: 43 additions & 12 deletions lib/web_socket.rb
Expand Up @@ -35,6 +35,12 @@ def ord


class WebSocket class WebSocket


class << self

attr_accessor(:debug)

end

class Error < RuntimeError class Error < RuntimeError


end end
Expand All @@ -44,7 +50,7 @@ def initialize(arg, params = {})


@server = params[:server] @server = params[:server]
@socket = arg @socket = arg
line = @socket.gets().chomp() line = gets().chomp()
if !(line =~ /\AGET (\S+) HTTP\/1.1\z/n) if !(line =~ /\AGET (\S+) HTTP\/1.1\z/n)
raise(WebSocket::Error, "invalid request: #{line}") raise(WebSocket::Error, "invalid request: #{line}")
end end
Expand All @@ -65,17 +71,18 @@ def initialize(arg, params = {})
raise(WebSocket::Error, "unsupported scheme: #{uri.scheme}") raise(WebSocket::Error, "unsupported scheme: #{uri.scheme}")
end end
@path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "") @path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "")
host = uri.host + (uri.port == 80 ? "" : ":#{uri.port}")
origin = params[:origin] || "http://#{uri.host}" origin = params[:origin] || "http://#{uri.host}"
@socket = TCPSocket.new(uri.host, uri.port || 80) @socket = TCPSocket.new(uri.host, uri.port || 80)
@socket.write( write(
"GET #{@path} HTTP/1.1\r\n" + "GET #{@path} HTTP/1.1\r\n" +
"Upgrade: WebSocket\r\n" + "Upgrade: WebSocket\r\n" +
"Connection: Upgrade\r\n" + "Connection: Upgrade\r\n" +
"Host: #{uri.host}\r\n" + "Host: #{host}\r\n" +
"Origin: #{origin}\r\n" + "Origin: #{origin}\r\n" +
"\r\n") "\r\n")
@socket.flush() flush()
line = @socket.gets().chomp() line = gets().chomp()
raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n) raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n)
read_header() read_header()
if @header["WebSocket-Origin"] != origin if @header["WebSocket-Origin"] != origin
Expand All @@ -91,6 +98,8 @@ def initialize(arg, params = {})


attr_reader(:server, :header, :path) attr_reader(:server, :header, :path)


REQUIRED_HEADER_KEYS = ["Upgrade", "Connection", "WebSocket-Origin", "WebSocket-Location"]

def handshake(status = nil, header = {}) def handshake(status = nil, header = {})
if @handshaked if @handshaked
raise(WebSocket::Error, "handshake has already been done") raise(WebSocket::Error, "handshake has already been done")
Expand All @@ -103,11 +112,14 @@ def handshake(status = nil, header = {})
"WebSocket-Location" => @server.uri + @path, "WebSocket-Location" => @server.uri + @path,
} }
header = def_header.merge(header) header = def_header.merge(header)
header_str = header.map(){ |k, v| "#{k}: #{v}\r\n" }.join("") # It seems 4 required header entries must appear in this order, otherwise connection with
@socket.write( # Chrome WebSocket implementation fails.
keys = REQUIRED_HEADER_KEYS + (header.keys - REQUIRED_HEADER_KEYS)
header_str = keys.map(){ |k| "#{k}: #{header[k]}\r\n" }.join("")
write(
"HTTP/1.1 #{status}\r\n" + "HTTP/1.1 #{status}\r\n" +
"#{header_str}\r\n") "#{header_str}\r\n")
@socket.flush() flush()
@handshaked = true @handshaked = true
end end


Expand All @@ -116,15 +128,15 @@ def send(data)
raise(WebSocket::Error, "call WebSocket\#handshake first") raise(WebSocket::Error, "call WebSocket\#handshake first")
end end
data = data.dup().force_encoding("ASCII-8BIT") data = data.dup().force_encoding("ASCII-8BIT")
@socket.write("\x00#{data}\xff") write("\x00#{data}\xff")
@socket.flush() flush()
end end


def receive() def receive()
if !@handshaked if !@handshaked
raise(WebSocket::Error, "call WebSocket\#handshake first") raise(WebSocket::Error, "call WebSocket\#handshake first")
end end
packet = @socket.gets("\xff") packet = gets("\xff")
return nil if !packet return nil if !packet
if !(packet =~ /\A\x00(.*)\xff\z/nm) if !(packet =~ /\A\x00(.*)\xff\z/nm)
raise(WebSocket::Error, "input must start with \\x00 and end with \\xff") raise(WebSocket::Error, "input must start with \\x00 and end with \\xff")
Expand Down Expand Up @@ -152,7 +164,7 @@ def close()


def read_header() def read_header()
@header = {} @header = {}
@socket.each_line() do |line| while line = gets()
line = line.chomp() line = line.chomp()
break if line.empty? break if line.empty?
if !(line =~ /\A(\S+): (.*)\z/n) if !(line =~ /\A(\S+): (.*)\z/n)
Expand All @@ -168,6 +180,25 @@ def read_header()
end end
end end


def gets(rs = $/)
line = @socket.gets(rs)
$stderr.printf("recv> %p\n" % line) if WebSocket.debug
return line
end

def write(data)
if WebSocket.debug
data.scan(/\G(.*?(\n|\z))/n) do
$stderr.printf("send> %p\n" % $&) if !$&.empty?
end
end
@socket.write(data)
end

def flush()
@socket.flush()
end

end end




Expand Down

0 comments on commit 2621fc6

Please sign in to comment.