RubyでつくるWebSocketサーバーにprotocol-websocket gemを取り入れる その3 - @ledsun blog ではWEBrickを使ってHTTPレスポンス文字列を作成しました。
今度は protocol-websocket を使ってHTTPレスポンス文字列を作成します。
require 'socket'
require 'webrick'
require 'protocol/websocket/headers'
require 'protocol/http/headers'
require 'protocol/http/response'
def read_headers_from(socket)
request = WEBrick::HTTPRequest.new(WEBrick::Config::HTTP)
request.parse(socket)
request.header
end
def calculate_accept_nonce_from(headers)
key = headers[Protocol::WebSocket::Headers::SEC_WEBSOCKET_KEY].first
Protocol::WebSocket::Headers::Nounce.accept_digest(key)
end
server = TCPServer.new 'localhost',
2345
loop do
socket = server.accept
headers = read_headers_from socket
unless headers["upgrade"] = Protocol::WebSocket::Headers::PROTOCOL
puts "Not a websocket request"
socket.close
next
end
response_key = calculate_accept_nonce_from headers
puts "response_key: #{response_key}"
response_headers = Protocol::HTTP::Headers.new
response_headers.add Protocol::WebSocket::Headers::SEC_WEBSOCKET_ACCEPT, response_key
response = Protocol::HTTP::Response.new("HTTP/1.1",
101,
response_headers,
nil,
Protocol::WebSocket::Headers::PROTOCOL)
response_string = <<~HTTP
#{response.version} #{response.status} Switching Protocols
Upgrade: websocket
Connection: Upgrade
#{response.headers.to_h.map { |k, v| "#{k}: #{v.join}" }.join("\n")}
HTTP
puts response_string
socket.write response_string
puts 'Handshake response sent'
first_byte = socket.getbyte
fin = first_byte & 0b10000000
opcode = first_byte & 0b00001111
raise 'fin bit is not set' unless fin
raise 'opcode is not a text' unless opcode == 0x1
second_byte = socket.getbyte
is_masked = second_byte & 0b10000000
payload_size = second_byte & 0b01111111
raise 'mask bit is not set' unless is_masked
raise 'payload size > 125 is not supported' unless payload_size <= 125
puts "Payload size: #{payload_size}"
mask = 4.times.map { socket.getbyte }
puts "Mask: #{mask}"
data = payload_size.times.map.with_index { socket.getbyte ^ mask[_2 % 4] }
puts "Data: #{data.pack('C*')}"
response_message = "Loud and clear!"
response = [0b10000001,
response_message.size,
response_message
].pack("CCA#{response_message.size}")
socket.write response
socket.close
end
レスポンス文字列作成部分だけを抜き出します。
protocol-websocketでは
response_headers = Protocol::HTTP::Headers.new
response_headers.add Protocol::WebSocket::Headers::SEC_WEBSOCKET_ACCEPT, response_key
response = Protocol::HTTP::Response.new("HTTP/1.1",
101,
response_headers,
nil,
Protocol::WebSocket::Headers::PROTOCOL)
response_string = <<~HTTP
#{response.version} #{response.status} Switching Protocols
Upgrade: websocket
Connection: Upgrade
#{response.headers.to_h.map { |k, v| "#{k}: #{v.join}" }.join("\n")}
HTTP
socket.write response_string
Protocol::HTTP::Response
にはレスポンス文字列を返すメソッドがなかったので、なかなか面倒になってしまいました。
WebSocketでは
response = WEBrick::HTTPResponse.new(WEBrick::Config::HTTP)
response.status = 101
response.upgrade! Protocol::WebSocket::Headers::PROTOCOL
response['Sec-WebSocket-Accept'] = response_key
response.send_response socket
でした。
どうやらHTTPリクエストやHTTPレスポンスを素朴に扱いたい場合は、WEBrickを使う方が便利そうです。
次回はいよいよWebSocketのFrameを作る部分でs。う