8784: Fix test for latest firefox.
[arvados.git] / services / api / app / middlewares / rack_socket.rb
1 require 'rack'
2 require 'faye/websocket'
3 require 'eventmachine'
4
5 # A Rack middleware to handle inbound websocket connection requests and hand
6 # them over to the faye websocket library.
7 class RackSocket
8
9   DEFAULT_ENDPOINT  = '/websocket'
10
11   # Stop EventMachine on signal, this should give it a chance to to unwind any
12   # open connections.
13   def die_gracefully_on_signal
14     Signal.trap("INT") { EM.stop }
15     Signal.trap("TERM") { EM.stop }
16   end
17
18   # Create a new RackSocket handler
19   # +app+  The next layer of the Rack stack.
20   #
21   # Accepts options:
22   # +:handler+ (Required) A class to handle new connections.  #initialize will
23   # call handler.new to create the actual handler instance object.  When a new
24   # websocket connection is established, #on_connect on the handler instance
25   # object will be called with the new connection.
26   #
27   # +:mount+ The HTTP request path that will be recognized for websocket
28   # connect requests, defaults to '/websocket'.
29   #
30   # +:websocket_only+  If true, the server will only handle websocket requests,
31   # and all other requests will result in an error.  If false, unhandled
32   # non-websocket requests will be passed along on to 'app' in the usual Rack
33   # way.
34   def initialize(app = nil, options = nil)
35     @app = app if app.respond_to?(:call)
36     @options = [app, options].grep(Hash).first || {}
37     @endpoint = @options[:mount] || DEFAULT_ENDPOINT
38     @websocket_only = @options[:websocket_only] || false
39
40     # from https://gist.github.com/eatenbyagrue/1338545#file-eventmachine-rb
41     if defined?(PhusionPassenger)
42       PhusionPassenger.on_event(:starting_worker_process) do |forked|
43         # for passenger, we need to avoid orphaned threads
44         if forked && EM.reactor_running?
45           EM.stop
46         end
47         Thread.new do
48           begin
49             EM.run
50           ensure
51             ActiveRecord::Base.connection.close
52           end
53         end
54         die_gracefully_on_signal
55       end
56     else
57       # faciliates debugging
58       Thread.abort_on_exception = true
59       # just spawn a thread and start it up
60       Thread.new do
61         begin
62           EM.run
63         ensure
64           ActiveRecord::Base.connection.close
65         end
66       end
67     end
68
69     # Create actual handler instance object from handler class.
70     @handler = @options[:handler].new
71   end
72
73   # Handle websocket connection request, or pass on to the next middleware
74   # supplied in +app+ initialize (unless +:websocket_only+ option is true, in
75   # which case return an error response.)
76   # +env+ the Rack environment with information about the request.
77   def call env
78     request = Rack::Request.new(env)
79     if request.path_info == @endpoint and Faye::WebSocket.websocket?(env)
80       if @handler.overloaded?
81         return [503, {"Content-Type" => "text/plain"}, ["Too many connections, try again later."]]
82       end
83
84       ws = Faye::WebSocket.new(env, nil, :ping => 30)
85
86       # Notify handler about new connection
87       @handler.on_connect ws
88
89       # Return async Rack response
90       ws.rack_response
91     elsif not @websocket_only
92       @app.call env
93     else
94       [406, {"Content-Type" => "text/plain"}, ["Only websocket connections are permitted on this port."]]
95     end
96   end
97
98 end