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:
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.
Do websocket implementations use http protocol internally? Yes, initially, then they switch to the webSocket protocol. All webSocket connections start with an HTTP request with a header that requests an upgrade to the webSocket protocol.
WebSockets allow for a higher amount of efficiency compared to REST because they do not require the HTTP request/response overhead for each message sent and received. When a client wants ongoing updates about the state of the resource, WebSockets are generally a good fit.
Yes it can run on the same port, in fact it must run on the same port; the raison d'etre of websocket handshake is so that the websocket can run on the same connection without confusing intermediaries that doesn't understand websocket.
WebSockets are used to provide a connection between a client and a server so that both parties can send data at any time. The client uses a process known as the WebSocket handshake, which helps establish a connection between the server and the client.
The problem was that async-sinatra
was using its own threads, and so was websocket-client-simple
. The solution is to use bindings and the eval
function, though this is not very efficient at all. I am hoping that optimizations or better solutions are available.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With