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 # ここら辺はecho_server.rbと同じ。純粋なTCP通信 socket = server.accept # HTTPリクエストheaderを読み込む headers = read_headers_from socket # WebSocketリクエストかどうかを判定する 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 # ソケットそのものはHTTP通信で使われているものと同じ socket.write response_string puts 'Handshake response sent' # ここからはWebSocket通信 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。う