I am writing a Sinatra web server that I would like to be RESTful, but the thing is that it has to interact with another server that communicates exclusively via web sockets. So, this needs to happen:
- A request comes into my Sinatra server from a client
- My server opens a web socket to the foreign server
- My server asynchronously waits for messages and things from the foreign server until the socket is closed (this should only take two hundred or so milliseconds)
- My server sends back a response to client
I'm sure this is not too complicated to accomplish, but I'm a bit stuck on it. Basically, if the entire web socket logic can be wrapped in a single function, then that function could be made to be blocking, and that'd be that. But I don't know how to wrap the web socket logic and block it. What do you think? A simplified version of what I've got is below.
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
EDIT
After further thought, I realized that a way that this might be done using a thread halt and a thread wake up. This feels rather elaborate, and I'm not sure how to do this correctly with Ruby, but this is the idea:
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
EDIT 2
I have made further progress. I am now using the Async Sinatra gem, which requires the Thin web server. This is how it is set up:
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
The thing is, it still isn't working. Its console output is as expected:
Request received
Connected to web socket
WS open
Sent: A nice message to process
Got message: blah blah blah
WS closed
But it is not sending any data back to the client. The body 'Closed ...'
method does not seem to have any effect.
The problem was that
async-sinatra
was using its own threads, and so waswebsocket-client-simple
. The solution is to use bindings and theeval
function, though this is not very efficient at all. I am hoping that optimizations or better solutions are available.