celluloid-eventsource 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Code Climate](https://codeclimate.com/github/Tonkpils/celluloid-eventsource.png)](https://codeclimate.com/github/Tonkpils/celluloid-eventsource)
|
5
5
|
[![Build Status](https://travis-ci.org/Tonkpils/celluloid-eventsource.svg?branch=master)](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
|