em-eventsource 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/Gemfile +3 -0
  2. data/README.md +90 -0
  3. data/lib/em-eventsource.rb +163 -0
  4. metadata +105 -0
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
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: []