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 +1 -1
- data/Gemfile.lock +143 -0
- data/README.md +34 -0
- data/config/rainbows.rb +1 -5
- data/lib/assets/javascripts/firehose/long_poll.js.coffee +5 -4
- data/lib/assets/javascripts/firehose/web_socket.js.coffee +5 -1
- data/lib/firehose.rb +0 -1
- data/lib/firehose/rack/app.rb +11 -4
- data/lib/firehose/rack/consumer.rb +15 -139
- data/lib/firehose/rack/consumer/http_long_poll.rb +86 -0
- data/lib/firehose/rack/consumer/web_socket.rb +95 -0
- data/lib/firehose/server/app.rb +0 -1
- data/lib/firehose/server/channel.rb +2 -2
- data/lib/firehose/version.rb +2 -2
- data/spec/lib/client/consumer_spec.rb +6 -2
- data/spec/lib/rack/consumer/http_long_poll_spec.rb +12 -0
- data/spec/lib/rack/consumer_spec.rb +8 -0
- data/spec/lib/server/app_spec.rb +15 -1
- metadata +48 -145
- data/lib/firehose/patches/rainbows.rb +0 -35
- data/lib/firehose/patches/swf_policy_request.rb +0 -26
- data/lib/firehose/patches/thin.rb +0 -27
data/.gitignore
CHANGED
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
|
20
|
-
|
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
|
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 "
|
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
|
data/lib/firehose/rack/app.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
data/lib/firehose/server/app.rb
CHANGED
@@ -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
|
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
|
data/lib/firehose/version.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Firehose::Client::Consumer::WebSocket do
|
4
|
-
|
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
|
-
|
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
|
data/spec/lib/server/app_spec.rb
CHANGED
@@ -1 +1,15 @@
|
|
1
|
-
|
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.
|
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-
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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.
|
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
|