2017-08-31 91 views
9

Piszę serwer WWW Sinatra, że ​​chciałbym być REST, ale chodzi o to, że musi on wchodzić w interakcje z innym serwerem, który komunikuje się wyłącznie za pośrednictwem gniazd internetowych. Tak, to musi się zdarzyć:Sinatra używająca klienta websocket do odpowiedzi na żądanie http

  1. Żądanie przychodzi do mojego serwera Sinatra od klienta
  2. Serwer otwiera websocket na serwerze zagranicznym
  3. Serwer asynchronicznie czeka na wiadomości i rzeczy z zagranicznych serwer aż gniazdo jest zamknięte (to powinno brać tylko dwieście lub tak milisekund)
  4. Mój serwer wysyła odpowiedź do klienta

Jestem pewien, że nie jest to zbyt skomplikowane do wykonania, ale Trochę mi to utknęło. Zasadniczo, jeśli cała logika gniazda sieciowego może być opakowana w jedną funkcję, to można by zablokować tę funkcję, i to by było właśnie to. Ale nie wiem, jak owijać logikę gniazda sieciowego i blokować ją. Co myślisz? Uproszczona wersja tego, co mam, znajduje się poniżej.

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

Po głębszej refleksji, zdałem sobie sprawę, że sposób, że może to być zrobione za pomocą gwintu i halt wybudzenia gwintu. To czuje się dość skomplikowany i nie jestem pewien jak to zrobić poprawnie z Ruby, ale jest to pomysł:

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

jakie poczyniła dalsze postępy. Używam teraz klejnotu Async Sinatra, który wymaga serwera WWW Thin. Oto, jak jest skonfigurowany:

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 

Chodzi o to, że nadal nie działa. Wyjście konsoli jest zgodne z oczekiwaniami:

Request received 
Connected to web socket 
WS open 
Sent: A nice message to process 
Got message: blah blah blah 
WS closed 

Ale nie wysyła żadnych danych z powrotem do klienta. Wydaje się, że metoda body 'Closed ...' nie przynosi żadnego efektu.

+1

Sidenote: projekt sugerowany w pytaniu jest nieproduktywny, a wydajność ciężka. Bardziej sensowne jest utrzymywanie otwartego połączenia przez cały czas działania aplikacji. Taki jest cały cel websockets - utrzymywanie trwałego połączenia. – Myst

+0

To nie jest mój prawdziwy kod, ale tylko najprostszy sposób jego napisania, który mógłbym wymyślić, aby zademonstrować problem. Ale dziękuję, to dobra wskazówka. – tschwab

+0

Aby wyjaśnić, nie chcesz, aby strona ładowała się po uruchomieniu kodu gniazda sieciowego? Podajesz również, że powinien być asynchroniczny, więc jestem zdezorientowany. Możesz mieć funkcję asynchroniczną wewnątrz trasy, ponieważ trasa powróci bez żadnych informacji z metody asynchronicznej. – Cereal

Odpowiedz

0

Problem polegał na tym, że async-sinatra używało własnych wątków, podobnie jak websocket-client-simple. Rozwiązaniem jest użycie wiązań i funkcji eval, chociaż nie jest to w ogóle zbyt wydajne. Mam nadzieję, że dostępne są optymalizacje lub lepsze rozwiązania.

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