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.
@@ -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
@@ -0,0 +1,7 @@
1
+ RSpec.configure do |config|
2
+ config.mock_with :rspec
3
+ config.expect_with :rspec
4
+ config.expect_with :rspec do |c|
5
+ c.syntax = :expect
6
+ end
7
+ end
@@ -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: