redisse 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +152 -0
- data/Rakefile +28 -0
- data/bin/redisse +4 -0
- data/example/.env +3 -0
- data/example/README.md +37 -0
- data/example/bin/publish +15 -0
- data/example/bin/redis +13 -0
- data/example/config.ru +40 -0
- data/example/nginx.conf +29 -0
- data/example/public/index.html +46 -0
- data/example/spec/app_spec.rb +39 -0
- data/lib/redisse.rb +181 -0
- data/lib/redisse/configuration.rb +47 -0
- data/lib/redisse/publisher.rb +70 -0
- data/lib/redisse/redirect_endpoint.rb +45 -0
- data/lib/redisse/server.rb +205 -0
- data/lib/redisse/server/redis.rb +39 -0
- data/lib/redisse/server/responses.rb +22 -0
- data/lib/redisse/server/stats.rb +10 -0
- data/lib/redisse/server_sent_events.rb +17 -0
- data/lib/redisse/version.rb +3 -0
- data/redisse.gemspec +29 -0
- data/spec/example_spec.rb +200 -0
- data/spec/publisher_spec.rb +81 -0
- data/spec/redirect_endpoint_spec.rb +98 -0
- data/spec/server_sent_events_spec.rb +53 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/spec_system_helper.rb +204 -0
- metadata +214 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative '../lib/redisse/server_sent_events'
|
3
|
+
|
4
|
+
describe Redisse::ServerSentEvents do
|
5
|
+
|
6
|
+
include described_class
|
7
|
+
|
8
|
+
it "outputs a basic Server-Sent Event" do
|
9
|
+
expect(server_sent_event('foobar')).
|
10
|
+
to match(/\Adata: ?foobar\n\n\z/)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "outputs the empty string with nil data" do
|
14
|
+
expect(server_sent_event(nil)).
|
15
|
+
to match(/\Adata: ?\n\n\z/)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "uses data as a String" do
|
19
|
+
object = Object.new
|
20
|
+
def object.to_s
|
21
|
+
"data"
|
22
|
+
end
|
23
|
+
expect(server_sent_event(object)).
|
24
|
+
to match(/\Adata: ?data\n\n\z/)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "outputs a Server-Sent Event with type" do
|
28
|
+
event = server_sent_event('foo', type: :event_type_foo)
|
29
|
+
expect(event).
|
30
|
+
to match(/^event: ?event_type_foo$/)
|
31
|
+
expect(event).to end_with("\n\n")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "outputs a Server-Sent Event with id" do
|
35
|
+
event = server_sent_event('foo', id: 12)
|
36
|
+
expect(event).
|
37
|
+
to match(/^id: ?12$/)
|
38
|
+
expect(event).to end_with("\n\n")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "outputs a Server-Sent Event with retry" do
|
42
|
+
event = server_sent_event('foo', retry: 500)
|
43
|
+
expect(event).
|
44
|
+
to match(/^retry: ?500$/)
|
45
|
+
expect(event).to end_with("\n\n")
|
46
|
+
end
|
47
|
+
|
48
|
+
it "outputs a Server-Sent Event with multiple data lines if necessary" do
|
49
|
+
expect(server_sent_event("hello\nworld\n!")).
|
50
|
+
to match(/\Adata: ?hello\ndata: ?world\ndata: ?!\n\n\z/)
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,204 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'socket'
|
3
|
+
require 'net/http'
|
4
|
+
require 'strscan'
|
5
|
+
|
6
|
+
shared_context "system" do
|
7
|
+
# Classes are not accessible from before, after hooks
|
8
|
+
# methods are
|
9
|
+
def run_server(*args)
|
10
|
+
Server.new(*args)
|
11
|
+
end
|
12
|
+
|
13
|
+
Event = Struct.new :data, :type, :id
|
14
|
+
|
15
|
+
class EventReader
|
16
|
+
def self.open(*args, &block)
|
17
|
+
reader = new(*args)
|
18
|
+
if block_given?
|
19
|
+
yield_and_close(reader, &block)
|
20
|
+
else
|
21
|
+
reader
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
private :new
|
27
|
+
|
28
|
+
private
|
29
|
+
def yield_and_close(reader)
|
30
|
+
yield reader
|
31
|
+
ensure
|
32
|
+
reader.close
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(uri, last_event_id = nil)
|
37
|
+
@uri = URI(uri)
|
38
|
+
@last_event_id = last_event_id
|
39
|
+
@queue = Queue.new
|
40
|
+
@thread = Thread.new { connect }
|
41
|
+
@thread.abort_on_exception = true
|
42
|
+
event = @queue.pop
|
43
|
+
fail ':connected expected' unless event == :connected
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :response
|
47
|
+
|
48
|
+
CloseConnection = Class.new StandardError
|
49
|
+
def close
|
50
|
+
@closed_at = Time.now.to_f
|
51
|
+
@thread.raise CloseConnection
|
52
|
+
end
|
53
|
+
|
54
|
+
def connected?
|
55
|
+
return Net::HTTPOK === response && !@closed
|
56
|
+
end
|
57
|
+
|
58
|
+
# #each is blocking while the connection persists
|
59
|
+
# call #close in the given block to make #each return
|
60
|
+
def each
|
61
|
+
return enum_for(:each) unless block_given?
|
62
|
+
return unless connected?
|
63
|
+
while (event = @queue.pop) != :over
|
64
|
+
yield event
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def full_stream
|
69
|
+
raise "No stream: response was #@response" unless @scanner
|
70
|
+
@scanner.full_stream
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def connect
|
76
|
+
Net::HTTP.start(@uri.host, @uri.port) do |http|
|
77
|
+
response_received = false
|
78
|
+
headers = { 'Accept' => 'text/event-stream' }
|
79
|
+
headers['Last-Event-Id'] = @last_event_id.to_s if @last_event_id
|
80
|
+
http.request_get @uri, headers do |response|
|
81
|
+
# Fix a bug? in Net::HTTP where if the connection times out,
|
82
|
+
# the block runs again
|
83
|
+
return if response_received
|
84
|
+
response_received = true
|
85
|
+
self.response = response
|
86
|
+
read_events if connected?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
rescue CloseConnection
|
90
|
+
ensure
|
91
|
+
@closed = true
|
92
|
+
@queue << :over
|
93
|
+
end
|
94
|
+
|
95
|
+
def response=(response)
|
96
|
+
@response = response
|
97
|
+
@scanner = EventScanner.new { |event| @queue << event }
|
98
|
+
@queue << :connected
|
99
|
+
end
|
100
|
+
|
101
|
+
def read_events
|
102
|
+
@response.read_body do |segment|
|
103
|
+
@scanner << segment
|
104
|
+
break if @closed
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
class EventScanner
|
110
|
+
def initialize(&block)
|
111
|
+
@body = StringScanner.new ""
|
112
|
+
@event = Event.new ""
|
113
|
+
@block = block
|
114
|
+
end
|
115
|
+
|
116
|
+
def <<(segment)
|
117
|
+
@body << segment
|
118
|
+
while lines = @body.scan_until(/\n\n/)
|
119
|
+
lines.split("\n").each do |line|
|
120
|
+
field, value = line.split(/: ?/, 2)
|
121
|
+
next if field.empty?
|
122
|
+
case field
|
123
|
+
when "id"
|
124
|
+
@event.id = value
|
125
|
+
when "event"
|
126
|
+
@event.type = value
|
127
|
+
when "data"
|
128
|
+
@event.data << value if value
|
129
|
+
@event.data << "\n"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
@event.data.chomp!
|
133
|
+
@block.call @event
|
134
|
+
@event = Event.new ""
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def full_stream
|
139
|
+
@body.string.dup
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class Server
|
144
|
+
def initialize(command, port, pidfile: nil)
|
145
|
+
@command = command
|
146
|
+
@port = port
|
147
|
+
@pidfile = pidfile
|
148
|
+
check_tcp
|
149
|
+
start
|
150
|
+
end
|
151
|
+
|
152
|
+
def start
|
153
|
+
@pid = Process.spawn("#@command", %i(in out err) => :close)
|
154
|
+
if @pidfile
|
155
|
+
wait
|
156
|
+
@pid = nil
|
157
|
+
sleep(0.1) until File.exist?(@pidfile)
|
158
|
+
@pid = File.read(@pidfile).to_i if File.exist?(@pidfile)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def wait
|
163
|
+
if @pidfile
|
164
|
+
sleep(0.1) while File.exists?(@pidfile)
|
165
|
+
else
|
166
|
+
Process.wait(@pid)
|
167
|
+
end
|
168
|
+
rescue Errno::ESRCH
|
169
|
+
end
|
170
|
+
|
171
|
+
def stop
|
172
|
+
return unless @pid
|
173
|
+
Process.kill("TERM", @pid)
|
174
|
+
wait
|
175
|
+
ensure
|
176
|
+
@pid = nil
|
177
|
+
end
|
178
|
+
|
179
|
+
def wait_tcp
|
180
|
+
Timeout.timeout(5) do
|
181
|
+
_wait_tcp
|
182
|
+
end
|
183
|
+
rescue Timeout::Error
|
184
|
+
fail "Could not connect to server at localhost:#@port"
|
185
|
+
end
|
186
|
+
|
187
|
+
def _wait_tcp
|
188
|
+
connection = TCPSocket.new 'localhost', @port
|
189
|
+
self
|
190
|
+
rescue
|
191
|
+
retry
|
192
|
+
ensure
|
193
|
+
connection.close if connection
|
194
|
+
end
|
195
|
+
|
196
|
+
def check_tcp
|
197
|
+
connection = TCPSocket.new 'localhost', @port
|
198
|
+
fail "port #@port already used"
|
199
|
+
rescue Errno::ECONNREFUSED
|
200
|
+
ensure
|
201
|
+
connection.close if connection
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
metadata
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redisse
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "Étienne Barrié"
|
8
|
+
- Julien Blanchard
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-06-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.3'
|
21
|
+
type: :development
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.3'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: rake
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rspec
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
type: :development
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: yard-tomdoc
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rack-test
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: dotenv
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: goliath
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :runtime
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: em-hiredis
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :runtime
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: redis
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
type: :runtime
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
description:
|
141
|
+
email:
|
142
|
+
- etienne.barrie@gmail.com
|
143
|
+
- julien@sideburns.eu
|
144
|
+
executables:
|
145
|
+
- redisse
|
146
|
+
extensions: []
|
147
|
+
extra_rdoc_files: []
|
148
|
+
files:
|
149
|
+
- ".gitignore"
|
150
|
+
- ".rspec"
|
151
|
+
- ".ruby-version"
|
152
|
+
- ".travis.yml"
|
153
|
+
- Gemfile
|
154
|
+
- LICENSE.txt
|
155
|
+
- README.md
|
156
|
+
- Rakefile
|
157
|
+
- bin/redisse
|
158
|
+
- example/.env
|
159
|
+
- example/README.md
|
160
|
+
- example/bin/publish
|
161
|
+
- example/bin/redis
|
162
|
+
- example/config.ru
|
163
|
+
- example/nginx.conf
|
164
|
+
- example/public/index.html
|
165
|
+
- example/spec/app_spec.rb
|
166
|
+
- lib/redisse.rb
|
167
|
+
- lib/redisse/configuration.rb
|
168
|
+
- lib/redisse/publisher.rb
|
169
|
+
- lib/redisse/redirect_endpoint.rb
|
170
|
+
- lib/redisse/server.rb
|
171
|
+
- lib/redisse/server/redis.rb
|
172
|
+
- lib/redisse/server/responses.rb
|
173
|
+
- lib/redisse/server/stats.rb
|
174
|
+
- lib/redisse/server_sent_events.rb
|
175
|
+
- lib/redisse/version.rb
|
176
|
+
- redisse.gemspec
|
177
|
+
- spec/example_spec.rb
|
178
|
+
- spec/publisher_spec.rb
|
179
|
+
- spec/redirect_endpoint_spec.rb
|
180
|
+
- spec/server_sent_events_spec.rb
|
181
|
+
- spec/spec_helper.rb
|
182
|
+
- spec/spec_system_helper.rb
|
183
|
+
homepage: https://github.com/tigerlily/redisse
|
184
|
+
licenses:
|
185
|
+
- MIT
|
186
|
+
metadata: {}
|
187
|
+
post_install_message:
|
188
|
+
rdoc_options: []
|
189
|
+
require_paths:
|
190
|
+
- lib
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
192
|
+
requirements:
|
193
|
+
- - ">="
|
194
|
+
- !ruby/object:Gem::Version
|
195
|
+
version: '0'
|
196
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
197
|
+
requirements:
|
198
|
+
- - ">="
|
199
|
+
- !ruby/object:Gem::Version
|
200
|
+
version: '0'
|
201
|
+
requirements: []
|
202
|
+
rubyforge_project:
|
203
|
+
rubygems_version: 2.2.2
|
204
|
+
signing_key:
|
205
|
+
specification_version: 4
|
206
|
+
summary: Server-Sent Events via Redis
|
207
|
+
test_files:
|
208
|
+
- spec/example_spec.rb
|
209
|
+
- spec/publisher_spec.rb
|
210
|
+
- spec/redirect_endpoint_spec.rb
|
211
|
+
- spec/server_sent_events_spec.rb
|
212
|
+
- spec/spec_helper.rb
|
213
|
+
- spec/spec_system_helper.rb
|
214
|
+
has_rdoc:
|