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 +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
|