celluloid-eventsource 0.2.2 → 0.3.0
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -1
- data/{LICENSE.md → LICENSE} +0 -0
- data/README.md +0 -2
- data/celluloid-eventsource.gemspec +1 -2
- data/lib/celluloid/eventsource.rb +73 -34
- data/lib/celluloid/eventsource/version.rb +1 -1
- data/spec/celluloid/eventsource_spec.rb +119 -68
- data/spec/spec_helper.rb +5 -86
- data/spec/support/black_hole.rb +5 -0
- data/spec/support/dummy_server.rb +100 -0
- metadata +12 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7fbe88d12ab2285c9b866977ade2ab069c5fce76
|
4
|
+
data.tar.gz: 72e75170841864fd4746aad568c17f43e4984c55
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8ad5063fbcbe45581f9c8b53f8f7b4adf25578b4f26a20c56d942b367ff8bdf226e7d8dfbd8a222a6bd3c5986b9c90a54731ee5b924199168ba5b0f6ea6707e
|
7
|
+
data.tar.gz: e5f052c515980ca818b6bbfac1449c22e8f59b423139215b98d31c798cfbc1afeb9b6004766576496f705736afa227a39bbf890b7d78c6dab463cc736dd65318
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/{LICENSE.md → LICENSE}
RENAMED
File without changes
|
data/README.md
CHANGED
@@ -4,8 +4,6 @@
|
|
4
4
|
[](https://codeclimate.com/github/Tonkpils/celluloid-eventsource)
|
5
5
|
[](https://travis-ci.org/Tonkpils/celluloid-eventsource)
|
6
6
|
|
7
|
-
#### Under Development!! Use at your own risk :)
|
8
|
-
|
9
7
|
An EventSource client based off Celluloid::IO.
|
10
8
|
|
11
9
|
Specification based on [EventSource](http://www.w3.org/TR/2012/CR-eventsource-20121211/)
|
@@ -21,8 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_dependency 'celluloid-io', '>= 0.15.0', '<= 0.16.2'
|
22
22
|
spec.add_dependency 'http_parser.rb', '~> 0.6.0'
|
23
23
|
|
24
|
-
spec.add_development_dependency
|
25
|
-
spec.add_development_dependency "reel", '>= 0.5.0'
|
24
|
+
spec.add_development_dependency 'atomic', '~> 1.1.99'
|
26
25
|
spec.add_development_dependency "rspec", '~> 3.0.0'
|
27
26
|
spec.add_development_dependency "bundler", ">= 1.7.0"
|
28
27
|
spec.add_development_dependency "rake", '~> 10.1.0'
|
@@ -33,6 +33,8 @@ module Celluloid
|
|
33
33
|
@on = { open: ->{}, message: ->(_) {}, error: ->(_) {} }
|
34
34
|
@parser = ResponseParser.new
|
35
35
|
|
36
|
+
@chunked = false
|
37
|
+
|
36
38
|
yield self if block_given?
|
37
39
|
|
38
40
|
async.listen
|
@@ -53,7 +55,7 @@ module Celluloid
|
|
53
55
|
def listen
|
54
56
|
establish_connection
|
55
57
|
|
56
|
-
process_stream
|
58
|
+
chunked? ? process_chunked_stream : process_stream
|
57
59
|
rescue IOError
|
58
60
|
# Closing the socket during read causes this exception and kills the actor
|
59
61
|
# We really don't wan to do anything if the socket is closed.
|
@@ -82,6 +84,8 @@ module Celluloid
|
|
82
84
|
|
83
85
|
private
|
84
86
|
|
87
|
+
MessageEvent = Struct.new(:type, :data, :last_event_id)
|
88
|
+
|
85
89
|
def ssl?
|
86
90
|
url.scheme == 'https'
|
87
91
|
end
|
@@ -120,35 +124,6 @@ module Celluloid
|
|
120
124
|
}
|
121
125
|
end
|
122
126
|
|
123
|
-
def process_field(line)
|
124
|
-
case line
|
125
|
-
when /^data:(.+)$/
|
126
|
-
@data_buffer << $1.lstrip.concat("\n")
|
127
|
-
when /^id:(.+)$/
|
128
|
-
@last_event_id_buffer = $1.lstrip
|
129
|
-
when /^retry:(\d+)$/
|
130
|
-
@reconnect_timeout = $1.to_i
|
131
|
-
when /^event:(.+)$/
|
132
|
-
@event_type_buffer = $1.lstrip
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
MessageEvent = Struct.new(:type, :data, :last_event_id)
|
137
|
-
|
138
|
-
def process_event
|
139
|
-
@last_event_id = @last_event_id_buffer
|
140
|
-
|
141
|
-
unless @data_buffer.empty?
|
142
|
-
@data_buffer = @data_buffer.chomp("\n") if @data_buffer.end_with?("\n")
|
143
|
-
event = MessageEvent.new(:message, @data_buffer, @last_event_id)
|
144
|
-
event.type = @event_type_buffer.to_sym unless @event_type_buffer.empty?
|
145
|
-
|
146
|
-
dispatch_event(event)
|
147
|
-
end
|
148
|
-
|
149
|
-
clear_buffers!
|
150
|
-
end
|
151
|
-
|
152
127
|
def clear_buffers!
|
153
128
|
@data_buffer = ""
|
154
129
|
@event_type_buffer = ""
|
@@ -160,20 +135,84 @@ module Celluloid
|
|
160
135
|
end
|
161
136
|
end
|
162
137
|
|
138
|
+
def chunked?
|
139
|
+
@chunked
|
140
|
+
end
|
141
|
+
|
142
|
+
def process_chunked_stream
|
143
|
+
until closed? || @socket.eof?
|
144
|
+
handle_chunked_stream
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
163
148
|
def process_stream
|
164
149
|
until closed? || @socket.eof?
|
165
150
|
line = @socket.readline
|
151
|
+
line.strip.empty? ? process_event : parse_line(line)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def handle_chunked_stream
|
156
|
+
chunk_header = @socket.readline
|
157
|
+
bytes_to_read = chunk_header.to_i(16)
|
158
|
+
bytes_read = 0
|
159
|
+
while bytes_read < bytes_to_read do
|
160
|
+
line = @socket.readline
|
161
|
+
bytes_read += line.size
|
162
|
+
|
163
|
+
line.strip.empty? ? process_event : parse_line(line)
|
164
|
+
end
|
165
|
+
|
166
|
+
if !line.nil? && line.strip.empty?
|
167
|
+
process_event
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def parse_line(line)
|
172
|
+
case line
|
173
|
+
when /^:.*$/
|
174
|
+
when /^(\w+): ?(.*)$/
|
175
|
+
process_field($1, $2)
|
176
|
+
else
|
177
|
+
if chunked? && !@data_buffer.empty?
|
178
|
+
@data_buffer.rstrip!
|
179
|
+
process_field("data", line.rstrip)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def process_event
|
185
|
+
@last_event_id = @last_event_id_buffer
|
186
|
+
|
187
|
+
return if @data_buffer.empty?
|
188
|
+
|
189
|
+
@data_buffer.chomp!("\n") if @data_buffer.end_with?("\n")
|
190
|
+
event = MessageEvent.new(:message, @data_buffer, @last_event_id)
|
191
|
+
event.type = @event_type_buffer.to_sym unless @event_type_buffer.empty?
|
192
|
+
|
193
|
+
dispatch_event(event)
|
194
|
+
ensure
|
195
|
+
clear_buffers!
|
196
|
+
end
|
166
197
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
198
|
+
def process_field(field_name, field_value)
|
199
|
+
case field_name
|
200
|
+
when "event"
|
201
|
+
@event_type_buffer = field_value
|
202
|
+
when "data"
|
203
|
+
@data_buffer << field_value.concat("\n")
|
204
|
+
when "id"
|
205
|
+
@last_event_id_buffer = field_value
|
206
|
+
when "retry"
|
207
|
+
if /^(?<num>\d+)$/ =~ field_value
|
208
|
+
@reconnect_timeout = num.to_i
|
171
209
|
end
|
172
210
|
end
|
173
211
|
end
|
174
212
|
|
175
213
|
def handle_headers(headers)
|
176
214
|
if headers['Content-Type'].include?("text/event-stream")
|
215
|
+
@chunked = !headers["Transfer-Encoding"].nil? && headers["Transfer-Encoding"].include?("chunked")
|
177
216
|
@ready_state = OPEN
|
178
217
|
@on[:open].call
|
179
218
|
else
|
@@ -1,15 +1,23 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'support/dummy_server'
|
2
3
|
|
3
|
-
TIMEOUT = 0.0001
|
4
4
|
|
5
|
+
# See: https://html.spec.whatwg.org/multipage/comms.html#event-stream-interpretation
|
5
6
|
RSpec.describe Celluloid::EventSource do
|
6
|
-
let(:data) { "foo bar " }
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
let!(:chunk_size) { DummyServer::CHUNK_SIZE }
|
9
|
+
|
10
|
+
def dummy
|
11
|
+
@dummy ||= DummyServer.new
|
12
|
+
end
|
13
|
+
|
14
|
+
before(:all) do
|
15
|
+
dummy.listen(DummyServer::CONFIG[:BindAddress], DummyServer::CONFIG[:Port])
|
16
|
+
Thread.new { dummy.start }
|
17
|
+
end
|
18
|
+
|
19
|
+
after(:all) do
|
20
|
+
dummy.shutdown
|
13
21
|
end
|
14
22
|
|
15
23
|
describe '#initialize' do
|
@@ -33,93 +41,136 @@ RSpec.describe Celluloid::EventSource do
|
|
33
41
|
end
|
34
42
|
end
|
35
43
|
|
36
|
-
|
37
|
-
with_sse_server do |server|
|
38
|
-
@last_event_id = ""
|
39
|
-
ces = Celluloid::EventSource.new("http://localhost:63310") do |conn|
|
40
|
-
conn.on_message { |event| @last_event_id = event.last_event_id }
|
41
|
-
end
|
44
|
+
context 'callbacks' do
|
42
45
|
|
43
|
-
|
46
|
+
let(:future) { Celluloid::Future.new }
|
47
|
+
let(:value_class) { Class.new(Struct.new(:value)) }
|
44
48
|
|
45
|
-
|
46
|
-
|
47
|
-
|
49
|
+
describe '#on_open' do
|
50
|
+
|
51
|
+
it 'the client has an opened connection' do
|
48
52
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
53
|
+
Celluloid::EventSource.new(dummy.endpoint) do |conn|
|
54
|
+
conn.on_open do
|
55
|
+
future.signal(value_class.new({ called: true, state: conn.ready_state }))
|
56
|
+
conn.close
|
57
|
+
end
|
54
58
|
end
|
55
59
|
|
56
|
-
|
60
|
+
expect(future.value).to eq({ called: true, state: Celluloid::EventSource::OPEN })
|
61
|
+
end
|
62
|
+
end
|
57
63
|
|
58
|
-
|
64
|
+
describe '#on_error' do
|
59
65
|
|
60
|
-
|
61
|
-
}.to_not yield_control
|
62
|
-
end
|
63
|
-
end
|
66
|
+
it 'receives response body through error event' do
|
64
67
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
conn.on_message(&event)
|
68
|
+
Celluloid::EventSource.new("#{dummy.endpoint}/error") do |conn|
|
69
|
+
conn.on_error do |error|
|
70
|
+
future.signal(value_class.new({ msg: error, state: conn.ready_state }))
|
71
|
+
end
|
70
72
|
end
|
71
73
|
|
72
|
-
|
74
|
+
expect(future.value).to eq({ msg: { status_code: 400, body: '{"msg": "blop"}' },
|
75
|
+
state: Celluloid::EventSource::CLOSED })
|
76
|
+
end
|
77
|
+
end
|
73
78
|
|
74
|
-
|
79
|
+
describe '#on_message' do
|
75
80
|
|
76
|
-
|
77
|
-
}.to yield_with_args(Celluloid::EventSource::MessageEvent)
|
78
|
-
end
|
79
|
-
end
|
81
|
+
it 'receives data through message event' do
|
80
82
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
83
|
+
Celluloid::EventSource.new(dummy.endpoint) do |conn|
|
84
|
+
conn.on_message do |message|
|
85
|
+
if '3' == message.last_event_id
|
86
|
+
future.signal(value_class.new({ msg: message, state: conn.ready_state }))
|
87
|
+
conn.close
|
88
|
+
end
|
89
|
+
end
|
86
90
|
end
|
87
91
|
|
88
|
-
|
92
|
+
payload = future.value
|
93
|
+
expect(payload[:msg]).to be_a(Celluloid::EventSource::MessageEvent)
|
94
|
+
expect(payload[:msg].type).to eq(:message)
|
95
|
+
expect(payload[:msg].last_event_id).to eq('3')
|
96
|
+
expect(payload[:state]).to eq(Celluloid::EventSource::OPEN)
|
97
|
+
end
|
89
98
|
|
90
|
-
|
91
|
-
|
92
|
-
|
99
|
+
it 'ignores lines starting with ":"' do
|
100
|
+
Celluloid::EventSource.new("#{dummy.endpoint}/ping") do |conn|
|
101
|
+
conn.on_message do |message|
|
102
|
+
future.signal(value_class.new({ msg: message, state: conn.ready_state }))
|
103
|
+
conn.close
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
expect(future.value(3)[:msg].data).to eq('pong')
|
108
|
+
end
|
93
109
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
110
|
+
it "aggregates events properly" do
|
111
|
+
Celluloid::EventSource.new("#{dummy.endpoint}/continuous") do |conn|
|
112
|
+
conn.on_message do |message|
|
113
|
+
future.signal(value_class.new({ msg: message, state: conn.ready_state }))
|
114
|
+
conn.close
|
115
|
+
end
|
99
116
|
end
|
117
|
+
expect(future.value(3)[:msg].data).to eq("YHOO\n+2\n10")
|
118
|
+
end
|
100
119
|
|
101
|
-
|
120
|
+
context "with chunked streams" do
|
121
|
+
it "properly parses chunked encoding" do
|
122
|
+
Celluloid::EventSource.new("#{dummy.endpoint}/chunk") do |conn|
|
123
|
+
conn.on_message do |message|
|
124
|
+
future.signal(value_class.new({ msg: message, state: conn.ready_state }))
|
125
|
+
conn.close
|
126
|
+
end
|
127
|
+
end
|
128
|
+
expect(future.value(3)[:msg].data).to eq("f" * (chunk_size + 25))
|
129
|
+
end
|
102
130
|
|
103
|
-
|
104
|
-
|
105
|
-
|
131
|
+
it "parses multiple continuous chunks" do
|
132
|
+
Celluloid::EventSource.new("#{dummy.endpoint}/continuous_chunks") do |conn|
|
133
|
+
conn.on_message do |message|
|
134
|
+
future.signal(value_class.new({ msg: message, state: conn.ready_state }))
|
135
|
+
conn.close
|
136
|
+
end
|
137
|
+
end
|
106
138
|
|
107
|
-
|
108
|
-
|
109
|
-
|
139
|
+
data = "o" * chunk_size + "\n" + "m" * chunk_size + "\n" + "g" * chunk_size
|
140
|
+
expect(future.value(3)[:msg].data).to eq(data)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "parses multiple chunks" do
|
144
|
+
Celluloid::EventSource.new("#{dummy.endpoint}/multiple_chunks") do |conn|
|
145
|
+
conn.on_message do |message|
|
146
|
+
future.signal(value_class.new({ msg: message, state: conn.ready_state }))
|
147
|
+
conn.close
|
148
|
+
end
|
149
|
+
end
|
110
150
|
|
111
|
-
|
112
|
-
ces = Celluloid::EventSource.new("http://localhost:63310") do |conn|
|
113
|
-
conn.on(event_name, &event)
|
151
|
+
expect(future.value(3)[:msg].data).to eq({test: "long_chunk", another_chunk: "a" * chunk_size, chunks: "f" * chunk_size }.to_json)
|
114
152
|
end
|
153
|
+
end
|
154
|
+
end
|
115
155
|
|
116
|
-
|
156
|
+
describe '#on' do
|
157
|
+
let(:custom) { :custom_event }
|
117
158
|
|
118
|
-
|
159
|
+
it 'receives custom events and handles them' do
|
119
160
|
|
120
|
-
|
121
|
-
|
161
|
+
Celluloid::EventSource.new("#{dummy.endpoint}/#{custom}") do |conn|
|
162
|
+
conn.on(custom) do |message|
|
163
|
+
future.signal(value_class.new({ msg: message, state: conn.ready_state }))
|
164
|
+
conn.close
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
payload = future.value
|
169
|
+
expect(payload[:msg]).to be_a(Celluloid::EventSource::MessageEvent)
|
170
|
+
expect(payload[:msg].type).to eq(custom)
|
171
|
+
expect(payload[:msg].last_event_id).to eq('1')
|
172
|
+
expect(payload[:state]).to eq(Celluloid::EventSource::OPEN)
|
173
|
+
end
|
122
174
|
end
|
123
175
|
end
|
124
|
-
|
125
176
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -4,8 +4,6 @@ require 'bundler/setup'
|
|
4
4
|
require 'celluloid/eventsource'
|
5
5
|
require 'celluloid/rspec'
|
6
6
|
|
7
|
-
require 'reel'
|
8
|
-
|
9
7
|
logfile = File.open(File.expand_path("../../log/test.log", __FILE__), 'a')
|
10
8
|
logfile.sync = true
|
11
9
|
|
@@ -13,89 +11,10 @@ Celluloid.logger = Logger.new(logfile)
|
|
13
11
|
|
14
12
|
RSpec.configure do |config|
|
15
13
|
config.expose_dsl_globally = false
|
16
|
-
end
|
17
|
-
|
18
|
-
class ServerSentEvents < Reel::Server::HTTP
|
19
|
-
include Celluloid::Logger
|
20
|
-
|
21
|
-
attr_reader :last_event_id, :connections
|
22
|
-
|
23
|
-
def initialize(ip = '127.0.0.1', port = 63310)
|
24
|
-
@connections = []
|
25
|
-
@history = []
|
26
|
-
@last_event_id = 0
|
27
|
-
super(ip, port, &method(:on_connection))
|
28
|
-
end
|
29
|
-
|
30
|
-
def broadcast(event, data)
|
31
|
-
if @history.size >= 10
|
32
|
-
@history.slice!(0, @history.size - 1000)
|
33
|
-
end
|
34
|
-
|
35
|
-
@last_event_id += 1
|
36
|
-
@history << { id: @last_event_id, event: event, data: data }
|
37
|
-
|
38
|
-
@connections.each do |socket|
|
39
|
-
async.send_sse(socket, data, event, @last_event_id)
|
40
|
-
end
|
41
|
-
true
|
42
|
-
end
|
43
|
-
|
44
|
-
def send_ping
|
45
|
-
@connections.each do |socket|
|
46
|
-
begin
|
47
|
-
socket << ":\n"
|
48
|
-
rescue Reel::SocketError
|
49
|
-
@connections.delete(socket)
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
private
|
55
|
-
# event and id are optional, Eventsource only needs data
|
56
|
-
def send_sse(socket, data, event = nil, id = nil)
|
57
|
-
begin
|
58
|
-
socket.id id if id
|
59
|
-
socket.event event if event
|
60
|
-
socket.data data
|
61
|
-
rescue Reel::SocketError, NoMethodError
|
62
|
-
@connections.delete(socket) if @connections.include?(socket)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def handle_request(request)
|
67
|
-
event_stream = Reel::EventStream.new do |socket|
|
68
|
-
@connections << socket
|
69
|
-
socket.retry 5000
|
70
|
-
# after a Connection reset resend newer Messages to the Client, query['last_event_id'] is needed for https://github.com/Yaffle/EventSource
|
71
|
-
if @history.count > 0 && id = request.headers['Last-Event-ID']
|
72
|
-
begin
|
73
|
-
if history = @history.select {|h| h[:id] >= Integer(id)}.map {|a| "id: \nevent: %s%s\ndata: %s" % [a[:id], a[:event], a[:data]]}.join("\n\n")
|
74
|
-
socket << "%s\n\n" % [history]
|
75
|
-
end
|
76
|
-
rescue ArgumentError, Reel::SocketError
|
77
|
-
@connections.delete(socket)
|
78
|
-
request.close
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
request.respond Reel::StreamResponse.new(:ok, {
|
84
|
-
'Content-Type' => 'text/event-stream; charset=utf-8',
|
85
|
-
'Cache-Control' => 'no-cache'}, event_stream)
|
86
|
-
end
|
87
14
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
elsif request.path == '/error/no_body'
|
94
|
-
request.respond :bad_request, {'Content-Type' => 'application/json; charset=UTF-8'}
|
95
|
-
request.close
|
96
|
-
else
|
97
|
-
handle_request(request)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
15
|
+
# Run specs in random order to surface order dependencies. If you find an
|
16
|
+
# order dependency and want to debug it, you can fix the order by providing
|
17
|
+
# the seed, which is printed after each run.
|
18
|
+
# --seed 1234
|
19
|
+
config.order = :random
|
101
20
|
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'webrick'
|
2
|
+
require 'atomic'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
require 'support/black_hole'
|
6
|
+
|
7
|
+
class DummyServer < WEBrick::HTTPServer
|
8
|
+
CHUNK_SIZE = 100
|
9
|
+
CONFIG = {
|
10
|
+
:BindAddress => '127.0.0.1',
|
11
|
+
:Port => 5000,
|
12
|
+
:AccessLog => BlackHole,
|
13
|
+
:Logger => BlackHole,
|
14
|
+
:DoNotListen => true,
|
15
|
+
:OutputBufferSize => CHUNK_SIZE
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def initialize(options = {})
|
19
|
+
super(CONFIG)
|
20
|
+
mount('/', SSETestServlet)
|
21
|
+
mount('/error', ErrorServlet)
|
22
|
+
end
|
23
|
+
|
24
|
+
def endpoint
|
25
|
+
"#{scheme}://#{addr}:#{port}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def addr
|
29
|
+
config[:BindAddress]
|
30
|
+
end
|
31
|
+
|
32
|
+
def port
|
33
|
+
config[:Port]
|
34
|
+
end
|
35
|
+
|
36
|
+
def scheme
|
37
|
+
'http'
|
38
|
+
end
|
39
|
+
|
40
|
+
# Simple server that broadcasts Time.now
|
41
|
+
class SSETestServlet < WEBrick::HTTPServlet::AbstractServlet
|
42
|
+
|
43
|
+
def initialize(*args)
|
44
|
+
@event_id = Atomic.new(0)
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def do_GET(req, res)
|
49
|
+
event = String(Array(req.path.match(/\/?(\w+)/i)).pop).to_sym
|
50
|
+
res.content_type = 'text/event-stream; charset=utf-8'
|
51
|
+
res['Cache-Control'] = 'no-cache'
|
52
|
+
r,w = IO.pipe
|
53
|
+
res.body = r
|
54
|
+
res.chunked = true
|
55
|
+
t = Thread.new do
|
56
|
+
begin
|
57
|
+
w << "retry: 1000\n"
|
58
|
+
case event
|
59
|
+
when :continuous_chunks
|
60
|
+
data = "data: %s\ndata: %s\ndata: %s\n\n" % ["o" * CHUNK_SIZE, "m" * CHUNK_SIZE, "g" * CHUNK_SIZE]
|
61
|
+
w << data
|
62
|
+
when :multiple_chunks
|
63
|
+
data = {test: "long_chunk", another_chunk: "a" * CHUNK_SIZE, chunks: "f" * CHUNK_SIZE}.to_json
|
64
|
+
w << "data: #{data}\n\n"
|
65
|
+
when :continuous
|
66
|
+
w << "data: YHOO\ndata: +2\ndata: 10\n\n"
|
67
|
+
when :chunk
|
68
|
+
data = "f" * (CHUNK_SIZE + 25)
|
69
|
+
w << "data: %s\n\n" % data
|
70
|
+
when :ping
|
71
|
+
w << ": ignore this line\n"
|
72
|
+
w << "event: \ndata: pong\n\n" # easy way to know a 'ping' has been sent
|
73
|
+
else
|
74
|
+
42.times do
|
75
|
+
w << "id: %s\nevent: %s\ndata: %s\n\n" % [ @event_id.update { |v| v + 1 },
|
76
|
+
event,
|
77
|
+
Time.now ]
|
78
|
+
end
|
79
|
+
w << "event: %s\ndata: %s\n\n" % %w(end end)
|
80
|
+
end
|
81
|
+
rescue => ex
|
82
|
+
puts $!.inspect, $@ unless ex.is_a?(Errno::EPIPE)
|
83
|
+
ensure
|
84
|
+
w.close
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class ErrorServlet < WEBrick::HTTPServlet::AbstractServlet
|
91
|
+
def do_GET(req, res)
|
92
|
+
res.content_type = 'application/json; charset=utf-8'
|
93
|
+
res.status = 400
|
94
|
+
res.keep_alive = false # true by default
|
95
|
+
res.body = '{"msg": "blop"}'
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: celluloid-eventsource
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leo Correa
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: celluloid-io
|
@@ -45,33 +45,19 @@ dependencies:
|
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: 0.6.0
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
|
-
name:
|
48
|
+
name: atomic
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
|
-
- - "
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: '0'
|
54
|
-
type: :development
|
55
|
-
prerelease: false
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
requirements:
|
58
|
-
- - ">="
|
59
|
-
- !ruby/object:Gem::Version
|
60
|
-
version: '0'
|
61
|
-
- !ruby/object:Gem::Dependency
|
62
|
-
name: reel
|
63
|
-
requirement: !ruby/object:Gem::Requirement
|
64
|
-
requirements:
|
65
|
-
- - ">="
|
51
|
+
- - "~>"
|
66
52
|
- !ruby/object:Gem::Version
|
67
|
-
version:
|
53
|
+
version: 1.1.99
|
68
54
|
type: :development
|
69
55
|
prerelease: false
|
70
56
|
version_requirements: !ruby/object:Gem::Requirement
|
71
57
|
requirements:
|
72
|
-
- - "
|
58
|
+
- - "~>"
|
73
59
|
- !ruby/object:Gem::Version
|
74
|
-
version:
|
60
|
+
version: 1.1.99
|
75
61
|
- !ruby/object:Gem::Dependency
|
76
62
|
name: rspec
|
77
63
|
requirement: !ruby/object:Gem::Requirement
|
@@ -139,7 +125,7 @@ files:
|
|
139
125
|
- ".rspec"
|
140
126
|
- ".travis.yml"
|
141
127
|
- Gemfile
|
142
|
-
- LICENSE
|
128
|
+
- LICENSE
|
143
129
|
- README.md
|
144
130
|
- Rakefile
|
145
131
|
- celluloid-eventsource.gemspec
|
@@ -150,6 +136,8 @@ files:
|
|
150
136
|
- spec/celluloid/eventsource/response_parser_spec.rb
|
151
137
|
- spec/celluloid/eventsource_spec.rb
|
152
138
|
- spec/spec_helper.rb
|
139
|
+
- spec/support/black_hole.rb
|
140
|
+
- spec/support/dummy_server.rb
|
153
141
|
homepage: https://github.com/Tonkpils/celluloid-eventsource
|
154
142
|
licenses:
|
155
143
|
- MIT
|
@@ -178,3 +166,5 @@ test_files:
|
|
178
166
|
- spec/celluloid/eventsource/response_parser_spec.rb
|
179
167
|
- spec/celluloid/eventsource_spec.rb
|
180
168
|
- spec/spec_helper.rb
|
169
|
+
- spec/support/black_hole.rb
|
170
|
+
- spec/support/dummy_server.rb
|