firehose 1.2.8 → 1.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -3,8 +3,8 @@
3
3
  .DS_Store
4
4
  *.swp
5
5
  .env
6
- Gemfile.lock
7
6
  log/*
8
7
  pkg/*
8
+ tmp
9
9
  *.sublime-project
10
10
  *.sublime-workspace
data/Gemfile.lock ADDED
@@ -0,0 +1,143 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ firehose (1.2.9)
5
+ em-hiredis (>= 0.2.0)
6
+ em-http-request (>= 1.0.0)
7
+ eventmachine (>= 1.0.0)
8
+ faraday
9
+ faye-websocket
10
+ json
11
+ thor
12
+
13
+ GEM
14
+ remote: http://rubygems.org/
15
+ specs:
16
+ addressable (2.3.5)
17
+ async_rack_test (0.0.4)
18
+ coderay (1.0.9)
19
+ coffee-script (2.2.0)
20
+ coffee-script-source
21
+ execjs
22
+ coffee-script-source (1.6.3)
23
+ cookiejar (0.3.0)
24
+ crack (0.4.1)
25
+ safe_yaml (~> 0.9.0)
26
+ daemons (1.1.9)
27
+ diff-lcs (1.2.4)
28
+ dotenv (0.9.0)
29
+ em-hiredis (0.2.1)
30
+ hiredis (~> 0.4.0)
31
+ em-http-request (1.1.1)
32
+ addressable (>= 2.3.4)
33
+ cookiejar
34
+ em-socksify (>= 0.3)
35
+ eventmachine (>= 1.0.3)
36
+ http_parser.rb (>= 0.6.0.beta.2)
37
+ em-socksify (0.3.0)
38
+ eventmachine (>= 1.0.0.beta.4)
39
+ eventmachine (1.0.3)
40
+ execjs (2.0.2)
41
+ faraday (0.8.8)
42
+ multipart-post (~> 1.2.0)
43
+ faye-websocket (0.7.0)
44
+ eventmachine (>= 0.12.0)
45
+ websocket-driver (>= 0.3.0)
46
+ ffi (1.9.0)
47
+ foreman (0.63.0)
48
+ dotenv (>= 0.7)
49
+ thor (>= 0.13.6)
50
+ formatador (0.2.4)
51
+ guard (1.8.3)
52
+ formatador (>= 0.2.4)
53
+ listen (~> 1.3)
54
+ lumberjack (>= 1.0.2)
55
+ pry (>= 0.9.10)
56
+ thor (>= 0.14.6)
57
+ guard-bundler (1.0.0)
58
+ bundler (~> 1.0)
59
+ guard (~> 1.1)
60
+ guard-coffeescript (1.3.4)
61
+ coffee-script (>= 2.2.0)
62
+ guard (>= 1.1.0)
63
+ guard-rspec (3.1.0)
64
+ guard (>= 1.8)
65
+ rspec (~> 2.13)
66
+ hike (1.2.3)
67
+ hiredis (0.4.5)
68
+ http_parser.rb (0.6.0.beta.2)
69
+ json (1.8.1)
70
+ kgio (2.8.1)
71
+ listen (1.3.1)
72
+ rb-fsevent (>= 0.9.3)
73
+ rb-inotify (>= 0.9)
74
+ rb-kqueue (>= 0.2)
75
+ lumberjack (1.0.4)
76
+ method_source (0.8.2)
77
+ multi_json (1.8.2)
78
+ multipart-post (1.2.0)
79
+ pry (0.9.12.2)
80
+ coderay (~> 1.0.5)
81
+ method_source (~> 0.8)
82
+ slop (~> 3.4)
83
+ rack (1.5.2)
84
+ rack-test (0.6.2)
85
+ rack (>= 1.0)
86
+ rainbows (4.4.3)
87
+ kgio (~> 2.5)
88
+ rack (~> 1.1)
89
+ unicorn (~> 4.1)
90
+ raindrops (0.12.0)
91
+ rake (10.1.0)
92
+ rb-fsevent (0.9.3)
93
+ rb-inotify (0.9.2)
94
+ ffi (>= 0.5.0)
95
+ rb-kqueue (0.2.0)
96
+ ffi (>= 0.5.0)
97
+ rspec (2.14.1)
98
+ rspec-core (~> 2.14.0)
99
+ rspec-expectations (~> 2.14.0)
100
+ rspec-mocks (~> 2.14.0)
101
+ rspec-core (2.14.6)
102
+ rspec-expectations (2.14.3)
103
+ diff-lcs (>= 1.1.3, < 2.0)
104
+ rspec-mocks (2.14.4)
105
+ safe_yaml (0.9.7)
106
+ slop (3.4.6)
107
+ sprockets (2.10.0)
108
+ hike (~> 1.2)
109
+ multi_json (~> 1.0)
110
+ rack (~> 1.0)
111
+ tilt (~> 1.1, != 1.3.0)
112
+ thin (1.6.0)
113
+ daemons (>= 1.0.9)
114
+ eventmachine (>= 1.0.0)
115
+ rack (>= 1.5.0)
116
+ thor (0.18.1)
117
+ tilt (1.4.1)
118
+ unicorn (4.6.3)
119
+ kgio (~> 2.6)
120
+ rack
121
+ raindrops (~> 0.7)
122
+ webmock (1.15.0)
123
+ addressable (>= 2.2.7)
124
+ crack (>= 0.3.2)
125
+ websocket-driver (0.3.0)
126
+
127
+ PLATFORMS
128
+ ruby
129
+
130
+ DEPENDENCIES
131
+ async_rack_test
132
+ firehose!
133
+ foreman
134
+ guard-bundler
135
+ guard-coffeescript
136
+ guard-rspec
137
+ rack-test
138
+ rainbows (~> 4.4.3)
139
+ rake
140
+ rspec
141
+ sprockets
142
+ thin
143
+ webmock
data/README.md CHANGED
@@ -118,6 +118,40 @@ firehose.publish(json).to("/my/messages/path")
118
118
 
119
119
  Firehose can be configured via environmental variables. Take a look at the [`.env.sample`](./.env.sample) file for more info.
120
120
 
121
+ ## Rack Configuration
122
+
123
+ There are two rack applications that are included with Firehose: `Firehose::Rack::Producer` which a client can `PUT` HTTP request with message payloads to publish information on Firehose and the `Firehose::Rack::Consumer` application which a client connects to via HTT long polling or WebSockets to consume a message.
124
+
125
+ ### Consumer Configuration
126
+
127
+ ```ruby
128
+ # Kitchen-sink rack configuration file example
129
+ require 'firehose'
130
+
131
+ consumer = Firehose::Rack::Consumer.new do |app|
132
+ # Configure how long the server should wait before send the client a 204
133
+ # with a request to reconnect. Typically browsers time-out the client connection
134
+ # after 30 seconds, so we set the `Firehose.Consumer` JS client to 25, and the
135
+ # server to 20 to make sure latency or timing doesn't cause any problems.
136
+ app.http_long_poll.timeout = 20
137
+ end
138
+
139
+ run consumer
140
+ ```
141
+
142
+ ### Publisher Configuration
143
+
144
+ ```ruby
145
+ # Kitchen-sink rack configuration file example
146
+ require 'firehose'
147
+
148
+ # There's nothing to configure with the Publisher, but its possible that
149
+ # you might include rack middleware authorization mechanisms here to control
150
+ # who can publish to Firehose.
151
+
152
+ run Firehose::Rack::Publisher.new
153
+ ```
154
+
121
155
  ## Sprockets
122
156
 
123
157
  Using Sprockets is the recommended method of including the included client-side assets in a web page.
data/config/rainbows.rb CHANGED
@@ -14,8 +14,4 @@ end
14
14
  # the rest of the Unicorn configuration...
15
15
  worker_processes [ENV['WORKER_PROCESSES'].to_i, 1].max # Default to 1
16
16
  working_directory ENV['WORKING_DIRECTORY'] if ENV['WORKING_DIRECTORY']
17
- logger Firehose.logger
18
-
19
- after_fork do |server, worker|
20
- require 'firehose/patches/rainbows'
21
- end if ENV['RACK_ENV'] == 'development'
17
+ logger Firehose.logger
@@ -16,10 +16,10 @@ class Firehose.LongPoll extends Firehose.Transport
16
16
  constructor: (args) ->
17
17
  super args
18
18
 
19
- @config.ssl ?= false
20
- # Configrations specifically for web sockets
19
+ @config.ssl ?= false
20
+
21
+ # Configrations specifically for long polling
21
22
  @config.longPoll ||= {}
22
- # Protocol schema we should use for talking to WS server.
23
23
  @config.longPoll.url ||= "#{@_protocol()}:#{@config.uri}"
24
24
  # How many ms should we wait before timing out the AJAX connection?
25
25
  @config.longPoll.timeout ||= 25000
@@ -30,8 +30,9 @@ class Firehose.LongPoll extends Firehose.Transport
30
30
  @_okInterval = 0
31
31
  @_stopRequestLoop = false
32
32
 
33
+ # Protocol schema we should use for talking to firehose server.
33
34
  _protocol: =>
34
- if @config.ssl then 'https' else 'http'
35
+ if @config.ssl then "https" else "http"
35
36
 
36
37
  _request: =>
37
38
  return if @_stopRequestLoop
@@ -18,7 +18,7 @@ class Firehose.WebSocket extends Firehose.Transport
18
18
  # Run this is a try/catch block because IE10 inside of a .NET control
19
19
  # complains about security zones.
20
20
  try
21
- @socket = new window.WebSocket "ws:#{@config.uri}?#{$.param @config.params}"
21
+ @socket = new window.WebSocket "#{@_protocol()}:#{@config.uri}?#{$.param @config.params}"
22
22
  @socket.onopen = @_open
23
23
  @socket.onclose = @_close
24
24
  @socket.onerror = @_error
@@ -26,6 +26,10 @@ class Firehose.WebSocket extends Firehose.Transport
26
26
  catch err
27
27
  console?.log(err)
28
28
 
29
+ # Protocol schema we should use for talking to firehose server.
30
+ _protocol: =>
31
+ if @config.ssl then "wss" else "ws"
32
+
29
33
  _open: =>
30
34
  sendPing @socket
31
35
 
data/lib/firehose.rb CHANGED
@@ -13,7 +13,6 @@ module Firehose
13
13
  autoload :Assets, 'firehose/assets'
14
14
  autoload :Rack, 'firehose/rack'
15
15
  autoload :CLI, 'firehose/cli'
16
- autoload :SwfPolicyRequest, 'firehose/swf_policy_request'
17
16
 
18
17
  # Default URI for the Firehose server. Consider the port "well-known" and bindable from other apps.
19
18
  URI = URI.parse("//0.0.0.0:7474").freeze
@@ -4,6 +4,10 @@ module Firehose
4
4
  # which talks directly to the Redis server. Also dispatches between HTTP and WebSocket
5
5
  # transport handlers depending on the clients' request.
6
6
  class App
7
+ def initialize
8
+ yield self if block_given?
9
+ end
10
+
7
11
  def call(env)
8
12
  # Cache the parsed request so we don't need to re-parse it when we pass
9
13
  # control onto another app.
@@ -25,15 +29,18 @@ module Firehose
25
29
  end
26
30
  end
27
31
 
32
+ # The consumer pulls messages off of the backend and passes messages to the
33
+ # connected HTTP or WebSocket client. This can be configured from the initialization
34
+ # method of the rack app.
35
+ def consumer
36
+ @consumer ||= Consumer.new
37
+ end
38
+
28
39
  private
29
40
  def publisher
30
41
  @publisher ||= Publisher.new
31
42
  end
32
43
 
33
- def consumer
34
- @consumer ||= Consumer.new
35
- end
36
-
37
44
  def ping
38
45
  @ping ||= Ping.new
39
46
  end
@@ -1,4 +1,3 @@
1
- require 'faye/websocket'
2
1
  require 'json'
3
2
 
4
3
  module Firehose
@@ -7,157 +6,34 @@ module Firehose
7
6
  # binds that to the Firehose::Server::Subscription class, which is bound to a channel that
8
7
  # gets published to.
9
8
  class Consumer
9
+ # Rack consumer transports
10
+ autoload :HttpLongPoll, 'firehose/rack/consumer/http_long_poll'
11
+ autoload :WebSocket, 'firehose/rack/consumer/web_socket'
12
+
13
+ # Let the client configure the consumer on initialization.
14
+ def initialize
15
+ yield self if block_given?
16
+ end
17
+
10
18
  def call(env)
11
19
  websocket_request?(env) ? websocket.call(env) : http_long_poll.call(env)
12
20
  end
13
21
 
14
- private
22
+ # Memoized instance of web socket that can be configured from the rack app.
15
23
  def websocket
16
- WebSocket.new
24
+ @web_socket ||= WebSocket.new
17
25
  end
18
26
 
27
+ # Memoized instance of http long poll handler that can be configured from the rack app.
19
28
  def http_long_poll
20
29
  @http_long_poll ||= HttpLongPoll.new
21
30
  end
22
31
 
32
+ private
33
+ # Determine if the incoming request is a websocket request.
23
34
  def websocket_request?(env)
24
- Faye::WebSocket.websocket?(env)
25
- end
26
-
27
- class HttpLongPoll
28
- include Firehose::Rack::Helpers
29
-
30
- # How long should we wait before closing out the consuming clients web connection
31
- # for long polling? Most browsers timeout after a connection has been idle for 30s.
32
- TIMEOUT = 20
33
-
34
- def call(env)
35
- req = env['parsed_request'] ||= ::Rack::Request.new(env)
36
- path = req.path
37
- method = req.request_method
38
- # Get the Last Message Sequence from the query string.
39
- # Ideally we'd use an HTTP header, but android devices don't let us
40
- # set any HTTP headers for CORS requests.
41
- last_sequence = req.params['last_message_sequence'].to_i
42
-
43
- case method
44
- # GET is how clients subscribe to the queue. When a messages comes in, we flush out a response,
45
- # close down the requeust, and the client then reconnects.
46
- when 'GET'
47
- Firehose.logger.debug "HTTP GET with last_sequence #{last_sequence} for path #{path} with query #{env["QUERY_STRING"].inspect} and params #{req.params.inspect}"
48
- EM.next_tick do
49
-
50
- if last_sequence < 0
51
- env['async.callback'].call response(400, "The last_message_sequence parameter may not be less than zero", response_headers(env))
52
- else
53
- Server::Channel.new(path).next_message(last_sequence, :timeout => TIMEOUT).callback do |message, sequence|
54
- env['async.callback'].call response(200, wrap_frame(message, sequence), response_headers(env))
55
- end.errback do |e|
56
- if e == :timeout
57
- env['async.callback'].call response(204, '', response_headers(env))
58
- else
59
- Firehose.logger.error "Unexpected error when trying to GET last_sequence #{last_sequence} for path #{path}: #{e.inspect}"
60
- env['async.callback'].call response(500, 'Unexpected error', response_headers(env))
61
- end
62
- end
63
- end
64
-
65
- end
66
-
67
- # Tell the web server that this will be an async response.
68
- ASYNC_RESPONSE
69
-
70
- else
71
- Firehose.logger.debug "HTTP #{method} not supported"
72
- response(501, "#{method} not supported.")
73
- end
74
- end
75
-
76
-
77
- private
78
-
79
- def wrap_frame(message, last_sequence)
80
- JSON.generate :message => message, :last_sequence => last_sequence
81
- end
82
-
83
- # If the request is a CORS request, return those headers, otherwise don't worry 'bout it
84
- def response_headers(env)
85
- cors_origin(env) ? cors_headers(env) : {}
86
- end
87
-
88
- def cors_origin(env)
89
- env['HTTP_ORIGIN']
90
- end
91
-
92
- def cors_headers(env)
93
- # TODO seperate out CORS logic as an async middleware with a Goliath web server.
94
- {'Access-Control-Allow-Origin' => cors_origin(env)}
95
- end
35
+ Firehose::Rack::Consumer::WebSocket.request?(env)
96
36
  end
97
-
98
-
99
- # It _may_ be more memory efficient if we used the same instance of
100
- # this class (or if we even just used a lambda) for every connection.
101
- class WebSocket
102
- def call(env)
103
- req = ::Rack::Request.new env
104
- @ws = Faye::WebSocket.new env
105
- @path = req.path
106
- @ws.onopen = method :handle_open
107
- @ws.onclose = method :handle_close
108
- @ws.onerror = method :handle_error
109
- @ws.onmessage = method :handle_message
110
- return @ws.rack_response
111
- end
112
-
113
- private
114
- def subscribe(last_sequence)
115
- @subscribed = true
116
- @channel = Server::Channel.new @path
117
- @deferrable = @channel.next_message last_sequence
118
- @deferrable.callback do |message, sequence|
119
- Firehose.logger.debug "WS sent `#{message}` to `#{@path}` with sequence `#{sequence}`"
120
- @ws.send self.class.wrap_frame(message, last_sequence)
121
- subscribe sequence
122
- end
123
- @deferrable.errback do |e|
124
- EM.next_tick { raise e.inspect } unless e == :disconnect
125
- end
126
- end
127
-
128
- def handle_message(event)
129
- msg = JSON.parse(event.data, :symbolize_names => true) rescue {}
130
- seq = msg[:message_sequence]
131
- if msg[:ping] == 'PING'
132
- Firehose.logger.debug "WS ping received, sending pong"
133
- @ws.send JSON.generate :pong => 'PONG'
134
- elsif !@subscribed && seq.kind_of?(Integer)
135
- Firehose.logger.debug "Subscribing at message_sequence #{seq}"
136
- subscribe seq
137
- end
138
- end
139
-
140
- def handle_open(event)
141
- Firehose.logger.debug "WebSocket subscribed to `#{@path}`. Waiting for message_sequence..."
142
- end
143
-
144
- def handle_close(event)
145
- if @deferrable
146
- @deferrable.fail :disconnect
147
- @channel.unsubscribe(@deferrable) if @channel
148
- end
149
- Firehose.logger.debug "WS connection `#{@path}` closing. Code: #{event.code.inspect}; Reason #{event.reason.inspect}"
150
- end
151
-
152
- def handle_error(event)
153
- Firehose.logger.error "WS connection `#{@path}` error. Message: `#{event.message.inspect}`; Data: `#{event.data.inspect}`"
154
- end
155
-
156
- def self.wrap_frame(message, last_sequence)
157
- JSON.generate :message => message, :last_sequence => last_sequence
158
- end
159
- end
160
-
161
37
  end
162
38
  end
163
39
  end
@@ -0,0 +1,86 @@
1
+ require 'json'
2
+
3
+ module Firehose
4
+ module Rack
5
+ class Consumer
6
+ class HttpLongPoll
7
+ include Firehose::Rack::Helpers
8
+
9
+ # How long should we wait before closing out the consuming clients web connection
10
+ # for long polling? Most browsers timeout after a connection has been idle for 30s.
11
+ TIMEOUT = 20
12
+
13
+ # Configures the timeout for the
14
+ attr_accessor :timeout
15
+
16
+ def initialize(timeout=TIMEOUT)
17
+ @timeout = timeout
18
+ yield self if block_given?
19
+ end
20
+
21
+ def call(env)
22
+ req = env['parsed_request'] ||= ::Rack::Request.new(env)
23
+ path = req.path
24
+ method = req.request_method
25
+ # Get the Last Message Sequence from the query string.
26
+ # Ideally we'd use an HTTP header, but android devices don't let us
27
+ # set any HTTP headers for CORS requests.
28
+ last_sequence = req.params['last_message_sequence'].to_i
29
+
30
+ case method
31
+ # GET is how clients subscribe to the queue. When a messages comes in, we flush out a response,
32
+ # close down the requeust, and the client then reconnects.
33
+ when 'GET'
34
+ Firehose.logger.debug "HTTP GET with last_sequence #{last_sequence} for path #{path} with query #{env["QUERY_STRING"].inspect} and params #{req.params.inspect}"
35
+ EM.next_tick do
36
+
37
+ if last_sequence < 0
38
+ env['async.callback'].call response(400, "The last_message_sequence parameter may not be less than zero", response_headers(env))
39
+ else
40
+ Server::Channel.new(path).next_message(last_sequence, :timeout => timeout).callback do |message, sequence|
41
+ env['async.callback'].call response(200, wrap_frame(message, sequence), response_headers(env))
42
+ end.errback do |e|
43
+ if e == :timeout
44
+ env['async.callback'].call response(204, '', response_headers(env))
45
+ else
46
+ Firehose.logger.error "Unexpected error when trying to GET last_sequence #{last_sequence} for path #{path}: #{e.inspect}"
47
+ env['async.callback'].call response(500, 'Unexpected error', response_headers(env))
48
+ end
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ # Tell the web server that this will be an async response.
55
+ ASYNC_RESPONSE
56
+
57
+ else
58
+ Firehose.logger.debug "HTTP #{method} not supported"
59
+ response(501, "#{method} not supported.")
60
+ end
61
+ end
62
+
63
+
64
+ private
65
+
66
+ def wrap_frame(message, last_sequence)
67
+ JSON.generate :message => message, :last_sequence => last_sequence
68
+ end
69
+
70
+ # If the request is a CORS request, return those headers, otherwise don't worry 'bout it
71
+ def response_headers(env)
72
+ cors_origin(env) ? cors_headers(env) : {}
73
+ end
74
+
75
+ def cors_origin(env)
76
+ env['HTTP_ORIGIN']
77
+ end
78
+
79
+ def cors_headers(env)
80
+ # TODO seperate out CORS logic as an async middleware with a Goliath web server.
81
+ {'Access-Control-Allow-Origin' => cors_origin(env)}
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,95 @@
1
+ require 'faye/websocket'
2
+ require 'json'
3
+
4
+ module Firehose
5
+ module Rack
6
+ class Consumer
7
+ class WebSocket
8
+ # Setup a handler for the websocket connection.
9
+ def call(env)
10
+ ws = Faye::WebSocket.new(env)
11
+ Handler.new(ws)
12
+ ws.rack_response
13
+ end
14
+
15
+ # Determine if the rack request is a WebSocket request.
16
+ def self.request?(env)
17
+ Faye::WebSocket.websocket?(env)
18
+ end
19
+
20
+ # Manages connection state for the web socket that's connected
21
+ # by the Consumer::WebSocket class. Deals with message sequence,
22
+ # connection, failures, and subscription state.
23
+ class Handler
24
+ def initialize(ws)
25
+ @ws = ws
26
+ @req = ::Rack::Request.new ws.env
27
+ # Setup the event handlers from this class.
28
+ @ws.onopen = method :open
29
+ @ws.onclose = method :close
30
+ @ws.onerror = method :error
31
+ @ws.onmessage = method :message
32
+ end
33
+
34
+ # Subscribe the client to the channel on the server. Asks for
35
+ # the last sequence for clients that reconnect.
36
+ def subscribe(last_sequence)
37
+ @subscribed = true
38
+ @channel = Server::Channel.new @req.path
39
+ @deferrable = @channel.next_message last_sequence
40
+ @deferrable.callback do |message, sequence|
41
+ Firehose.logger.debug "WS sent `#{message}` to `#{@req.path}` with sequence `#{sequence}`"
42
+ @ws.send self.class.wrap_frame(message, last_sequence)
43
+ subscribe sequence
44
+ end
45
+ @deferrable.errback do |e|
46
+ EM.next_tick { raise e.inspect } unless e == :disconnect
47
+ end
48
+ end
49
+
50
+ # Manages messages sent from the connect client to the server. This is mostly
51
+ # used to handle heart-beats that are designed to prevent the WebSocket connection
52
+ # from timing out from inactivity.
53
+ def message(event)
54
+ msg = JSON.parse(event.data, :symbolize_names => true) rescue {}
55
+ seq = msg[:message_sequence]
56
+ if msg[:ping] == 'PING'
57
+ Firehose.logger.debug "WS ping received, sending pong"
58
+ @ws.send JSON.generate :pong => 'PONG'
59
+ elsif !@subscribed && seq.kind_of?(Integer)
60
+ Firehose.logger.debug "Subscribing at message_sequence #{seq}"
61
+ subscribe seq
62
+ end
63
+ end
64
+
65
+ # Log a message that the client has connected.
66
+ def open(event)
67
+ Firehose.logger.debug "WebSocket subscribed to `#{@req.path}`. Waiting for message_sequence..."
68
+ end
69
+
70
+ # Log a message that hte client has disconnected and reset the state for the class. Clean
71
+ # up the subscribers to the channels.
72
+ def close(event)
73
+ if @deferrable
74
+ @deferrable.fail :disconnect
75
+ @channel.unsubscribe(@deferrable) if @channel
76
+ end
77
+ Firehose.logger.debug "WS connection `#{@req.path}` closing. Code: #{event.code.inspect}; Reason #{event.reason.inspect}"
78
+ end
79
+
80
+ # Log errors if a socket fails. `close` will fire after this to clean up any
81
+ # remaining connectons.
82
+ def error(event)
83
+ Firehose.logger.error "WS connection `#{@req.path}` error. Message: `#{event.message.inspect}`; Data: `#{event.data.inspect}`"
84
+ end
85
+
86
+ # Wrap a message in a sequence so that the client can record this and give us
87
+ # the sequence when it reconnects.
88
+ def self.wrap_frame(message, last_sequence)
89
+ JSON.generate :message => message, :last_sequence => last_sequence
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -36,7 +36,6 @@ module Firehose
36
36
  # Boot the Firehose server with the Thin app server.
37
37
  def start_thin
38
38
  require 'thin'
39
- require 'firehose/patches/thin' if ENV['RACK_ENV'] == 'development'
40
39
 
41
40
  Faye::WebSocket.load_adapter('thin')
42
41
 
@@ -31,7 +31,7 @@ module Firehose
31
31
  redis.lrange(list_key, 0, Server::Publisher::MAX_MESSAGES).
32
32
  errback {|e| deferrable.fail e }
33
33
  redis.exec.callback do |(sequence, message_list)|
34
- Firehose.logger.debug "exec returened: `#{sequence}` and `#{message_list.inspect}`"
34
+ Firehose.logger.debug "exec returned: `#{sequence}` and `#{message_list.inspect}`"
35
35
  sequence = sequence.to_i
36
36
 
37
37
  if sequence.nil? || (diff = sequence - last_sequence) <= 0
@@ -77,4 +77,4 @@ module Firehose
77
77
  end
78
78
  end
79
79
  end
80
- end
80
+ end
@@ -1,4 +1,4 @@
1
1
  module Firehose
2
- VERSION = "1.2.8"
3
- CODENAME = "Straight Stream"
2
+ VERSION = "1.2.9"
3
+ CODENAME = "Configurable Carl"
4
4
  end
@@ -1,9 +1,13 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Firehose::Client::Consumer::WebSocket do
4
- it "should be tested" # probably inside of the integrations
4
+ context "transport" do
5
+ # Transport for Firehose::Client::App class is tested via the spec/integrations suite.
6
+ end
5
7
  end
6
8
 
7
9
  describe Firehose::Client::Consumer::HttpLongPoll do
8
- it "should be tested" # probably inside of the integrations
10
+ context "transport" do
11
+ # Transport for Firehose::Client::App class is tested via the spec/integrations suite.
12
+ end
9
13
  end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Firehose::Rack::Consumer::HttpLongPoll do
4
+ context "transport" do
5
+ # Transport for Firehose::Rack::App class is tested via the spec/integrations suite.
6
+ end
7
+ context "configuration" do
8
+ it "should have #timeout" do
9
+ Firehose::Rack::Consumer::HttpLongPoll.new(200).timeout.should == 200
10
+ end
11
+ end
12
+ end
@@ -8,4 +8,12 @@ describe Firehose::Rack::Consumer, :type => :request do
8
8
 
9
9
  it "should have Content-Length on OPTIONS request"
10
10
  it "should have Content-Length on GET request"
11
+
12
+ context "configuration" do
13
+ let(:app) { Firehose::Rack::Consumer }
14
+
15
+ it "should configure long polling timeout" do
16
+ app.new{ |a| a.http_long_poll.timeout = 300 }.http_long_poll.timeout.should == 300
17
+ end
18
+ end
11
19
  end
@@ -1 +1,15 @@
1
- # The Firehose::Server::App class is tested via the spec/integrations suite.
1
+ require 'spec_helper'
2
+
3
+ describe Firehose::Rack::App do
4
+ context "transport" do
5
+ # Transport for Firehose::Rack::App class is tested via the spec/integrations suite.
6
+ end
7
+
8
+ context "configuration" do
9
+ let(:app) { Firehose::Rack::App }
10
+
11
+ it "should configure long polling timeout" do
12
+ app.new{ |a| a.consumer.http_long_poll.timeout = 300 }.consumer.http_long_poll.timeout.should == 300
13
+ end
14
+ end
15
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: firehose
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.8
4
+ version: 1.2.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -12,11 +12,12 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2013-06-13 00:00:00.000000000 Z
15
+ date: 2013-10-29 00:00:00.000000000 -07:00
16
+ default_executable:
16
17
  dependencies:
17
18
  - !ruby/object:Gem::Dependency
18
19
  name: eventmachine
19
- requirement: !ruby/object:Gem::Requirement
20
+ requirement: &70105915795260 !ruby/object:Gem::Requirement
20
21
  none: false
21
22
  requirements:
22
23
  - - ! '>='
@@ -24,15 +25,10 @@ dependencies:
24
25
  version: 1.0.0
25
26
  type: :runtime
26
27
  prerelease: false
27
- version_requirements: !ruby/object:Gem::Requirement
28
- none: false
29
- requirements:
30
- - - ! '>='
31
- - !ruby/object:Gem::Version
32
- version: 1.0.0
28
+ version_requirements: *70105915795260
33
29
  - !ruby/object:Gem::Dependency
34
30
  name: em-hiredis
35
- requirement: !ruby/object:Gem::Requirement
31
+ requirement: &70105915830300 !ruby/object:Gem::Requirement
36
32
  none: false
37
33
  requirements:
38
34
  - - ! '>='
@@ -40,15 +36,10 @@ dependencies:
40
36
  version: 0.2.0
41
37
  type: :runtime
42
38
  prerelease: false
43
- version_requirements: !ruby/object:Gem::Requirement
44
- none: false
45
- requirements:
46
- - - ! '>='
47
- - !ruby/object:Gem::Version
48
- version: 0.2.0
39
+ version_requirements: *70105915830300
49
40
  - !ruby/object:Gem::Dependency
50
41
  name: thor
51
- requirement: !ruby/object:Gem::Requirement
42
+ requirement: &70105915829500 !ruby/object:Gem::Requirement
52
43
  none: false
53
44
  requirements:
54
45
  - - ! '>='
@@ -56,15 +47,10 @@ dependencies:
56
47
  version: '0'
57
48
  type: :runtime
58
49
  prerelease: false
59
- version_requirements: !ruby/object:Gem::Requirement
60
- none: false
61
- requirements:
62
- - - ! '>='
63
- - !ruby/object:Gem::Version
64
- version: '0'
50
+ version_requirements: *70105915829500
65
51
  - !ruby/object:Gem::Dependency
66
52
  name: faraday
67
- requirement: !ruby/object:Gem::Requirement
53
+ requirement: &70105915828480 !ruby/object:Gem::Requirement
68
54
  none: false
69
55
  requirements:
70
56
  - - ! '>='
@@ -72,15 +58,10 @@ dependencies:
72
58
  version: '0'
73
59
  type: :runtime
74
60
  prerelease: false
75
- version_requirements: !ruby/object:Gem::Requirement
76
- none: false
77
- requirements:
78
- - - ! '>='
79
- - !ruby/object:Gem::Version
80
- version: '0'
61
+ version_requirements: *70105915828480
81
62
  - !ruby/object:Gem::Dependency
82
63
  name: faye-websocket
83
- requirement: !ruby/object:Gem::Requirement
64
+ requirement: &70105915827560 !ruby/object:Gem::Requirement
84
65
  none: false
85
66
  requirements:
86
67
  - - ! '>='
@@ -88,15 +69,10 @@ dependencies:
88
69
  version: '0'
89
70
  type: :runtime
90
71
  prerelease: false
91
- version_requirements: !ruby/object:Gem::Requirement
92
- none: false
93
- requirements:
94
- - - ! '>='
95
- - !ruby/object:Gem::Version
96
- version: '0'
72
+ version_requirements: *70105915827560
97
73
  - !ruby/object:Gem::Dependency
98
74
  name: em-http-request
99
- requirement: !ruby/object:Gem::Requirement
75
+ requirement: &70105915826680 !ruby/object:Gem::Requirement
100
76
  none: false
101
77
  requirements:
102
78
  - - ! '>='
@@ -104,15 +80,10 @@ dependencies:
104
80
  version: 1.0.0
105
81
  type: :runtime
106
82
  prerelease: false
107
- version_requirements: !ruby/object:Gem::Requirement
108
- none: false
109
- requirements:
110
- - - ! '>='
111
- - !ruby/object:Gem::Version
112
- version: 1.0.0
83
+ version_requirements: *70105915826680
113
84
  - !ruby/object:Gem::Dependency
114
85
  name: json
115
- requirement: !ruby/object:Gem::Requirement
86
+ requirement: &70105915826240 !ruby/object:Gem::Requirement
116
87
  none: false
117
88
  requirements:
118
89
  - - ! '>='
@@ -120,15 +91,10 @@ dependencies:
120
91
  version: '0'
121
92
  type: :runtime
122
93
  prerelease: false
123
- version_requirements: !ruby/object:Gem::Requirement
124
- none: false
125
- requirements:
126
- - - ! '>='
127
- - !ruby/object:Gem::Version
128
- version: '0'
94
+ version_requirements: *70105915826240
129
95
  - !ruby/object:Gem::Dependency
130
96
  name: rspec
131
- requirement: !ruby/object:Gem::Requirement
97
+ requirement: &70105915825720 !ruby/object:Gem::Requirement
132
98
  none: false
133
99
  requirements:
134
100
  - - ! '>='
@@ -136,15 +102,10 @@ dependencies:
136
102
  version: '0'
137
103
  type: :development
138
104
  prerelease: false
139
- version_requirements: !ruby/object:Gem::Requirement
140
- none: false
141
- requirements:
142
- - - ! '>='
143
- - !ruby/object:Gem::Version
144
- version: '0'
105
+ version_requirements: *70105915825720
145
106
  - !ruby/object:Gem::Dependency
146
107
  name: webmock
147
- requirement: !ruby/object:Gem::Requirement
108
+ requirement: &70105915824960 !ruby/object:Gem::Requirement
148
109
  none: false
149
110
  requirements:
150
111
  - - ! '>='
@@ -152,15 +113,10 @@ dependencies:
152
113
  version: '0'
153
114
  type: :development
154
115
  prerelease: false
155
- version_requirements: !ruby/object:Gem::Requirement
156
- none: false
157
- requirements:
158
- - - ! '>='
159
- - !ruby/object:Gem::Version
160
- version: '0'
116
+ version_requirements: *70105915824960
161
117
  - !ruby/object:Gem::Dependency
162
118
  name: guard-rspec
163
- requirement: !ruby/object:Gem::Requirement
119
+ requirement: &70105915839000 !ruby/object:Gem::Requirement
164
120
  none: false
165
121
  requirements:
166
122
  - - ! '>='
@@ -168,15 +124,10 @@ dependencies:
168
124
  version: '0'
169
125
  type: :development
170
126
  prerelease: false
171
- version_requirements: !ruby/object:Gem::Requirement
172
- none: false
173
- requirements:
174
- - - ! '>='
175
- - !ruby/object:Gem::Version
176
- version: '0'
127
+ version_requirements: *70105915839000
177
128
  - !ruby/object:Gem::Dependency
178
129
  name: guard-bundler
179
- requirement: !ruby/object:Gem::Requirement
130
+ requirement: &70105915838200 !ruby/object:Gem::Requirement
180
131
  none: false
181
132
  requirements:
182
133
  - - ! '>='
@@ -184,15 +135,10 @@ dependencies:
184
135
  version: '0'
185
136
  type: :development
186
137
  prerelease: false
187
- version_requirements: !ruby/object:Gem::Requirement
188
- none: false
189
- requirements:
190
- - - ! '>='
191
- - !ruby/object:Gem::Version
192
- version: '0'
138
+ version_requirements: *70105915838200
193
139
  - !ruby/object:Gem::Dependency
194
140
  name: guard-coffeescript
195
- requirement: !ruby/object:Gem::Requirement
141
+ requirement: &70105915837780 !ruby/object:Gem::Requirement
196
142
  none: false
197
143
  requirements:
198
144
  - - ! '>='
@@ -200,15 +146,10 @@ dependencies:
200
146
  version: '0'
201
147
  type: :development
202
148
  prerelease: false
203
- version_requirements: !ruby/object:Gem::Requirement
204
- none: false
205
- requirements:
206
- - - ! '>='
207
- - !ruby/object:Gem::Version
208
- version: '0'
149
+ version_requirements: *70105915837780
209
150
  - !ruby/object:Gem::Dependency
210
151
  name: rainbows
211
- requirement: !ruby/object:Gem::Requirement
152
+ requirement: &70105915837100 !ruby/object:Gem::Requirement
212
153
  none: false
213
154
  requirements:
214
155
  - - ~>
@@ -216,15 +157,10 @@ dependencies:
216
157
  version: 4.4.3
217
158
  type: :development
218
159
  prerelease: false
219
- version_requirements: !ruby/object:Gem::Requirement
220
- none: false
221
- requirements:
222
- - - ~>
223
- - !ruby/object:Gem::Version
224
- version: 4.4.3
160
+ version_requirements: *70105915837100
225
161
  - !ruby/object:Gem::Dependency
226
162
  name: thin
227
- requirement: !ruby/object:Gem::Requirement
163
+ requirement: &70105915836480 !ruby/object:Gem::Requirement
228
164
  none: false
229
165
  requirements:
230
166
  - - ! '>='
@@ -232,15 +168,10 @@ dependencies:
232
168
  version: '0'
233
169
  type: :development
234
170
  prerelease: false
235
- version_requirements: !ruby/object:Gem::Requirement
236
- none: false
237
- requirements:
238
- - - ! '>='
239
- - !ruby/object:Gem::Version
240
- version: '0'
171
+ version_requirements: *70105915836480
241
172
  - !ruby/object:Gem::Dependency
242
173
  name: rack-test
243
- requirement: !ruby/object:Gem::Requirement
174
+ requirement: &70105915835820 !ruby/object:Gem::Requirement
244
175
  none: false
245
176
  requirements:
246
177
  - - ! '>='
@@ -248,15 +179,10 @@ dependencies:
248
179
  version: '0'
249
180
  type: :development
250
181
  prerelease: false
251
- version_requirements: !ruby/object:Gem::Requirement
252
- none: false
253
- requirements:
254
- - - ! '>='
255
- - !ruby/object:Gem::Version
256
- version: '0'
182
+ version_requirements: *70105915835820
257
183
  - !ruby/object:Gem::Dependency
258
184
  name: async_rack_test
259
- requirement: !ruby/object:Gem::Requirement
185
+ requirement: &70105915835180 !ruby/object:Gem::Requirement
260
186
  none: false
261
187
  requirements:
262
188
  - - ! '>='
@@ -264,15 +190,10 @@ dependencies:
264
190
  version: '0'
265
191
  type: :development
266
192
  prerelease: false
267
- version_requirements: !ruby/object:Gem::Requirement
268
- none: false
269
- requirements:
270
- - - ! '>='
271
- - !ruby/object:Gem::Version
272
- version: '0'
193
+ version_requirements: *70105915835180
273
194
  - !ruby/object:Gem::Dependency
274
195
  name: foreman
275
- requirement: !ruby/object:Gem::Requirement
196
+ requirement: &70105915834620 !ruby/object:Gem::Requirement
276
197
  none: false
277
198
  requirements:
278
199
  - - ! '>='
@@ -280,15 +201,10 @@ dependencies:
280
201
  version: '0'
281
202
  type: :development
282
203
  prerelease: false
283
- version_requirements: !ruby/object:Gem::Requirement
284
- none: false
285
- requirements:
286
- - - ! '>='
287
- - !ruby/object:Gem::Version
288
- version: '0'
204
+ version_requirements: *70105915834620
289
205
  - !ruby/object:Gem::Dependency
290
206
  name: sprockets
291
- requirement: !ruby/object:Gem::Requirement
207
+ requirement: &70105915834200 !ruby/object:Gem::Requirement
292
208
  none: false
293
209
  requirements:
294
210
  - - ! '>='
@@ -296,15 +212,10 @@ dependencies:
296
212
  version: '0'
297
213
  type: :development
298
214
  prerelease: false
299
- version_requirements: !ruby/object:Gem::Requirement
300
- none: false
301
- requirements:
302
- - - ! '>='
303
- - !ruby/object:Gem::Version
304
- version: '0'
215
+ version_requirements: *70105915834200
305
216
  - !ruby/object:Gem::Dependency
306
217
  name: rake
307
- requirement: !ruby/object:Gem::Requirement
218
+ requirement: &70105915833780 !ruby/object:Gem::Requirement
308
219
  none: false
309
220
  requirements:
310
221
  - - ! '>='
@@ -312,12 +223,7 @@ dependencies:
312
223
  version: '0'
313
224
  type: :development
314
225
  prerelease: false
315
- version_requirements: !ruby/object:Gem::Requirement
316
- none: false
317
- requirements:
318
- - - ! '>='
319
- - !ruby/object:Gem::Version
320
- version: '0'
226
+ version_requirements: *70105915833780
321
227
  description: Firehose is a realtime web application toolkit for building realtime
322
228
  Ruby web applications.
323
229
  email:
@@ -336,6 +242,7 @@ files:
336
242
  - .rspec
337
243
  - .travis.yml
338
244
  - Gemfile
245
+ - Gemfile.lock
339
246
  - Guardfile
340
247
  - Procfile
341
248
  - README.md
@@ -358,12 +265,11 @@ files:
358
265
  - lib/firehose/client/consumer.rb
359
266
  - lib/firehose/client/producer.rb
360
267
  - lib/firehose/logging.rb
361
- - lib/firehose/patches/rainbows.rb
362
- - lib/firehose/patches/swf_policy_request.rb
363
- - lib/firehose/patches/thin.rb
364
268
  - lib/firehose/rack.rb
365
269
  - lib/firehose/rack/app.rb
366
270
  - lib/firehose/rack/consumer.rb
271
+ - lib/firehose/rack/consumer/http_long_poll.rb
272
+ - lib/firehose/rack/consumer/web_socket.rb
367
273
  - lib/firehose/rack/ping.rb
368
274
  - lib/firehose/rack/publisher.rb
369
275
  - lib/firehose/rails.rb
@@ -381,6 +287,7 @@ files:
381
287
  - spec/lib/client/consumer_spec.rb
382
288
  - spec/lib/client/producer_spec.rb
383
289
  - spec/lib/firehose_spec.rb
290
+ - spec/lib/rack/consumer/http_long_poll_spec.rb
384
291
  - spec/lib/rack/consumer_spec.rb
385
292
  - spec/lib/rack/ping_spec.rb
386
293
  - spec/lib/rack/publisher_spec.rb
@@ -389,6 +296,7 @@ files:
389
296
  - spec/lib/server/publisher_spec.rb
390
297
  - spec/lib/server/subscriber_spec.rb
391
298
  - spec/spec_helper.rb
299
+ has_rdoc: true
392
300
  homepage: http://firehose.io/
393
301
  licenses: []
394
302
  post_install_message:
@@ -401,21 +309,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
401
309
  - - ! '>='
402
310
  - !ruby/object:Gem::Version
403
311
  version: '0'
404
- segments:
405
- - 0
406
- hash: 1688305656129984284
407
312
  required_rubygems_version: !ruby/object:Gem::Requirement
408
313
  none: false
409
314
  requirements:
410
315
  - - ! '>='
411
316
  - !ruby/object:Gem::Version
412
317
  version: '0'
413
- segments:
414
- - 0
415
- hash: 1688305656129984284
416
318
  requirements: []
417
319
  rubyforge_project: firehose
418
- rubygems_version: 1.8.23
320
+ rubygems_version: 1.3.9.5
419
321
  signing_key:
420
322
  specification_version: 3
421
323
  summary: Build realtime Ruby web applications
@@ -428,6 +330,7 @@ test_files:
428
330
  - spec/lib/client/consumer_spec.rb
429
331
  - spec/lib/client/producer_spec.rb
430
332
  - spec/lib/firehose_spec.rb
333
+ - spec/lib/rack/consumer/http_long_poll_spec.rb
431
334
  - spec/lib/rack/consumer_spec.rb
432
335
  - spec/lib/rack/ping_spec.rb
433
336
  - spec/lib/rack/publisher_spec.rb
@@ -1,35 +0,0 @@
1
- # This file file monkeypatches Rainbows! to return a proper SWF policy file.
2
- # Enable this with something like this in your config/rainbows.rb file:
3
- #
4
- # after_fork do |server, worker|
5
- # require 'firehose/patches/rainbows'
6
- # end if ENV['RACK_ENV'] == 'development'
7
- #
8
- # You should only use this in development. It has not been well tested in a
9
- # production environment.
10
- #
11
- # NOTE: This only works if you are using Rainbows! with EventMachine.
12
- #
13
- # Some helpful links:
14
- # http://unicorn.bogomips.org/Unicorn/Configurator.html
15
- # http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html
16
- # http://blog.vokle.com/index.php/2009/06/10/dealing-with-adobe-and-serving-socket-policy-servers-via-nginx-and-10-lines-of-code/
17
-
18
- require 'firehose/patches/swf_policy_request'
19
- require 'rainbows'
20
-
21
- # Ensure the class already exists so we are overwriting it.
22
- Rainbows::EventMachine::Client
23
-
24
- class Rainbows::EventMachine::Client
25
- include Firehose::Patches::SwfPolicyRequest
26
- alias_method :receive_data_without_swf_policy, :receive_data
27
- # Borrowed from: https://github.com/igrigorik/em-websocket/blob/3e7f7d7760cc23b9d1d34fc1c17bab4423b5d11a/lib/em-websocket/connection.rb#L104
28
- def receive_data(data)
29
- if handle_swf_policy_request(data)
30
- return false
31
- else
32
- receive_data_without_swf_policy(data)
33
- end
34
- end
35
- end
@@ -1,26 +0,0 @@
1
- module Firehose
2
- module Patches
3
- # Helpers for making Firehose work with Macromedia Flash Sockets. Since this doesn't use "normal" HTTP, we
4
- # have to monkey patch both Rainbows and Thin to recognize when a request is for a SWF policy.
5
- module SwfPolicyRequest
6
- # Borrowed from: https://github.com/igrigorik/em-websocket/blob/3e7f7d7760cc23b9d1d34fc1c17bab4423b5d11a/lib/em-websocket/connection.rb#L104
7
- def handle_swf_policy_request(data)
8
- if data =~ /\A<policy-file-request\s*\/>/
9
- Firehose.logger.debug "Received SWF Policy request: #{data.inspect}"
10
- send_data policy
11
- close_connection_after_writing
12
- true
13
- end
14
- end
15
-
16
- def policy
17
- <<-EOS
18
- <?xml version="1.0"?>
19
- <cross-domain-policy>
20
- <allow-access-from domain="*" to-ports="*"/>
21
- </cross-domain-policy>
22
- EOS
23
- end
24
- end
25
- end
26
- end
@@ -1,27 +0,0 @@
1
- # This file file monkeypatches Thin to return a proper SWF policy file.
2
- #
3
- # You should only use this in development. It has not been well tested in a
4
- # production environment.
5
- #
6
- # NOTE: This only works if you are using Thin with EventMachine.
7
- #
8
- # Some helpful links:
9
- # http://www.adobe.com/devnet/flashplayer/articles/socket_policy_files.html
10
- # http://blog.vokle.com/index.php/2009/06/10/dealing-with-adobe-and-serving-socket-policy-servers-via-nginx-and-10-lines-of-code/
11
-
12
- require 'firehose/patches/swf_policy_request'
13
- require 'thin'
14
- # Ensure the class already exists so we are overwriting it.
15
- Thin::Connection
16
-
17
- class Thin::Connection
18
- include Firehose::Patches::SwfPolicyRequest
19
- alias_method :receive_data_without_swf_policy, :receive_data
20
- def receive_data(data)
21
- if handle_swf_policy_request(data)
22
- return false
23
- else
24
- receive_data_without_swf_policy(data)
25
- end
26
- end
27
- end