firehose 0.0.3 → 0.0.4
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/README.md +30 -35
- data/firehose.gemspec +3 -3
- data/lib/assets/flash/{WebSocketMain.swf → firehose/WebSocketMain.swf} +0 -0
- data/lib/assets/javascripts/firehose.js.coffee +5 -0
- data/lib/assets/javascripts/firehose/base.js.coffee +1 -0
- data/lib/assets/javascripts/firehose/client.js.coffee +9 -0
- data/lib/assets/javascripts/firehose/long_poll.js.coffee +79 -0
- data/lib/assets/javascripts/firehose/transport.js.coffee +40 -0
- data/lib/assets/javascripts/firehose/web_socket.js.coffee +48 -0
- data/lib/firehose/version.rb +3 -2
- metadata +34 -26
data/README.md
CHANGED
@@ -9,26 +9,26 @@
|
|
9
9
|
|
10
10
|
# What is Firehose?
|
11
11
|
|
12
|
-
Firehose is both a Rack application and
|
12
|
+
Firehose is both a Rack application and JavasScript library that makes building scalable real-time web applications possible.
|
13
13
|
|
14
14
|
# How is it different from socket.io?
|
15
15
|
|
16
16
|
socket.io attempts to store connection state per node instance. Firehose makes no attempt to store connection state.
|
17
17
|
|
18
|
-
Also, socket.io attempts to abstract a low-latency full-duplex port. Firehose assumes that its impossible to simulate this in older web browsers that don't support WebSockets. As such, Firehose focuses on low-latency server-to-client connections and encourages the use of HTTP transports for client-to-server communications.
|
18
|
+
Also, socket.io attempts to abstract a low-latency full-duplex port. Firehose assumes that its impossible to simulate this in older web browsers that don't support WebSockets. As such, Firehose focuses on low-latency server-to-client connections and encourages the use of existing HTTP transports, like POST and PUT, for client-to-server communications.
|
19
19
|
|
20
|
-
Finally,
|
20
|
+
Finally, Firehose attempts to solve data consistency issues and authentication by encourage the use of proxying to the web application.
|
21
21
|
|
22
22
|
# Getting Started
|
23
23
|
|
24
24
|
First, you'll need to install and run RabbitMQ.
|
25
25
|
|
26
|
-
```
|
26
|
+
```sh
|
27
27
|
apt-get install rabbitmq # Install on Ubuntu
|
28
28
|
brew install rabbitmq # Install on Mac Homebrew
|
29
29
|
```
|
30
30
|
|
31
|
-
## The
|
31
|
+
## The Server
|
32
32
|
|
33
33
|
The consumer is the web server that your client connects to for real-time updates. Create a config.ru file with the following:
|
34
34
|
|
@@ -36,21 +36,7 @@ The consumer is the web server that your client connects to for real-time update
|
|
36
36
|
require 'rubygems'
|
37
37
|
require 'firehose'
|
38
38
|
|
39
|
-
run Firehose::
|
40
|
-
config.timeout = 20
|
41
|
-
|
42
|
-
# Extract the consumer ID from the HTTP session. This could be a cookie
|
43
|
-
# query param, or whatever.
|
44
|
-
config.consumer = Proc.new do |env|
|
45
|
-
Firehose::Consumer.new(env['HTTP_CONSUMER_ID'])
|
46
|
-
end
|
47
|
-
|
48
|
-
# Use the /url/path for the queue channel. You could change this to a query
|
49
|
-
# param, or whatever
|
50
|
-
config.channel = Proc.new do |env|
|
51
|
-
env['PATH_INFO']
|
52
|
-
end
|
53
|
-
end
|
39
|
+
run Firehose::Rack::App.new
|
54
40
|
```
|
55
41
|
|
56
42
|
Now run the config.ru file in a server that supports async Rack callbacks (like thin or rainbows)
|
@@ -59,32 +45,41 @@ Now run the config.ru file in a server that supports async Rack callbacks (like
|
|
59
45
|
thin -R config.ru -p 4000 start
|
60
46
|
```
|
61
47
|
|
62
|
-
## The
|
48
|
+
## The Publisher
|
63
49
|
|
64
|
-
Lets test
|
50
|
+
Lets test it out! Open two terminal windows. In one window, curl:
|
65
51
|
|
66
52
|
```sh
|
67
|
-
curl "http://localhost:4000/"
|
53
|
+
curl "http://localhost:4000/hello"
|
68
54
|
```
|
69
55
|
|
70
|
-
Then run the following
|
56
|
+
Then run the following in the other terminal:
|
71
57
|
|
72
|
-
```
|
73
|
-
|
74
|
-
|
58
|
+
```sh
|
59
|
+
curl -X PUT -d "Greetings fellow human being..." "http://localhost:4000/hello"
|
60
|
+
```
|
61
|
+
|
62
|
+
and you should see the message in the other terminal.
|
75
63
|
|
76
|
-
|
64
|
+
```sh
|
65
|
+
Greetings fellow human being...
|
77
66
|
```
|
78
67
|
|
79
|
-
##
|
68
|
+
## Yeah, so?
|
69
|
+
|
70
|
+
You have a dirt simple HTTP pub-sub feed. You could setup an `after_commit` hook on ActiveRecord to push JSON to an end-point. On the subscription side, you could have a Backbone.js application that picks up the changes and updates the client-side UI.
|
71
|
+
|
72
|
+
Holy mackerel! Its a nice, clean, RESTful way to build real-time web applications.
|
73
|
+
|
74
|
+
# The JavaScript Client
|
80
75
|
|
81
|
-
|
76
|
+
Firehose doesn't just stop at curl; it has a full-featured JavaScript client that lets you subscribe to channels for live updates.
|
82
77
|
|
83
78
|
```javascript
|
84
79
|
new Firehose.Client()
|
85
80
|
.url({
|
86
|
-
websocket: 'ws://
|
87
|
-
longpoll: 'http://
|
81
|
+
websocket: 'ws://localhost:5100',
|
82
|
+
longpoll: 'http://localhost:5100'
|
88
83
|
})
|
89
84
|
.params({
|
90
85
|
cid: '024023948234'
|
@@ -93,13 +88,13 @@ new Firehose.Client()
|
|
93
88
|
timeout: 5000
|
94
89
|
})
|
95
90
|
.message(function(msg){
|
96
|
-
|
91
|
+
console.log(msg); // Fires when a message is received from the server.
|
97
92
|
})
|
98
93
|
.connected(function(){
|
99
|
-
|
94
|
+
console.log('Howdy friend!');
|
100
95
|
})
|
101
96
|
.disconnected(function(){
|
102
|
-
|
97
|
+
console.log('Bu bye');
|
103
98
|
})
|
104
99
|
.connect()
|
105
100
|
```
|
data/firehose.gemspec
CHANGED
@@ -5,9 +5,9 @@ require "firehose/version"
|
|
5
5
|
Gem::Specification.new do |s|
|
6
6
|
s.name = "firehose"
|
7
7
|
s.version = Firehose::VERSION
|
8
|
-
s.authors = ["Brad Gessler"]
|
9
|
-
s.email = ["brad@
|
10
|
-
s.homepage = ""
|
8
|
+
s.authors = ["Brad Gessler", "Steel Fu"]
|
9
|
+
s.email = ["brad@polleverywhere.com", "steel@polleverywhere.com"]
|
10
|
+
s.homepage = "http://github.com/polleverywhere/firehose"
|
11
11
|
s.summary = %q{Build realtime Ruby web applications}
|
12
12
|
s.description = %q{Firehose is a realtime web application toolkit for building realtime Ruby web applications.}
|
13
13
|
|
File without changes
|
@@ -0,0 +1 @@
|
|
1
|
+
window.Firehose = {}
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class Firehose.Client
|
2
|
+
constructor: (args) ->
|
3
|
+
@transports = ['WebSocket', 'LongPoll']
|
4
|
+
|
5
|
+
# Detect the first supported transport give it back
|
6
|
+
transport = _.detect @transports, (transport) ->
|
7
|
+
Firehose[transport].supported()
|
8
|
+
|
9
|
+
return new Firehose[transport](args)
|
@@ -0,0 +1,79 @@
|
|
1
|
+
class Firehose.LongPoll extends Firehose.Transport
|
2
|
+
# CORS is supported in IE 8+
|
3
|
+
@ieSupported: =>
|
4
|
+
$.browser.msie and parseInt($.browser.version) > 7 and window.XDomainRequest
|
5
|
+
|
6
|
+
@supported: =>
|
7
|
+
# IE 8+, FF 3.5+, Chrome 4+, Safari 4+, Opera 12+, iOS 3.2+, Android 2.1+
|
8
|
+
$.support.cors || LongPoll.ieSupported()
|
9
|
+
|
10
|
+
constructor: (args) ->
|
11
|
+
super args
|
12
|
+
|
13
|
+
# We use the lag time to make the client live longer than the server.
|
14
|
+
@_lagTime = 5000
|
15
|
+
@_timeout = @options.timeout + @_lagTime
|
16
|
+
@_dataType = "json"
|
17
|
+
@_offlineTimer
|
18
|
+
@_okInterval = 0
|
19
|
+
|
20
|
+
@registerIETransport()
|
21
|
+
|
22
|
+
registerIETransport: =>
|
23
|
+
if LongPoll.ieSupported()
|
24
|
+
$.ajaxTransport 'json', (options, orignalOptions, jqXhr) ->
|
25
|
+
xdr = null
|
26
|
+
send: (_, callback) ->
|
27
|
+
xdr = new XDomainRequest()
|
28
|
+
xdr.onload = ->
|
29
|
+
statusCode = if xdr.responseText.length > 0 then 200 else 204
|
30
|
+
callback(statusCode, 'success', text: xdr.responseText)
|
31
|
+
|
32
|
+
xdr.onerror = xdr.ontimeout = ->
|
33
|
+
callback(400, 'failed', text: xdr.responseText)
|
34
|
+
|
35
|
+
xdr.open(options.type, options.url)
|
36
|
+
xdr.send(options.data)
|
37
|
+
|
38
|
+
abort: ->
|
39
|
+
if xdr
|
40
|
+
xdr.onerror = $.noop()
|
41
|
+
xdr.abort()
|
42
|
+
|
43
|
+
# also, override the support check
|
44
|
+
$.support.cors = true;
|
45
|
+
|
46
|
+
connect: (delay = 0) =>
|
47
|
+
@onConnected()
|
48
|
+
super(delay)
|
49
|
+
|
50
|
+
_request: =>
|
51
|
+
$.ajax @url["longpoll"],
|
52
|
+
crossDomain: true
|
53
|
+
cache: false
|
54
|
+
dataType: @_dataType
|
55
|
+
data: @params
|
56
|
+
timeout: @_timeout
|
57
|
+
success: @_success
|
58
|
+
error: @_error
|
59
|
+
|
60
|
+
_success: (data, status, jqXhr) =>
|
61
|
+
if jqXhr.status == 204
|
62
|
+
# If we get a 204 back, that means the server timed-out and sent back a 204 with a
|
63
|
+
# X-Http-Next-Request header
|
64
|
+
#
|
65
|
+
# Why did we use a 204 and not a 408? Because FireFox is really stupid about 400 level error
|
66
|
+
# codes and would claims its a 0 error code, which we use for something else. Firefox is IE
|
67
|
+
# in thise case
|
68
|
+
@connect(@_okInterval)
|
69
|
+
else
|
70
|
+
@onMessage(data)
|
71
|
+
@connect(@_okInterval)
|
72
|
+
|
73
|
+
# We need this custom handler to have the connection status
|
74
|
+
# properly displayed
|
75
|
+
_error: (jqXhr, status, error) =>
|
76
|
+
clearTimeout(@_offlineTimer)
|
77
|
+
@onDisconnected()
|
78
|
+
@_offlineTimer = setTimeout(@onConnected, @_errorInterval + @_lagTime)
|
79
|
+
@connect(@_errorInterval)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class Firehose.Transport
|
2
|
+
# Class method to determine whether transport is supported by the current browser
|
3
|
+
@supported: =>
|
4
|
+
false
|
5
|
+
|
6
|
+
constructor: (args) ->
|
7
|
+
@_errorInterval = 5000
|
8
|
+
|
9
|
+
# Chainable config
|
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
|
19
|
+
connect: (delay = 0) =>
|
20
|
+
setTimeout =>
|
21
|
+
@_request()
|
22
|
+
, delay
|
23
|
+
this
|
24
|
+
|
25
|
+
# Sub classes need to implement this method to handle requests
|
26
|
+
_request: =>
|
27
|
+
|
28
|
+
# Default error handler
|
29
|
+
_error: (event) =>
|
30
|
+
# Lets try to connect again with delay
|
31
|
+
@onDisconnected()
|
32
|
+
@connect(@_errorInterval)
|
33
|
+
|
34
|
+
# Default connection established handler
|
35
|
+
_open: (event) =>
|
36
|
+
@onConnected()
|
37
|
+
|
38
|
+
# Default connection closed handler
|
39
|
+
_close: (event) =>
|
40
|
+
@onDisconnected()
|
@@ -0,0 +1,48 @@
|
|
1
|
+
class Firehose.WebSocket extends Firehose.Transport
|
2
|
+
@flashSupported: =>
|
3
|
+
$.browser.msie
|
4
|
+
|
5
|
+
@supported: =>
|
6
|
+
# Compatibility reference: http://caniuse.com/websockets
|
7
|
+
# Native websocket support + Flash web socket
|
8
|
+
window.WebSocket || (window["MozWebSocket"] and window.MozWebSocket) || WebSocket.flashSupported()
|
9
|
+
|
10
|
+
constructor: (args) ->
|
11
|
+
super args
|
12
|
+
|
13
|
+
# Set flash socket path.
|
14
|
+
WebSocket.__swfLocation = "/flash/firehose/WebSocketMain.swf"
|
15
|
+
|
16
|
+
# Mozilla decided to have their own implementation of Web Sockets so detect for that
|
17
|
+
window.WebSocket = window.MozWebSocket if window["MozWebSocket"] and window.MozWebSocket
|
18
|
+
|
19
|
+
_request: =>
|
20
|
+
@socket = new window.WebSocket(@url["websocket"] + "?" + $.param(@params))
|
21
|
+
@socket.onopen = @_open
|
22
|
+
@socket.onclose = @_close
|
23
|
+
@socket.onerror = @_error
|
24
|
+
@socket.onmessage = @_message
|
25
|
+
|
26
|
+
_message: (event) =>
|
27
|
+
try
|
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)
|
31
|
+
|
32
|
+
_close: (event) =>
|
33
|
+
if !event || (event and !event.wasClean)
|
34
|
+
# This was not a clean disconnect. An error occurred somewhere
|
35
|
+
# Lets try to reconnect
|
36
|
+
@_error(event)
|
37
|
+
|
38
|
+
_error: (event) =>
|
39
|
+
# Cleanup the current connection
|
40
|
+
if @socket
|
41
|
+
@socket.onopen = null
|
42
|
+
@socket.onclose = null
|
43
|
+
@socket.onerror = null
|
44
|
+
@socket.onmessage = null
|
45
|
+
@socket.close()
|
46
|
+
delete(@socket)
|
47
|
+
|
48
|
+
super
|
data/lib/firehose/version.rb
CHANGED
metadata
CHANGED
@@ -1,11 +1,12 @@
|
|
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.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Brad Gessler
|
9
|
+
- Steel Fu
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
@@ -13,7 +14,7 @@ date: 2012-04-16 00:00:00.000000000 Z
|
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: eventmachine
|
16
|
-
requirement: &
|
17
|
+
requirement: &70267687581480 !ruby/object:Gem::Requirement
|
17
18
|
none: false
|
18
19
|
requirements:
|
19
20
|
- - ! '>='
|
@@ -21,10 +22,10 @@ dependencies:
|
|
21
22
|
version: 1.0.0.beta
|
22
23
|
type: :runtime
|
23
24
|
prerelease: false
|
24
|
-
version_requirements: *
|
25
|
+
version_requirements: *70267687581480
|
25
26
|
- !ruby/object:Gem::Dependency
|
26
27
|
name: amqp
|
27
|
-
requirement: &
|
28
|
+
requirement: &70267687580600 !ruby/object:Gem::Requirement
|
28
29
|
none: false
|
29
30
|
requirements:
|
30
31
|
- - ! '>='
|
@@ -32,10 +33,10 @@ dependencies:
|
|
32
33
|
version: 0.9.4
|
33
34
|
type: :runtime
|
34
35
|
prerelease: false
|
35
|
-
version_requirements: *
|
36
|
+
version_requirements: *70267687580600
|
36
37
|
- !ruby/object:Gem::Dependency
|
37
38
|
name: thin
|
38
|
-
requirement: &
|
39
|
+
requirement: &70267687580200 !ruby/object:Gem::Requirement
|
39
40
|
none: false
|
40
41
|
requirements:
|
41
42
|
- - ! '>='
|
@@ -43,10 +44,10 @@ dependencies:
|
|
43
44
|
version: '0'
|
44
45
|
type: :runtime
|
45
46
|
prerelease: false
|
46
|
-
version_requirements: *
|
47
|
+
version_requirements: *70267687580200
|
47
48
|
- !ruby/object:Gem::Dependency
|
48
49
|
name: websocket-rack
|
49
|
-
requirement: &
|
50
|
+
requirement: &70267687579660 !ruby/object:Gem::Requirement
|
50
51
|
none: false
|
51
52
|
requirements:
|
52
53
|
- - ! '>='
|
@@ -54,10 +55,10 @@ dependencies:
|
|
54
55
|
version: '0'
|
55
56
|
type: :runtime
|
56
57
|
prerelease: false
|
57
|
-
version_requirements: *
|
58
|
+
version_requirements: *70267687579660
|
58
59
|
- !ruby/object:Gem::Dependency
|
59
60
|
name: rspec
|
60
|
-
requirement: &
|
61
|
+
requirement: &70267687579160 !ruby/object:Gem::Requirement
|
61
62
|
none: false
|
62
63
|
requirements:
|
63
64
|
- - ! '>='
|
@@ -65,10 +66,10 @@ dependencies:
|
|
65
66
|
version: '0'
|
66
67
|
type: :development
|
67
68
|
prerelease: false
|
68
|
-
version_requirements: *
|
69
|
+
version_requirements: *70267687579160
|
69
70
|
- !ruby/object:Gem::Dependency
|
70
71
|
name: rack-test
|
71
|
-
requirement: &
|
72
|
+
requirement: &70267687578500 !ruby/object:Gem::Requirement
|
72
73
|
none: false
|
73
74
|
requirements:
|
74
75
|
- - ! '>='
|
@@ -76,10 +77,10 @@ dependencies:
|
|
76
77
|
version: '0'
|
77
78
|
type: :development
|
78
79
|
prerelease: false
|
79
|
-
version_requirements: *
|
80
|
+
version_requirements: *70267687578500
|
80
81
|
- !ruby/object:Gem::Dependency
|
81
82
|
name: guard-rspec
|
82
|
-
requirement: &
|
83
|
+
requirement: &70267687338120 !ruby/object:Gem::Requirement
|
83
84
|
none: false
|
84
85
|
requirements:
|
85
86
|
- - ! '>='
|
@@ -87,10 +88,10 @@ dependencies:
|
|
87
88
|
version: '0'
|
88
89
|
type: :development
|
89
90
|
prerelease: false
|
90
|
-
version_requirements: *
|
91
|
+
version_requirements: *70267687338120
|
91
92
|
- !ruby/object:Gem::Dependency
|
92
93
|
name: guard-bundler
|
93
|
-
requirement: &
|
94
|
+
requirement: &70267687337440 !ruby/object:Gem::Requirement
|
94
95
|
none: false
|
95
96
|
requirements:
|
96
97
|
- - ! '>='
|
@@ -98,10 +99,10 @@ dependencies:
|
|
98
99
|
version: '0'
|
99
100
|
type: :development
|
100
101
|
prerelease: false
|
101
|
-
version_requirements: *
|
102
|
+
version_requirements: *70267687337440
|
102
103
|
- !ruby/object:Gem::Dependency
|
103
104
|
name: thin
|
104
|
-
requirement: &
|
105
|
+
requirement: &70267687336400 !ruby/object:Gem::Requirement
|
105
106
|
none: false
|
106
107
|
requirements:
|
107
108
|
- - ! '>='
|
@@ -109,10 +110,10 @@ dependencies:
|
|
109
110
|
version: '0'
|
110
111
|
type: :development
|
111
112
|
prerelease: false
|
112
|
-
version_requirements: *
|
113
|
+
version_requirements: *70267687336400
|
113
114
|
- !ruby/object:Gem::Dependency
|
114
115
|
name: em-http-request
|
115
|
-
requirement: &
|
116
|
+
requirement: &70267687335300 !ruby/object:Gem::Requirement
|
116
117
|
none: false
|
117
118
|
requirements:
|
118
119
|
- - ~>
|
@@ -120,10 +121,10 @@ dependencies:
|
|
120
121
|
version: 0.3.0
|
121
122
|
type: :development
|
122
123
|
prerelease: false
|
123
|
-
version_requirements: *
|
124
|
+
version_requirements: *70267687335300
|
124
125
|
- !ruby/object:Gem::Dependency
|
125
126
|
name: guard-coffeescript
|
126
|
-
requirement: &
|
127
|
+
requirement: &70267687334360 !ruby/object:Gem::Requirement
|
127
128
|
none: false
|
128
129
|
requirements:
|
129
130
|
- - ! '>='
|
@@ -131,11 +132,12 @@ dependencies:
|
|
131
132
|
version: '0'
|
132
133
|
type: :development
|
133
134
|
prerelease: false
|
134
|
-
version_requirements: *
|
135
|
+
version_requirements: *70267687334360
|
135
136
|
description: Firehose is a realtime web application toolkit for building realtime
|
136
137
|
Ruby web applications.
|
137
138
|
email:
|
138
|
-
- brad@
|
139
|
+
- brad@polleverywhere.com
|
140
|
+
- steel@polleverywhere.com
|
139
141
|
executables:
|
140
142
|
- firehose
|
141
143
|
- firehose-test
|
@@ -153,7 +155,13 @@ files:
|
|
153
155
|
- bin/firehose-test
|
154
156
|
- config.ru
|
155
157
|
- firehose.gemspec
|
156
|
-
- lib/assets/flash/WebSocketMain.swf
|
158
|
+
- lib/assets/flash/firehose/WebSocketMain.swf
|
159
|
+
- lib/assets/javascripts/firehose.js.coffee
|
160
|
+
- lib/assets/javascripts/firehose/base.js.coffee
|
161
|
+
- lib/assets/javascripts/firehose/client.js.coffee
|
162
|
+
- lib/assets/javascripts/firehose/long_poll.js.coffee
|
163
|
+
- lib/assets/javascripts/firehose/transport.js.coffee
|
164
|
+
- lib/assets/javascripts/firehose/web_socket.js.coffee
|
157
165
|
- lib/firehose.rb
|
158
166
|
- lib/firehose/goliath.rb
|
159
167
|
- lib/firehose/http_publisher.rb
|
@@ -166,7 +174,7 @@ files:
|
|
166
174
|
- spec/integrations/goliath_spec.rb
|
167
175
|
- spec/integrations/thin_spec.rb
|
168
176
|
- spec/spec_helper.rb
|
169
|
-
homepage:
|
177
|
+
homepage: http://github.com/polleverywhere/firehose
|
170
178
|
licenses: []
|
171
179
|
post_install_message:
|
172
180
|
rdoc_options: []
|