Рукопожатие WebSocket с Ruby и EM::WebSocket::Server

Я пытаюсь создать простое соединение WebSocket в JavaScript с моим Rails-приложением. Я получаю следующее:

Сбой подключения WebSocket к "ws://localhost:4000/": ошибка во время рукопожатия WebSocket: отсутствует заголовок "Sec-WebSocket-Accept"

Что я делаю неправильно? Вот мой код:

JavaScript:

var socket = new WebSocket('ws://localhost:4000');

socket.onopen = function() {
  var handshake =
    "GET / HTTP/1.1\n" +
    "Host: localhost\n" +
    "Upgrade: websocket\n" +
    "Connection: Upgrade\n" +
    "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\n" +
    "Sec-WebSocket-Protocol: quote\n" +
    "Sec-WebSocket-Version: 13\n" +
    "Origin: http://localhost\n";

  socket.send(handshake);
};

socket.onmessage = function(data) {
  console.log(data);
};

Рубин:

require 'rubygems'
require 'em-websocket-server'

module QuoteService
  class WebSocket < EventMachine::WebSocket::Server
    def on_connect
      handshake_response =  "HTTP/1.1 101 Switching Protocols\n"
      handshake_response << "Upgrade: websocket\n"
      handshake_response << "Connection: Upgrade\n"
      handshake_response << "Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=\n"
      handshake_response << "Sec-WebSocket-Protocol: quote\n"

      send_message(handshake_response)
    end

    def on_receive(data)
      puts 'RECEIVED: ' + data
    end
  end
end

EventMachine.run do
  print 'Starting WebSocket server...'
  EventMachine.start_server '0.0.0.0', 4000, QuoteService::WebSocket
  puts 'running'
end

Заголовки рукопожатия приведены в Википедии.

1 ответ

Решение

1) Я думаю, что как только соединение открыто, запрос и ответ уже произошли, поэтому отправка заголовков в этот момент слишком поздняя. Кроме того, заголовки должны заканчиваться пустой строкой, которую вы пропустили.

2) Согласно демоверсиям, вам даже не нужно устанавливать заголовки на клиенте или на сервере - модуль ruby ​​автоматически заботится о заголовках на стороне сервера, а html5 автоматически заботится о заголовках на стороне клиента., Я думаю, что это должно работать:

require "em-websocket-server"

class EchoServer < EM::WebSocket::Server

  def on_connect
    EM::WebSocket::Log.debug "Connected"
    puts "I felt a connection."
  end

  def on_receive msg
    puts "RECEIVED: #{msg}"
    send_message msg
  end

end

EM.run do
  myhost = "0.0.0.0"
  myport = 8000
  puts "Starting WebSocket server.  Listening on port #{myport}..."
  EM.start_server myhost, myport, EchoServer
end  

HTML-файл:

<!DOCTYPE html> <html> <head><title>Test</title>

<script type="text/javascript">

  var myWebSocket = new WebSocket("ws://localhost:8000");

  myWebSocket.onopen = function(evt)    { 
    console.log("Connection open. Sending message..."); 
    myWebSocket.send("Hello WebSockets!");       };

  myWebSocket.onmessage = function(evt)    { 
    console.log(evt.data);
    myWebSocket.close();   };

  myWebSocket.onclose = function(evt)    { 
    console.log("Connection closed.");    };

  myWebSocket.onerror = function(err)   {
    alert(err.name + " => " + err.message);   } </script>

</head> <body>   <div>Hello</div> </body> </html>

И это работает в Safari 5.1.9 (который является более старым браузером): я вижу ожидаемый результат как на сервере, так и на клиенте. Тем не менее, код не работает в Firefox 21: я получаю сообщение об ошибке...

Firefox can't establish a connection to the server at ws://localhost:8000/.
    var myWebSocket = new WebSocket("ws://localhost:8000");

Я заметил, что и в Firebug, и в Safari Developer Tools сервер не отправляет заголовок Sec-WebSocket-Accept:

Response Headers

Connection          Upgrade
Upgrade         WebSocket
WebSocket-Location  ws://localhost:8000/
WebSocket-Origin    null


Request Headers

