firehose 0.0.8 → 0.0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/Procfile +1 -1
- data/README.md +17 -21
- data/lib/assets/javascripts/firehose/client.js.coffee +41 -4
- data/lib/assets/javascripts/firehose/long_poll.js.coffee +17 -12
- data/lib/assets/javascripts/firehose/transport.js.coffee +11 -17
- data/lib/assets/javascripts/firehose/web_socket.js.coffee +14 -10
- data/lib/firehose/cli.rb +1 -3
- data/lib/firehose/rack.rb +33 -2
- data/lib/firehose/version.rb +2 -2
- metadata +24 -25
- data/lib/firehose/thin.rb +0 -7
data/Procfile
CHANGED
@@ -1 +1 @@
|
|
1
|
-
firehose: firehose
|
1
|
+
firehose: firehose server
|
data/README.md
CHANGED
@@ -31,7 +31,7 @@ $ gem install firehose
|
|
31
31
|
Now fire up the server.
|
32
32
|
|
33
33
|
```ruby
|
34
|
-
$ firehose
|
34
|
+
$ firehose server
|
35
35
|
>> Thin web server (v1.3.1 codename Triple Espresso)
|
36
36
|
>> Maximum connections set to 1024
|
37
37
|
>> Listening on 127.0.0.1:7478, CTRL+C to stop
|
@@ -72,27 +72,23 @@ Firehose doesn't just stop at curl; it has a full-featured JavaScript client tha
|
|
72
72
|
Still have the server running? Copy and paste the code below into Firebug or the WebKit console.
|
73
73
|
|
74
74
|
```javascript
|
75
|
-
new Firehose.Client(
|
76
|
-
|
77
|
-
websocket: 'ws://localhost:7478/hello',
|
78
|
-
longpoll: 'http://localhost:7478/hello'
|
79
|
-
})
|
80
|
-
.params({
|
81
|
-
cid: '024023948234'
|
82
|
-
})
|
83
|
-
.options({
|
84
|
-
timeout: 5000
|
85
|
-
})
|
86
|
-
.message(function(msg){
|
75
|
+
new Firehose.Client({
|
76
|
+
message: function(msg){
|
87
77
|
console.log(msg);
|
88
|
-
}
|
89
|
-
|
90
|
-
console.log('
|
91
|
-
}
|
92
|
-
|
93
|
-
console.log('
|
94
|
-
}
|
95
|
-
|
78
|
+
},
|
79
|
+
connected: function(){
|
80
|
+
console.log("Great Scotts!! We're connected!");
|
81
|
+
},
|
82
|
+
disconnected: function(){
|
83
|
+
console.log("Well shucks, we're not connected anymore");
|
84
|
+
},
|
85
|
+
error: function(){
|
86
|
+
console.log("Well then, something went horribly wrong.");
|
87
|
+
},
|
88
|
+
// Note that we do NOT specify a protocol here because we don't
|
89
|
+
// know that yet.
|
90
|
+
uri: '//localhost:7478/hello'
|
91
|
+
}).connect();
|
96
92
|
```
|
97
93
|
|
98
94
|
Then publish another message.
|
@@ -1,8 +1,45 @@
|
|
1
1
|
class Firehose.Client
|
2
|
-
|
3
|
-
|
2
|
+
# Transports that are available to Firehose.
|
3
|
+
@transports: ['WebSocket', 'LongPoll']
|
4
4
|
|
5
|
+
# Generate a random client_id.
|
6
|
+
@nextClientId: ->
|
7
|
+
Math.floor((Math.random()*99999999999)+1)
|
8
|
+
|
9
|
+
constructor: (config={}) ->
|
10
|
+
# The clientId is used by the server to remember messages between requests. In a production environment,
|
11
|
+
# this should probably be some combination of "user_id-rand". Why the rand? Because a user may have multiple
|
12
|
+
# tabs open to the application, and each tab needs a different channel on the server.
|
13
|
+
config.clientId ||= Firehose.Client.nextClientId()
|
14
|
+
# List of transport stragies we have to use.
|
15
|
+
config.transports ||= Firehose.Client.transports
|
16
|
+
# Empty handler for messages.
|
17
|
+
config.message ||= ->
|
18
|
+
# Empty handler for error handling.
|
19
|
+
config.error ||= ->
|
20
|
+
# Empty handler for when we establish a connection.
|
21
|
+
config.connected ||= ->
|
22
|
+
# Empty handler for when we're disconnected.
|
23
|
+
config.disconnected ||= ->
|
24
|
+
# Additional options.
|
25
|
+
config.options ||= {}
|
26
|
+
# URL that we'll connect to.
|
27
|
+
config.uri ||= undefined
|
28
|
+
# Params that we'll tack on to the URL. We include a clientId in here for kicks.
|
29
|
+
config.params ||= { cid: config.clientId }
|
30
|
+
# Do stuff before we send the message into config.message. The sensible
|
31
|
+
# default on the webs is to parse JSON.
|
32
|
+
config.parse ||= (body) ->
|
33
|
+
$.parseJSON(body)
|
34
|
+
|
35
|
+
# Hang on to these config for when we connect.
|
36
|
+
@config = config
|
37
|
+
# Make sure we return ourself out of the constructor so we can chain.
|
38
|
+
this
|
39
|
+
|
40
|
+
connect: =>
|
5
41
|
# Figure out what transport is supported and return it.
|
6
|
-
|
42
|
+
# TODO if the initial connection fails, try the next transport mmmkay?
|
43
|
+
for transport in @config.transports
|
7
44
|
if transport = Firehose[transport]
|
8
|
-
return new transport(
|
45
|
+
return new transport(@config).connect() if transport.supported()
|
@@ -5,22 +5,28 @@ class Firehose.LongPoll extends Firehose.Transport
|
|
5
5
|
|
6
6
|
@supported: =>
|
7
7
|
# IE 8+, FF 3.5+, Chrome 4+, Safari 4+, Opera 12+, iOS 3.2+, Android 2.1+
|
8
|
-
$.support.cors || LongPoll.ieSupported()
|
8
|
+
$.support.cors || Firehose.LongPoll.ieSupported()
|
9
9
|
|
10
10
|
constructor: (args) ->
|
11
11
|
super args
|
12
12
|
|
13
|
+
# Configrations specifically for web sockets
|
14
|
+
@config.longPoll ||= {}
|
15
|
+
# Protocol schema we should use for talking to WS server.
|
16
|
+
@config.longPoll.url ||= "http:#{@config.uri}"
|
17
|
+
|
13
18
|
# We use the lag time to make the client live longer than the server.
|
14
19
|
@_lagTime = 5000
|
15
|
-
@_timeout = @options.timeout + @_lagTime
|
16
|
-
@_dataType = "json"
|
20
|
+
@_timeout = @config.options.timeout + @_lagTime
|
17
21
|
@_offlineTimer
|
18
22
|
@_okInterval = 0
|
19
23
|
|
20
24
|
@registerIETransport()
|
21
25
|
|
22
26
|
registerIETransport: =>
|
23
|
-
if LongPoll.ieSupported()
|
27
|
+
if Firehose.LongPoll.ieSupported()
|
28
|
+
# TODO - Ask Steel what this is for. Looks like some kind of polygot fill, but I want
|
29
|
+
# to take the 'json' transport out and do that myself.
|
24
30
|
$.ajaxTransport 'json', (options, orignalOptions, jqXhr) ->
|
25
31
|
xdr = null
|
26
32
|
send: (_, callback) ->
|
@@ -44,15 +50,14 @@ class Firehose.LongPoll extends Firehose.Transport
|
|
44
50
|
$.support.cors = true;
|
45
51
|
|
46
52
|
connect: (delay = 0) =>
|
47
|
-
@
|
53
|
+
@config.connected()
|
48
54
|
super(delay)
|
49
55
|
|
50
56
|
_request: =>
|
51
|
-
$.ajax @url
|
57
|
+
$.ajax @config.longPoll.url,
|
52
58
|
crossDomain: true
|
53
59
|
cache: false
|
54
|
-
|
55
|
-
data: @params
|
60
|
+
data: @config.params
|
56
61
|
timeout: @_timeout
|
57
62
|
success: @_success
|
58
63
|
error: @_error
|
@@ -67,13 +72,13 @@ class Firehose.LongPoll extends Firehose.Transport
|
|
67
72
|
# in thise case
|
68
73
|
@connect(@_okInterval)
|
69
74
|
else
|
70
|
-
@
|
75
|
+
@config.message(@config.parse(data))
|
71
76
|
@connect(@_okInterval)
|
72
77
|
|
73
78
|
# We need this custom handler to have the connection status
|
74
79
|
# properly displayed
|
75
80
|
_error: (jqXhr, status, error) =>
|
76
81
|
clearTimeout(@_offlineTimer)
|
77
|
-
@
|
78
|
-
@_offlineTimer = setTimeout(@
|
79
|
-
@connect(@
|
82
|
+
@config.disconnected()
|
83
|
+
@_offlineTimer = setTimeout(@config.connected, @_retryDelay + @_lagTime)
|
84
|
+
@connect(@_retryDelay)
|
@@ -1,21 +1,15 @@
|
|
1
1
|
class Firehose.Transport
|
2
|
-
# Class method to determine whether transport is supported by the current browser
|
2
|
+
# Class method to determine whether transport is supported by the current browser. Note that while
|
3
|
+
# the transport may be supported by the browser, its possible that the network connection won't
|
4
|
+
# succeed. That should be accounted for during the initial connecting to the server.
|
3
5
|
@supported: =>
|
4
6
|
false
|
5
7
|
|
6
|
-
constructor: (
|
7
|
-
@
|
8
|
+
constructor: (config={}) ->
|
9
|
+
@config = config
|
10
|
+
@_retryDelay = 5000
|
8
11
|
|
9
|
-
#
|
10
|
-
connected: (@onConnected) -> this
|
11
|
-
disconnected: (@onDisconnected) -> this
|
12
|
-
message: (@onMessage) -> this
|
13
|
-
error: (@onError) -> this
|
14
|
-
url: (@url) -> this
|
15
|
-
params: (@params) -> this
|
16
|
-
options: (@options) -> this
|
17
|
-
|
18
|
-
# Lets rock'n'roll
|
12
|
+
# Lets rock'n'roll! Connect to the server.
|
19
13
|
connect: (delay = 0) =>
|
20
14
|
setTimeout =>
|
21
15
|
@_request()
|
@@ -28,13 +22,13 @@ class Firehose.Transport
|
|
28
22
|
# Default error handler
|
29
23
|
_error: (event) =>
|
30
24
|
# Lets try to connect again with delay
|
31
|
-
@
|
32
|
-
@connect(@
|
25
|
+
@config.disconnected()
|
26
|
+
@connect(@_retryDelay)
|
33
27
|
|
34
28
|
# Default connection established handler
|
35
29
|
_open: (event) =>
|
36
|
-
@
|
30
|
+
@config.connected()
|
37
31
|
|
38
32
|
# Default connection closed handler
|
39
33
|
_close: (event) =>
|
40
|
-
@
|
34
|
+
@config.disconnected()
|
@@ -10,24 +10,28 @@ class Firehose.WebSocket extends Firehose.Transport
|
|
10
10
|
constructor: (args) ->
|
11
11
|
super args
|
12
12
|
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
# Configrations specifically for web sockets
|
14
|
+
@config.webSocket ||= {}
|
15
|
+
# Protocol schema we should use for talking to WS server.
|
16
|
+
@config.webSocket.url ||= "ws:#{@config.uri}?#{$.param(@config.params)}"
|
17
|
+
# Path of the swf WebSocket that we use in non-WS flash browsers.
|
18
|
+
@config.webSocket.swf_path ||= "/flash/firehose/WebSocketMain.swf"
|
19
|
+
|
20
|
+
# Set flash socket path for the WS SWF polyfill.
|
21
|
+
WebSocket.__swfLocation = @config.webSocket.swf_path
|
22
|
+
|
23
|
+
# Mozilla decided to have their own implementation of Web Sockets so detect for that.
|
17
24
|
window.WebSocket = window.MozWebSocket if window["MozWebSocket"] and window.MozWebSocket
|
18
25
|
|
19
26
|
_request: =>
|
20
|
-
@socket = new window.WebSocket(@url
|
27
|
+
@socket = new window.WebSocket(@config.webSocket.url)
|
21
28
|
@socket.onopen = @_open
|
22
29
|
@socket.onclose = @_close
|
23
30
|
@socket.onerror = @_error
|
24
31
|
@socket.onmessage = @_message
|
25
32
|
|
26
33
|
_message: (event) =>
|
27
|
-
|
28
|
-
@onMessage($.parseJSON(event.data))
|
29
|
-
catch e # If JSON parsing doesn't work, send the rest of it on through
|
30
|
-
@onMessage(event.data)
|
34
|
+
@config.message(@config.parse(event.data))
|
31
35
|
|
32
36
|
_close: (event) =>
|
33
37
|
if !event || (event and !event.wasClean)
|
@@ -44,5 +48,5 @@ class Firehose.WebSocket extends Firehose.Transport
|
|
44
48
|
@socket.onmessage = null
|
45
49
|
@socket.close()
|
46
50
|
delete(@socket)
|
47
|
-
|
51
|
+
|
48
52
|
super
|
data/lib/firehose/cli.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'thor'
|
2
|
+
require 'thin'
|
2
3
|
|
3
4
|
module Firehose
|
4
5
|
class CLI < Thor
|
@@ -6,12 +7,9 @@ module Firehose
|
|
6
7
|
method_option :port, :type => :numeric, :default => 7474, :required => true, :aliases => '-p'
|
7
8
|
method_option :host, :type => :string, :default => '0.0.0.0', :required => true, :aliases => '-h'
|
8
9
|
def server
|
9
|
-
require 'thin'
|
10
|
-
|
11
10
|
server = Thin::Server.new(options[:host], options[:port]) do
|
12
11
|
run Firehose::Rack::App.new
|
13
12
|
end
|
14
|
-
|
15
13
|
server.start!
|
16
14
|
end
|
17
15
|
end
|
data/lib/firehose/rack.rb
CHANGED
@@ -10,19 +10,50 @@ module Firehose
|
|
10
10
|
cid = req.params['cid']
|
11
11
|
path = req.path
|
12
12
|
method = req.request_method
|
13
|
+
timeout = 30
|
14
|
+
cors_headers = {
|
15
|
+
'Access-Control-Allow-Origin' => env['HTTP_ORIGIN'],
|
16
|
+
'Access-Control-Allow-Methods' => 'GET',
|
17
|
+
'Access-Control-Max-Age' => '1728000'
|
18
|
+
}
|
13
19
|
|
14
20
|
case method
|
15
21
|
# GET is how clients subscribe to the queue. When a messages comes in, we flush out a response,
|
16
22
|
# close down the requeust, and the client then reconnects.
|
17
23
|
when 'GET'
|
24
|
+
p [:subscribed, cid, path]
|
25
|
+
|
18
26
|
EM.next_tick do
|
27
|
+
# Setup a subscription with a client id. We haven't subscribed yet here.
|
19
28
|
subscription = Firehose::Subscription.new(cid)
|
29
|
+
|
30
|
+
# Setup a timeout timer to tell clients that time out that everything is OK
|
31
|
+
# and they should come back for more
|
32
|
+
timer = EM.add_timer(timeout) do
|
33
|
+
# We send a 204 OK to tell the client to reconnect.
|
34
|
+
env['async.callback'].call [204, cors_headers, []]
|
35
|
+
p [:timeout]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Ok, now subscribe to the subscription.
|
20
39
|
subscription.subscribe path do |payload|
|
21
40
|
subscription.unsubscribe
|
22
|
-
|
41
|
+
subscription = nil # Set this to nil so that our heart beat timer doesn't try to double unsub.
|
42
|
+
EM.cancel_timer timer # Turn off the heart beat so we don't execute any of that business.
|
43
|
+
env['async.callback'].call [200, cors_headers, [payload]]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Unsubscribe from the subscription if its still open and something bad happened
|
47
|
+
# or the heart beat triggered before we could finish.
|
48
|
+
env['async.close'].callback do
|
49
|
+
if subscription
|
50
|
+
subscription.unsubscribe
|
51
|
+
p [:close_unsubscription]
|
52
|
+
end
|
23
53
|
end
|
24
54
|
end
|
25
55
|
|
56
|
+
# Tell the web server that this will be an async response.
|
26
57
|
Firehose::Rack::AsyncResponse
|
27
58
|
|
28
59
|
# PUT is how we throw messages on to the fan-out queue.
|
@@ -33,7 +64,7 @@ module Firehose
|
|
33
64
|
|
34
65
|
[202, {}, []]
|
35
66
|
else
|
36
|
-
[501, {}, ["#{method} not supported."]]
|
67
|
+
[501, {'Content-Type' => 'text/plain'}, ["#{method} not supported."]]
|
37
68
|
end
|
38
69
|
end
|
39
70
|
end
|
data/lib/firehose/version.rb
CHANGED
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: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-04-
|
13
|
+
date: 2012-04-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: eventmachine
|
17
|
-
requirement: &
|
17
|
+
requirement: &70248633218840 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: 1.0.0.beta
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70248633218840
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: amqp
|
28
|
-
requirement: &
|
28
|
+
requirement: &70248633201380 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: 0.9.4
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70248633201380
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: thin
|
39
|
-
requirement: &
|
39
|
+
requirement: &70248633200480 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ! '>='
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: '0'
|
45
45
|
type: :runtime
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70248633200480
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: thor
|
50
|
-
requirement: &
|
50
|
+
requirement: &70248633199280 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ! '>='
|
@@ -55,10 +55,10 @@ dependencies:
|
|
55
55
|
version: '0'
|
56
56
|
type: :runtime
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *70248633199280
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: websocket-rack
|
61
|
-
requirement: &
|
61
|
+
requirement: &70248633198520 !ruby/object:Gem::Requirement
|
62
62
|
none: false
|
63
63
|
requirements:
|
64
64
|
- - ! '>='
|
@@ -66,10 +66,10 @@ dependencies:
|
|
66
66
|
version: '0'
|
67
67
|
type: :runtime
|
68
68
|
prerelease: false
|
69
|
-
version_requirements: *
|
69
|
+
version_requirements: *70248633198520
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: rspec
|
72
|
-
requirement: &
|
72
|
+
requirement: &70248633197580 !ruby/object:Gem::Requirement
|
73
73
|
none: false
|
74
74
|
requirements:
|
75
75
|
- - ! '>='
|
@@ -77,10 +77,10 @@ dependencies:
|
|
77
77
|
version: '0'
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
|
-
version_requirements: *
|
80
|
+
version_requirements: *70248633197580
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
82
|
name: rack-test
|
83
|
-
requirement: &
|
83
|
+
requirement: &70248633196960 !ruby/object:Gem::Requirement
|
84
84
|
none: false
|
85
85
|
requirements:
|
86
86
|
- - ! '>='
|
@@ -88,10 +88,10 @@ dependencies:
|
|
88
88
|
version: '0'
|
89
89
|
type: :development
|
90
90
|
prerelease: false
|
91
|
-
version_requirements: *
|
91
|
+
version_requirements: *70248633196960
|
92
92
|
- !ruby/object:Gem::Dependency
|
93
93
|
name: guard-rspec
|
94
|
-
requirement: &
|
94
|
+
requirement: &70248633195800 !ruby/object:Gem::Requirement
|
95
95
|
none: false
|
96
96
|
requirements:
|
97
97
|
- - ! '>='
|
@@ -99,10 +99,10 @@ dependencies:
|
|
99
99
|
version: '0'
|
100
100
|
type: :development
|
101
101
|
prerelease: false
|
102
|
-
version_requirements: *
|
102
|
+
version_requirements: *70248633195800
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
104
|
name: guard-bundler
|
105
|
-
requirement: &
|
105
|
+
requirement: &70248633195200 !ruby/object:Gem::Requirement
|
106
106
|
none: false
|
107
107
|
requirements:
|
108
108
|
- - ! '>='
|
@@ -110,10 +110,10 @@ dependencies:
|
|
110
110
|
version: '0'
|
111
111
|
type: :development
|
112
112
|
prerelease: false
|
113
|
-
version_requirements: *
|
113
|
+
version_requirements: *70248633195200
|
114
114
|
- !ruby/object:Gem::Dependency
|
115
115
|
name: em-http-request
|
116
|
-
requirement: &
|
116
|
+
requirement: &70248633178620 !ruby/object:Gem::Requirement
|
117
117
|
none: false
|
118
118
|
requirements:
|
119
119
|
- - ~>
|
@@ -121,10 +121,10 @@ dependencies:
|
|
121
121
|
version: 0.3.0
|
122
122
|
type: :development
|
123
123
|
prerelease: false
|
124
|
-
version_requirements: *
|
124
|
+
version_requirements: *70248633178620
|
125
125
|
- !ruby/object:Gem::Dependency
|
126
126
|
name: guard-coffeescript
|
127
|
-
requirement: &
|
127
|
+
requirement: &70248633177760 !ruby/object:Gem::Requirement
|
128
128
|
none: false
|
129
129
|
requirements:
|
130
130
|
- - ! '>='
|
@@ -132,7 +132,7 @@ dependencies:
|
|
132
132
|
version: '0'
|
133
133
|
type: :development
|
134
134
|
prerelease: false
|
135
|
-
version_requirements: *
|
135
|
+
version_requirements: *70248633177760
|
136
136
|
description: Firehose is a realtime web application toolkit for building realtime
|
137
137
|
Ruby web applications.
|
138
138
|
email:
|
@@ -166,7 +166,6 @@ files:
|
|
166
166
|
- lib/firehose/publisher.rb
|
167
167
|
- lib/firehose/rack.rb
|
168
168
|
- lib/firehose/subscription.rb
|
169
|
-
- lib/firehose/thin.rb
|
170
169
|
- lib/firehose/version.rb
|
171
170
|
- spec/integrations/amqp_resources_spec.rb
|
172
171
|
- spec/integrations/thin_spec.rb
|