Sinatra与EventMachine WebSockets配合使用有什么成功案例吗?

37

我一段时间以来一直在使用Sinatra,现在我想通过Websockets推送数据向我的Web应用程序添加实时功能。

我已经成功地独立使用了' em-websocket '宝石,但是我无法编写一个Ruby文件,其中包含Sinatra Web服务器和Web Socket服务器。

我尝试将run!或start!方法放在单独的线程中运行,但没有成功。

有人成功过吗?

我希望它们在同一个文件中,这样可以在两个服务器之间共享变量。

5个回答

27

没试过,但应该不难:

require 'em-websocket'
require 'sinatra/base'
require 'thin'

EM.run do
  class App < Sinatra::Base
    # Sinatra code here
  end

  EM::WebSocket.start(:host => '0.0.0.0', :port => 3001) do
    # Websocket code here
  end

  # You could also use Rainbows! instead of Thin.
  # Any EM based Rack handler should do.
  Thin::Server.start App, '0.0.0.0', 3000
end

此外,Cramp提供了一个Websocket实现,可直接与Thin/Rainbows一起使用!如果您愿意,可以提取出来,这样您甚至不需要在另一个端口上运行服务器。


这就是我完成它的方式。然而,我有一个相关的问题,即如何解码在Sinatra类中设置的Rack::Server::Cookie值,在客户端传递给ws.onopenhandshake中返回。请参阅我的更详细的问题:https://dev59.com/aXHYa4cB1Zd3GeqPNJnb - Dave Sag

20

谢谢 Konstantin... 它起作用了!我稍微调整了一下你的代码。在我更改的地方添加了注释。

- poul

require 'rubygems'      # <-- Added this require
require 'em-websocket'
require 'sinatra/base'
require 'thin'

EventMachine.run do     # <-- Changed EM to EventMachine
  class App < Sinatra::Base
      get '/' do
          return "foo"
      end
  end

  EventMachine::WebSocket.start(:host => '0.0.0.0', :port => 8080) do |ws| # <-- Added |ws|
      # Websocket code here
      ws.onopen {
          ws.send "connected!!!!"
      }

      ws.onmessage { |msg|
          puts "got message #{msg}"
      }

      ws.onclose   {
          ws.send "WebSocket closed"
      }

  end

  # You could also use Rainbows! instead of Thin.
  # Any EM based Rack handler should do.
  App.run!({:port => 3000})    # <-- Changed this line from Thin.start to App.run!
end

这个很好用。不过有一个问题,EventMachine套接字事件如何访问会话信息以验证事件是否来自已经通过身份验证的用户? - Dave Sag
我已根据我的评论在@Konstanti Haase的答案中概述了这个问题,并作为一个新问题 - 请参见https://dev59.com/aXHYa4cB1Zd3GeqPNJnb - Dave Sag

17

我偶然发现了这个websocket-rack GitHub项目,它基本上是一个Rack化的em-websocket,实际上我已经成功地将它与Sinatra应用程序并排运行。这是我的config.ru文件:

require 'rubygems'
require 'rack/websocket'
require 'sinatra/base'

class WebSocketApp < Rack::WebSocket::Application
  # ...
end

class SinatraApp < Sinatra::Base
  # ...
end

map '/ws' do
  run WebSocketApp.new
end

map '/' do
  run SinatraApp
end

玩的开心!
Colin


2
他字面上说:“这是我的config.ru文件:”。所以我认为“是”的答案 :) - Alex Martín Jiménez

12

我一直在使用sinatra-websocket,它允许您在与Sinatra相同的进程和端口上运行websocket服务器。

声明:我是维护者。

require 'sinatra'
require 'sinatra-websocket'

set :server, 'thin'
set :sockets, []

get '/' do
  if !request.websocket?
    erb :index
  else
    request.websocket do |ws|
      ws.onopen do
        ws.send("Hello World!")
        settings.sockets << ws
      end
      ws.onmessage do |msg|
        EM.next_tick { settings.sockets.each{|s| s.send(msg) } }
      end
      ws.onclose do
        warn("websocket closed")
        settings.sockets.delete(ws)
      end
    end
  end
end

__END__
@@ index
<html>
  <body>
     <h1>Simple Echo & Chat Server</h1>
     <form id="form">
       <input type="text" id="input" value="send a message"></input>
     </form>
     <div id="msgs"></div>
  </body>

  <script type="text/javascript">
    window.onload = function(){
      (function(){
        var show = function(el){
          return function(msg){ el.innerHTML = msg + '<br />' + el.innerHTML; }
        }(document.getElementById('msgs'));

        var ws       = new WebSocket('ws://' + window.location.host + window.location.pathname);
        ws.onopen    = function()  { show('websocket opened'); };
        ws.onclose   = function()  { show('websocket closed'); }
        ws.onmessage = function(m) { show('websocket message: ' +  m.data); };

        var sender = function(f){
          var input     = document.getElementById('input');
          input.onclick = function(){ input.value = "" };
          f.onsubmit    = function(){
            ws.send(input.value);
            input.value = "send a message";
            return false;
          }
        }(document.getElementById('form'));
      })();
    }
  </script>
</html>

作为使用Sinatra的websocket最快捷的方式(在我看来),我想问一下相比于使用'em-websocket',子类化Sinatra::Base并“手动”插入事件循环,这种方法带来了哪些劣势? - Redoman
还有,能否有人简要解释一下为什么在这里需要使用“next_tick”? - Redoman
@@simulacre -- 有什么想法可以让它在负载均衡设置上工作吗?我的意思是,从上面的例子中,每个Sinatra进程都将拥有自己的“settings.sockets”数组,使进程具有状态? - Sunder
绝对可爱 :) - Guillaume Roderick

8

请注意,您也可以使用Padrino应用程序与EventMachine一起使用(因为它们是Sinatra应用程序的子集):

require 'rubygems'
require 'eventmachine'
require 'padrino-core'
require 'thin'
require ::File.dirname(__FILE__) + '/config/boot.rb'

EM.run do
  Thin::Server.start Padrino.application, '0.0.0.0', 3000
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接