Accept                  text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding         gzip, deflate
Accept-Language         en-US,en;q=0.5
Cache-Control           no-cache
Connection          keep-alive, Upgrade
DNT                 1
Host                    localhost:8000
Origin                  null
Pragma                  no-cache
Sec-WebSocket-Key   r9xT+ywe533EHF09wxelkg==
Sec-WebSocket-Version   13
Upgrade                 websocket
User-Agent          Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:21.0) Gecko/20100101 Firefox/21.0

Ничто из того, что я пробовал, не заставляло код работать в Firefox 21.0 Чтобы проверить, поддерживает ли Firefox 21.0 даже веб-сокеты, я обратился к:

http://www.websocket.org/echo.html  

и он сказал, что мой браузер поддерживает веб-сокеты.

3) Есть ли причина, по которой вы должны использовать модуль em-websocket-server? Последняя модификация этого модуля на github была три года назад. И всякий раз, когда вы видите require rubygems в коде ruby, это должно предупредить вас, что код старый. Я попробовал новый модуль em-websocket, и мне удалось успешно передавать данные туда и обратно с помощью веб-сокетов как в Firefox 21.0, так и в Safari 5.1.9:

require 'em-websocket'

myhost = "0.0.0.0"
myport = 8000

EM.run {
  puts "Listening on port #{myport}..."

  EM::WebSocket.run(:host => myhost, :port => myport, :debug => false) do |ws|

    ws.onopen do |handshake|
      path = handshake.path
      query_str = handshake.query
      origin = handshake.origin

      puts "WebSocket opened:"
      puts "\t path  \t\t -> #{path}" 
      puts "\t query_str \t -> #{query_str}"
      puts "\t origin \t -> #{origin}"
    end 

    ws.onmessage { |msg|
      ws.send "Pong: #{msg}"
    }
    ws.onclose {
      puts "WebSocket closed"
    }
    ws.onerror { |e|
      puts "Error: #{e.message}"
    }
  end
}

Тот же код на стороне клиента. Теперь заголовки ответа включают Sec-WebSocket-Accept:

Response Headers

Connection          Upgrade
Sec-WebSocket-Accept    LyIm6d+kAAqkcTR744tVK9HMepY=
Upgrade                 websocket


Request Headers

Accept  text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding gzip, deflate
Accept-Language en-US,en;q=0.5
Cache-Control   no-cache
Connection  keep-alive, Upgrade
DNT 1
Host    localhost:8000
Origin  null
Pragma  no-cache
Sec-WebSocket-Key   pbK8lFHQAF+arl9tFvHn/Q==
Sec-WebSocket-Version   13
Upgrade websocket
User-Agent  Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:21.0) Gecko/20100101 Firefox/21.0

В вашем коде я не думаю, что вы устанавливаете какие-либо заголовки. Вместо этого вы просто отправляете сообщения, которые содержат символы, похожие на заголовки. По-видимому, ваш браузер требует заголовок Sec-WebSocket-Accept в ответе, прежде чем он разрешит соединение, и когда модуль em-websocket-server не может установить этот заголовок в ответе, ваш браузер отклоняет соединение.

Соответствующий исходный код для em-websockets-server выглядит следующим образом:

module EM
  module WebSocket
    module Protocol
      module Version76

        # generate protocol 76 compatible response headers
        def response
          response = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
          response << "Upgrade: WebSocket\r\n"
          response << "Connection: Upgrade\r\n"
          response << "Sec-WebSocket-Origin: #{origin}\r\n"
          response << "Sec-WebSocket-Location: #{scheme}://#{host}#{path}\r\n"

          if protocol
            response << "Sec-WebSocket-Protocol: #{protocol}\r\n"
          end

          response << "\r\n"
          response << Digest::MD5.digest(keyset)

          response
        end

Как видите, он не устанавливает заголовок Sec-WebSocket-Accept. Этот код находится в модуле под названием Version76, и поиск в Google для поиска веб-сокетов версии 76 дает устаревший протокол (который содержит пример запроса и ответа):

http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76

Вот текущий протокол websockets (который также содержит пример запроса и ответа):

http://tools.ietf.org/html/rfc6455

Вывод: em-websockets-server устарел.

Другие вопросы по тегам