redisse 0.4.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 +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:
|