servent 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/Gemfile.lock +1 -1
- data/README.md +58 -9
- data/{hypotesis/emitter.ru → examples/config.ru} +16 -19
- data/examples/consumer.rb +17 -0
- data/lib/servent.rb +9 -0
- data/lib/servent/event_source.rb +54 -17
- data/lib/servent/version.rb +1 -1
- metadata +5 -4
- data/hypotesis/consumer.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52b302c6ed718cbeaf7717c15db7028f171d12a5
|
4
|
+
data.tar.gz: 5dd81fbbcbd73b0e72b02c6e666feae00f6c3f09
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0dfae2233984931fd793d1229a3a5cd39a3d41f58b81721822f6f3a4b8f49a8e3a8b29284639b0bca29cbfc2394cd82a7ae00b1cebb303345fe33ab10b584b01
|
7
|
+
data.tar.gz: 44d2e1e155d868e4e1af43db8b143fbd8e8ba7d6a585768e5597e32531a987318c0c8044f706a73148b84c9969175b2c2ab0988f3231362795d7a7a1d993d54b
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project
|
4
|
+
will be documented in this file.
|
5
|
+
|
6
|
+
The format is based
|
7
|
+
on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
8
|
+
and this project
|
9
|
+
adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
10
|
+
|
11
|
+
## Unreleased
|
12
|
+
|
13
|
+
## [0.1.0] - 2017-11-20
|
14
|
+
### Added
|
15
|
+
- Consumer for a server-sent events endpoint.
|
16
|
+
- First fully functional example.
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Servent
|
2
2
|
|
3
|
-
[<img src="https://travis-ci.
|
3
|
+
[<img src="https://travis-ci.org/mistersourcerer/servent.svg?branch=master" />](https://travis-ci.org/mistersourcerer/servent)
|
4
4
|
|
5
5
|
Ruby _Server-Sent Events_ client.
|
6
6
|
A _EventSource_ Ruby implementation based on the [W3C specification](https://www.w3.org/TR/eventsource).
|
@@ -29,17 +29,10 @@ gem 'servent'
|
|
29
29
|
# id: 42
|
30
30
|
# data: Omg! Hello World.
|
31
31
|
|
32
|
-
events = Queue.new
|
33
|
-
|
34
32
|
event_source = Servent::EventSource.new("http://example.org/event-source")
|
35
33
|
event_source.on_message do |message|
|
36
|
-
events.push message
|
37
|
-
end
|
38
|
-
event_source.start
|
39
|
-
|
40
|
-
while (event = events.pop)
|
41
34
|
puts "Event type: #{event.type}"
|
42
|
-
puts "Event body: #{event.
|
35
|
+
puts "Event body: #{event.data}"
|
43
36
|
|
44
37
|
# Will print:
|
45
38
|
#
|
@@ -49,8 +42,64 @@ while (event = events.pop)
|
|
49
42
|
# ```
|
50
43
|
# And wait for the next event to arrive.
|
51
44
|
end
|
45
|
+
|
46
|
+
# join the internal event source thread
|
47
|
+
# so we can receive event until it terminates:
|
48
|
+
event_source.listen
|
52
49
|
```
|
53
50
|
|
51
|
+
## More examples
|
52
|
+
|
53
|
+
There is directory `examples` in this project
|
54
|
+
with a _WEBrick_ server
|
55
|
+
and also a `EventSource` consumer.
|
56
|
+
|
57
|
+
### How to run the example
|
58
|
+
|
59
|
+
#### TL;DR
|
60
|
+
|
61
|
+
# on one terminal:
|
62
|
+
$ rackup
|
63
|
+
|
64
|
+
# on a second one:
|
65
|
+
$ ruby consumer.rb
|
66
|
+
|
67
|
+
# on yeat another one
|
68
|
+
$ curl http://localhost:9292/broadcast
|
69
|
+
|
70
|
+
# and to make the consumer close itself:
|
71
|
+
$ curl http://localhost:9292/enough
|
72
|
+
|
73
|
+
#### More detailed version
|
74
|
+
|
75
|
+
if you are inside the directory
|
76
|
+
(or copied the files in the example dir to your own)
|
77
|
+
you can run a _rackup_:
|
78
|
+
|
79
|
+
$ rackup
|
80
|
+
|
81
|
+
The server will run on port _9292_
|
82
|
+
and it has 3 endpoints:
|
83
|
+
|
84
|
+
/
|
85
|
+
/broadcast
|
86
|
+
/enough
|
87
|
+
|
88
|
+
The root (`/`) is intended to consumers
|
89
|
+
and the one in the example
|
90
|
+
starts listening to that endpoint like this:
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
event_source = Servent::EventSource.new("http://localhost:9292/")
|
94
|
+
# ...
|
95
|
+
event_source.listen
|
96
|
+
```
|
97
|
+
|
98
|
+
If you want to test multiple messages arriving
|
99
|
+
you can use the `repeat` parameters in the request:
|
100
|
+
|
101
|
+
$ curl http://localhost/broadcast?repeat=3
|
102
|
+
|
54
103
|
## Development
|
55
104
|
|
56
105
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -8,15 +8,12 @@ class SSEEvent
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def event
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
data:
|
15
|
-
|
16
|
-
|
17
|
-
data: }
|
18
|
-
|
19
|
-
)
|
11
|
+
<<~EVENT
|
12
|
+
event: #{@type}
|
13
|
+
id: #{@id}
|
14
|
+
data: #{@text}
|
15
|
+
|
16
|
+
EVENT
|
20
17
|
end
|
21
18
|
end
|
22
19
|
|
@@ -33,20 +30,20 @@ server.mount_proc "/" do |_, res|
|
|
33
30
|
res.chunked = true
|
34
31
|
end
|
35
32
|
|
36
|
-
server.mount_proc "/
|
37
|
-
|
38
|
-
|
33
|
+
server.mount_proc "/broadcast" do |req, _|
|
34
|
+
repeat = req.query["repeat"].to_i
|
35
|
+
repeat = 1 if repeat <= 0 || repeat.nil?
|
39
36
|
|
40
|
-
|
41
|
-
|
42
|
-
|
37
|
+
clients.each do |client|
|
38
|
+
repeat.times do |counter|
|
39
|
+
client << SSEEvent.new("streaming #{counter}!").event
|
40
|
+
end
|
41
|
+
end
|
43
42
|
end
|
44
43
|
|
45
|
-
server.mount_proc "/
|
44
|
+
server.mount_proc "/enough" do |_, _|
|
46
45
|
clients.each do |client|
|
47
|
-
|
48
|
-
client << SSEEvent.new("streaming!").event
|
49
|
-
end
|
46
|
+
client << SSEEvent.new("close").event
|
50
47
|
end
|
51
48
|
end
|
52
49
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "net/http"
|
2
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
3
|
+
require "servent"
|
4
|
+
require "pp"
|
5
|
+
|
6
|
+
event_source = Servent::EventSource.new("http://localhost:9292/")
|
7
|
+
event_source.on_message do |event|
|
8
|
+
if event.data == "close"
|
9
|
+
puts "going to close this client"
|
10
|
+
return event_source.close
|
11
|
+
end
|
12
|
+
|
13
|
+
puts "received: "+ event.data
|
14
|
+
end
|
15
|
+
event_source.listen
|
16
|
+
|
17
|
+
puts "bye"
|
data/lib/servent.rb
CHANGED
@@ -8,4 +8,13 @@ module Servent
|
|
8
8
|
CONNECTING = 0
|
9
9
|
OPEN = 1
|
10
10
|
CLOSED = 2
|
11
|
+
|
12
|
+
REDIRECT_STATUSES = [301, 302, 303, 307]
|
13
|
+
RECONNECTION_STATUSES = [500, 502, 503, 504]
|
14
|
+
AUTHORIZATION_STATUSES = [305, 401, 407]
|
15
|
+
|
16
|
+
KNOWN_STATUSES = [200] +
|
17
|
+
REDIRECT_STATUSES +
|
18
|
+
RECONNECTION_STATUSES +
|
19
|
+
AUTHORIZATION_STATUSES
|
11
20
|
end
|
data/lib/servent/event_source.rb
CHANGED
@@ -4,7 +4,9 @@ require "net/http"
|
|
4
4
|
|
5
5
|
module Servent
|
6
6
|
class EventSource
|
7
|
-
|
7
|
+
DEFAULT_HEADERS = { "Accept" => "text/event-stream" }
|
8
|
+
|
9
|
+
attr_reader :ready_state, :uri
|
8
10
|
|
9
11
|
def initialize(url, net_http_options: { read_timeout: 600 })
|
10
12
|
@uri = URI(url)
|
@@ -20,18 +22,29 @@ module Servent
|
|
20
22
|
end
|
21
23
|
|
22
24
|
def start(http_starter = Net::HTTP)
|
25
|
+
@http_starter ||= http_starter
|
23
26
|
params = HTTPStartParams.new(@uri, @proxy_config, @net_http_options)
|
24
27
|
|
25
|
-
Thread.new {
|
26
|
-
http_starter.start(*params.parameterize) do |http|
|
28
|
+
@thread = Thread.new {
|
29
|
+
@http_starter.start(*params.parameterize) do |http|
|
27
30
|
get = Net::HTTP::Get.new @uri
|
28
|
-
|
31
|
+
DEFAULT_HEADERS.each { |header, value| get[header] = value }
|
29
32
|
yield http, get if block_given?
|
33
|
+
|
30
34
|
perform_request http, get
|
31
35
|
end
|
32
36
|
}
|
33
37
|
end
|
34
38
|
|
39
|
+
def listen(http_starter = Net::HTTP)
|
40
|
+
start(http_starter).join
|
41
|
+
end
|
42
|
+
|
43
|
+
def close
|
44
|
+
@ready_state = Servent::CLOSED
|
45
|
+
@thread.kill unless @thread.nil?
|
46
|
+
end
|
47
|
+
|
35
48
|
def on_open(&open_block)
|
36
49
|
@open_blocks << open_block
|
37
50
|
end
|
@@ -46,30 +59,54 @@ module Servent
|
|
46
59
|
|
47
60
|
private
|
48
61
|
|
49
|
-
def headers
|
50
|
-
{ "Accept" => "text/event-stream" }
|
51
|
-
end
|
52
|
-
|
53
62
|
def perform_request(http, type)
|
54
63
|
http.request type do |response|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
@error_blocks.each { |block| block.call response, :wrong_mime_type }
|
59
|
-
return
|
60
|
-
end
|
64
|
+
return fail_connection response if should_fail? response
|
65
|
+
return schedule_reconnection if should_reconnect? response
|
66
|
+
store_new_parmanent_url response
|
61
67
|
|
62
|
-
|
68
|
+
open_connection response
|
63
69
|
end
|
64
70
|
end
|
65
71
|
|
66
|
-
def
|
72
|
+
def open_connection(response)
|
67
73
|
@ready_state = Servent::OPEN
|
68
74
|
@open_blocks.each { |block| block.call response }
|
69
75
|
response.read_body do |chunk|
|
70
|
-
|
76
|
+
# FIXME: use the same stream object to parse
|
77
|
+
# different chunks.
|
78
|
+
stream = Stream.new chunk
|
79
|
+
events = stream.parse
|
80
|
+
events.each do |event|
|
81
|
+
@message_blocks.each { |block| block.call event }
|
82
|
+
end
|
71
83
|
end
|
72
84
|
end
|
85
|
+
|
86
|
+
def should_fail?(response)
|
87
|
+
return false if Servent::REDIRECT_STATUSES.include?(response.code.to_i)
|
88
|
+
(response["Content-Type"] != "text/event-stream") ||
|
89
|
+
!Servent::KNOWN_STATUSES.include?(response.code.to_i)
|
90
|
+
end
|
91
|
+
|
92
|
+
def fail_connection(response)
|
93
|
+
@ready_state = Servent::CLOSED
|
94
|
+
@error_blocks.each { |block| block.call response, :wrong_mime_type }
|
95
|
+
end
|
96
|
+
|
97
|
+
def should_reconnect?(response)
|
98
|
+
Servent::RECONNECTION_STATUSES.include? response.code.to_i
|
99
|
+
end
|
100
|
+
|
101
|
+
def schedule_reconnection
|
102
|
+
start
|
103
|
+
end
|
104
|
+
|
105
|
+
def store_new_parmanent_url(response)
|
106
|
+
return unless response.code.to_i == 301
|
107
|
+
@original_uri = @uri
|
108
|
+
@uri = URI(response["Location"])
|
109
|
+
end
|
73
110
|
end
|
74
111
|
|
75
112
|
class ProxyConfig
|
data/lib/servent/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: servent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ricardo Valeriano
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-11-
|
11
|
+
date: 2017-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -107,6 +107,7 @@ files:
|
|
107
107
|
- ".rubocop.yml"
|
108
108
|
- ".ruby-version"
|
109
109
|
- ".travis.yml"
|
110
|
+
- CHANGELOG.md
|
110
111
|
- CODE_OF_CONDUCT.md
|
111
112
|
- Gemfile
|
112
113
|
- Gemfile.lock
|
@@ -115,8 +116,8 @@ files:
|
|
115
116
|
- Rakefile
|
116
117
|
- bin/console
|
117
118
|
- bin/setup
|
118
|
-
-
|
119
|
-
-
|
119
|
+
- examples/config.ru
|
120
|
+
- examples/consumer.rb
|
120
121
|
- lib/servent.rb
|
121
122
|
- lib/servent/event.rb
|
122
123
|
- lib/servent/event_source.rb
|
data/hypotesis/consumer.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
require "net/http"
|
2
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
3
|
-
require "servent"
|
4
|
-
require "pp"
|
5
|
-
|
6
|
-
#q = Queue.new
|
7
|
-
#
|
8
|
-
#trap :INT do
|
9
|
-
# q << nil
|
10
|
-
#end
|
11
|
-
|
12
|
-
#Thread.new do
|
13
|
-
# uri = URI("http://localhost:9292/omg")
|
14
|
-
#
|
15
|
-
# Net::HTTP.start(uri.host, uri.port, read_timeout: 600) do |http|
|
16
|
-
# get = Net::HTTP::Get.new uri
|
17
|
-
# get["Accept"] = "text/event-stream"
|
18
|
-
# http.request(get) do |response|
|
19
|
-
# response.read_body do |chunk|
|
20
|
-
# q.push chunk
|
21
|
-
# end
|
22
|
-
# end
|
23
|
-
# q.push nil
|
24
|
-
# end
|
25
|
-
#end
|
26
|
-
|
27
|
-
event_source = Servent::EventSource.new("http://localhost:9292/omg")
|
28
|
-
event_source.on_message do |message|
|
29
|
-
#q.push message
|
30
|
-
pp message
|
31
|
-
end
|
32
|
-
event_source.start.join
|
33
|
-
|
34
|
-
#while (chunk = q.pop)
|
35
|
-
# puts chunk
|
36
|
-
#end
|
37
|
-
|
38
|
-
puts "bye"
|