firehose 1.2.8 → 1.2.9

Sign up to get free protection for your applications and to get access to all the features.
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