Sinatra使用websocket客户端响应HTTP请求

9
我正在编写一个Sinatra web服务器,希望它是RESTful的,但问题在于它必须与另一个通过web sockets通信的服务器进行交互。因此,需要进行以下操作:
  1. 从客户端发送请求到我的Sinatra服务器
  2. 我的服务器打开一个到另一个服务器的web socket
  3. 我的服务器异步等待来自另一个服务器的消息和数据,直到socket关闭(这应该只需要约200毫秒)
  4. 我的服务器将响应发送回客户端
我相信这并不太难实现,但我有些困惑。基本上,如果可以将整个web socket逻辑封装在一个函数中,那么该函数就可以被阻塞,然后就完成了。但我不知道如何封装并阻塞web socket逻辑。您认为呢?下面是我得到的简化版本。
require 'sinatra'
require 'websocket-client-simple'

get '/' do
     ws = WebSocket::Client::Simple.connect(' ws://URL... ')

     ws.on :message do
          puts 'bar'
     end

     ws.on :close do
          # At this point we need to send an HTTP response back to the client. But how?
     end

     ws.on :open do
          ws.send 'foo'
     end

end

编辑

经过进一步思考,我意识到可以使用线程暂停和线程唤醒的方式来实现。这种方法可能有些复杂,而且我不确定如何在 Ruby 中正确地实现它,但基本思路如下:

require 'sinatra'
require 'websocket-client-simple'

get '/' do
    socketResponse('wss:// ... URL ...')

    'Got a response from the web socket server!'
end

def socketResponse(url)
    thread = Thread.new do

        ws = WebSocket::Client::Simple.connect(url)

        ws.on :message do
            puts 'bar'
            # Maybe store each response in a thread-safe array to retrieve later or something
        end

        ws.on :close do
            thread.run
        end

        ws.on :open do
            ws.send 'foo'
        end

        Thread.stop
    end
end

编辑2

我已经取得进一步的进展。我现在正在使用Async Sinatra宝石,这需要Thin Web服务器。以下是设置方法:

require 'sinatra'
require 'sinatra/async'
require 'websocket-client-simple'

set :server, 'thin'

register Sinatra::Async

aget '/' do
    puts 'Request received'

    socketResponse('wss:// ... URL ...')
end

def socketResponse(url)
    ws = WebSocket::Client::Simple.connect(url)

    puts 'Connected to web socket'

    ws.on :message do |message|
        puts 'Got message:' + message.to_s
    end

    ws.on :close do
        puts 'WS closed'
        body 'Closed ...'
    end

    ws.on :open do
        puts 'WS open'

        message = 'A nice message to process'
        ws.send message
        puts 'Sent: ' + message
    end
end

问题在于,它仍然无法正常工作。控制台输出如预期所示:
Request received
Connected to web socket
WS open
Sent: A nice message to process
Got message: blah blah blah
WS closed

但是它没有向客户端发送任何数据。 body 'Closed ...' 方法似乎没有任何效果。


1
附注:你问题中提出的设计是低效且耗费性能的。保持websocket连接在应用程序运行期间始终打开才更有意义。这就是Websockets的全部意义 - 维持持久连接。 - Myst
这不是我的真实代码,只是我能想到的最简单的写法来演示问题。但谢谢你,这是一个很好的提示。 - tschwab
只是想澄清一下,您不希望页面在 Web Socket 代码运行之前加载?您还表示它应该是异步的,所以我有点困惑。您可以在路由内部使用异步函数,因为路由将在没有从异步方法获取任何信息的情况下返回。 - Cereal
页面的内容取决于Web套接字服务器发送的内容,因此在WS代码运行之前,页面无法加载。它需要异步只因为WS是异步的。它必须异步等待WS消息,但上面的函数socketResponse()应该阻塞直到WS关闭。这就是问题所在。 - tschwab
为什么不将 Web Socket 连接卸载到客户端,让客户端直接使用 Web Socket 连接请求和处理数据呢? - Myst
@Myst 我本来可以这样做,事实上,这正是我目前正在做的。但我不想那样做的原因基本上是模块化和解耦。如果客户端能够专门与我的服务器通信会更好。客户端代码目前相当混乱,我正在努力整理它。 - tschwab
1个回答

1
问题在于async-sinatra正在使用自己的线程,而websocket-client-simple也是如此。解决方案是使用绑定和eval函数,尽管这并不是很有效率。我希望有更好的优化或解决方案可用。
require 'sinatra'
require 'sinatra/async'
require 'websocket-client-simple'

set :server, 'thin'

register Sinatra::Async

aget '/' do
    puts 'Request received'

    socketResponse('wss:// ... URL ...', binding)
end

def socketResponse(url, b)
    ws = WebSocket::Client::Simple.connect(url)

    puts 'Connected to web socket'

    ws.on :message do |message|
        puts 'Got message:' + message.to_s
    end

    ws.on :close do
        puts 'WS closed'
        EM.schedule { b.eval " body 'Closed' " }
    end

    ws.on :open do
        puts 'WS open'

        message = 'A nice message to process'
        ws.send message
        puts 'Sent: ' + message
    end
end

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