em-eventsource 0.1.2
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/Gemfile +3 -0
- data/README.md +90 -0
- data/lib/em-eventsource.rb +163 -0
- metadata +105 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# EventSource client for EventMachine
|
2
|
+
|
3
|
+
This is currently a work in progress.
|
4
|
+
|
5
|
+
See the specification: http://dev.w3.org/html5/eventsource/
|
6
|
+
|
7
|
+
## Install
|
8
|
+
|
9
|
+
Install with Rubygems:
|
10
|
+
|
11
|
+
gem install em-eventsource
|
12
|
+
|
13
|
+
If you use bundler, add it to your Gemfile:
|
14
|
+
|
15
|
+
gem "em-ucengine", "~>0.1.2"
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
Basic usage:
|
20
|
+
|
21
|
+
require "em-eventsource"
|
22
|
+
EM.run do
|
23
|
+
source = EventMachine::EventSource.new("http://example.com/streaming")
|
24
|
+
source.message do |message|
|
25
|
+
puts "new message #{message}"
|
26
|
+
end
|
27
|
+
source.start # Start listening
|
28
|
+
end
|
29
|
+
|
30
|
+
Listening specific event name:
|
31
|
+
|
32
|
+
source.on "eventname" do |message|
|
33
|
+
puts "eventname #{message}"
|
34
|
+
end
|
35
|
+
|
36
|
+
Handle error:
|
37
|
+
|
38
|
+
source.error do |error|
|
39
|
+
puts "error #{error}"
|
40
|
+
end
|
41
|
+
|
42
|
+
Handle open stream:
|
43
|
+
|
44
|
+
source.open do
|
45
|
+
puts "opened"
|
46
|
+
end
|
47
|
+
|
48
|
+
Close the stream:
|
49
|
+
|
50
|
+
source.close
|
51
|
+
|
52
|
+
Current status of the connection:
|
53
|
+
|
54
|
+
source.ready_state # Can be EM::EventSource::CLOSED, EM::EventSource::CONNECTING, EM::EventSource::OPEN
|
55
|
+
|
56
|
+
Override the default retry value (if the connection is lost):
|
57
|
+
|
58
|
+
source.retry = 5 # in seconds
|
59
|
+
|
60
|
+
Get Last-Event-Id value:
|
61
|
+
|
62
|
+
source.last_event_id
|
63
|
+
|
64
|
+
Attach middleware:
|
65
|
+
|
66
|
+
source.use EM::Middleware::JSONResponse
|
67
|
+
|
68
|
+
## Licence
|
69
|
+
|
70
|
+
MIT License
|
71
|
+
|
72
|
+
Copyright (C) 2011 by af83
|
73
|
+
|
74
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
75
|
+
of this software and associated documentation files (the "Software"), to deal
|
76
|
+
in the Software without restriction, including without limitation the rights
|
77
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
78
|
+
copies of the Software, and to permit persons to whom the Software is
|
79
|
+
furnished to do so, subject to the following conditions:
|
80
|
+
|
81
|
+
The above copyright notice and this permission notice shall be included in
|
82
|
+
all copies or substantial portions of the Software.
|
83
|
+
|
84
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
85
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
86
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
87
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
88
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
89
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
90
|
+
THE SOFTWARE.
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "eventmachine"
|
4
|
+
require "em-http-request"
|
5
|
+
|
6
|
+
module EventMachine
|
7
|
+
# EventSource
|
8
|
+
# dev.w3.org/html5/eventsource/
|
9
|
+
class EventSource
|
10
|
+
# Get API url
|
11
|
+
attr_reader :url
|
12
|
+
# Get ready state
|
13
|
+
attr_reader :ready_state
|
14
|
+
# Get current retry value (in seconds)
|
15
|
+
attr_reader :retry
|
16
|
+
# Override retry value (in seconds)
|
17
|
+
attr_writer :retry
|
18
|
+
# Get value of last event id
|
19
|
+
attr_reader :last_event_id
|
20
|
+
# Ready state
|
21
|
+
# The connection has not yet been established, or it was closed and the user agent is reconnecting.
|
22
|
+
CONNECTING = 0
|
23
|
+
# The user agent has an open connection and is dispatching events as it receives them.
|
24
|
+
OPEN = 1
|
25
|
+
# The connection is not open, and the user agent is not trying to reconnect. Either there was a fatal error or the close() method was invoked.
|
26
|
+
CLOSED = 2
|
27
|
+
# Create a new stream
|
28
|
+
#
|
29
|
+
# @param [String] url
|
30
|
+
# @param [Hash] query
|
31
|
+
# @param [Hash] headers
|
32
|
+
def initialize(url, query={}, headers={})
|
33
|
+
@url = url
|
34
|
+
@query = query
|
35
|
+
@headers = headers
|
36
|
+
@ready_state = CLOSED
|
37
|
+
|
38
|
+
@last_event_id = nil
|
39
|
+
@retry = 3 # seconds
|
40
|
+
|
41
|
+
@opens = []
|
42
|
+
@errors = []
|
43
|
+
@messages = []
|
44
|
+
@on = {}
|
45
|
+
@middlewares = []
|
46
|
+
end
|
47
|
+
|
48
|
+
# Add open event handler
|
49
|
+
def open(&block)
|
50
|
+
@opens << block
|
51
|
+
end
|
52
|
+
|
53
|
+
# Add a specific event handler
|
54
|
+
#
|
55
|
+
# @param [String] name of event
|
56
|
+
def on(name, &block)
|
57
|
+
@on[name] ||= []
|
58
|
+
@on[name] << block
|
59
|
+
end
|
60
|
+
|
61
|
+
# Add message event handler
|
62
|
+
def message(&block)
|
63
|
+
@messages << block
|
64
|
+
end
|
65
|
+
|
66
|
+
# Add error event handler
|
67
|
+
def error(&block)
|
68
|
+
@errors << block
|
69
|
+
end
|
70
|
+
|
71
|
+
# Add a middleware
|
72
|
+
def use(*args, &block)
|
73
|
+
@middlewares << (args << block)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Start subscription
|
77
|
+
def start
|
78
|
+
@ready_state = CONNECTING
|
79
|
+
listen
|
80
|
+
end
|
81
|
+
|
82
|
+
# Cancel subscription
|
83
|
+
def close
|
84
|
+
@ready_state = CLOSED
|
85
|
+
@req.close
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def listen
|
91
|
+
@req = prepare_request
|
92
|
+
@req.errback do
|
93
|
+
next if @ready_state == CLOSED
|
94
|
+
@ready_state = CONNECTING
|
95
|
+
@errors.each { |error| error.call("Connection lost. Reconnecting.") }
|
96
|
+
EM.add_timer(@retry) do
|
97
|
+
listen
|
98
|
+
end
|
99
|
+
end
|
100
|
+
@req.headers do |headers|
|
101
|
+
if headers.status != 200
|
102
|
+
close
|
103
|
+
@errors.each { |error| error.call("Unexpected response status #{headers.status}") }
|
104
|
+
next
|
105
|
+
end
|
106
|
+
if /^text\/event-stream/.match headers['CONTENT_TYPE']
|
107
|
+
@ready_state = OPEN
|
108
|
+
@opens.each { |open| open.call }
|
109
|
+
else
|
110
|
+
close
|
111
|
+
@errors.each { |error| error.call("The content-type '#{headers['CONTENT_TYPE']}' is not text/event-stream") }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
buffer = ""
|
115
|
+
@req.stream do |chunk|
|
116
|
+
buffer += chunk
|
117
|
+
# TODO: manage \r, \r\n, \n
|
118
|
+
while index = buffer.index("\n\n")
|
119
|
+
stream = buffer.slice!(0..index)
|
120
|
+
handle_stream(stream)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def handle_stream(stream)
|
126
|
+
event = []
|
127
|
+
name = nil
|
128
|
+
stream.split("\n").each do |part|
|
129
|
+
/^data:(.+)$/.match(part) do |m|
|
130
|
+
event << m[1].strip
|
131
|
+
end
|
132
|
+
/^id:(.+)$/.match(part) do |m|
|
133
|
+
@last_event_id = m[1].strip
|
134
|
+
end
|
135
|
+
/^event:(.+)$/.match(part) do |m|
|
136
|
+
name = m[1].strip
|
137
|
+
end
|
138
|
+
/^retry:(.+)$/.match(part) do |m|
|
139
|
+
if m[1].strip! =~ /^[0-9]+$/
|
140
|
+
@retry = m[1].to_i
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
if name.nil?
|
145
|
+
@messages.each { |message| message.call(event.join("\n")) }
|
146
|
+
else
|
147
|
+
@on[name].each { |message| message.call(event.join("\n")) } if not @on[name].nil?
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def prepare_request
|
152
|
+
conn = EM::HttpRequest.new(@url)
|
153
|
+
@middlewares.each { |middleware|
|
154
|
+
block = middleware.pop
|
155
|
+
conn.use *middleware, &block
|
156
|
+
}
|
157
|
+
headers = @headers.merge({'Cache-Control' => 'no-cache'})
|
158
|
+
headers.merge!({'Last-Event-Id' => @last_event_id }) if not @last_event_id.nil?
|
159
|
+
conn.get({ :query => @query,
|
160
|
+
:head => headers})
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-eventsource
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- François de Metz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-08-02 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: eventmachine
|
16
|
+
requirement: &11648200 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.0.beta3
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *11648200
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: em-http-request
|
27
|
+
requirement: &11647720 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.0.0.beta4
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *11647720
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: minitest
|
38
|
+
requirement: &11647260 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *11647260
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: bundler
|
49
|
+
requirement: &11646880 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *11646880
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rake
|
60
|
+
requirement: &11646420 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *11646420
|
69
|
+
description: ! " em-eventsource is an eventmachine library to consume Server-Sent
|
70
|
+
Events streaming API.\n You can find the specification here: http://dev.w3.org/html5/eventsource/\n"
|
71
|
+
email: francois.de.metz@af83.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files:
|
75
|
+
- README.md
|
76
|
+
files:
|
77
|
+
- README.md
|
78
|
+
- Gemfile
|
79
|
+
- lib/em-eventsource.rb
|
80
|
+
homepage: http://github.com/af83/em-eventsource
|
81
|
+
licenses: []
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.8.6
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: em-eventsource is an eventmachine library to consume Server-Sent Events streaming
|
104
|
+
API.
|
105
|
+
test_files: []
|