servent 0.0.1 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: afcf04d7de0fbed51a568743c7dad4af879042d5
4
- data.tar.gz: 95b1d000e7171f9df857952d6e4698b6073aca09
3
+ metadata.gz: 52b302c6ed718cbeaf7717c15db7028f171d12a5
4
+ data.tar.gz: 5dd81fbbcbd73b0e72b02c6e666feae00f6c3f09
5
5
  SHA512:
6
- metadata.gz: ca999d77e3c6f6236091167bee4e8f815c05af640ff16a990eee5d237187f31f33207811d72bae5b26c68e38e70d63b734fb3bf6386cd68a916e4e756cf90e2a
7
- data.tar.gz: 1d0b871339cb5f9f281de6522a5bf699f7f2489d33b2feb968c94db512de8b3ca43c45b08326c1c0f079921da7f63a6dfd2f821abe9831ad95e27c8cc09e8f36
6
+ metadata.gz: 0dfae2233984931fd793d1229a3a5cd39a3d41f58b81721822f6f3a4b8f49a8e3a8b29284639b0bca29cbfc2394cd82a7ae00b1cebb303345fe33ab10b584b01
7
+ data.tar.gz: 44d2e1e155d868e4e1af43db8b143fbd8e8ba7d6a585768e5597e32531a987318c0c8044f706a73148b84c9969175b2c2ab0988f3231362795d7a7a1d993d54b
@@ -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.
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- servent (0.0.1)
4
+ servent (0.1.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Servent
2
2
 
3
- [<img src="https://travis-ci.com/mistersourcerer/servent.svg?token=aMwiRm3UQ11zdWwMxGgZ&branch=master" />](https://travis-ci.com/mistersourcerer/servent)
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.body}"
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
- %(event: #{@type}
12
- id: #{@id}
13
- data: {
14
- data: "id": "#{@id}",
15
- data: "type": "#{@type}",
16
- data: "text": "#{@text}"
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 "/omg" do |_, res|
37
- r, w = IO.pipe
38
- clients << w
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
- res.content_type = "text/event-stream"
41
- res.body = r
42
- res.chunked = true
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 "/broadcast" do |_, _|
44
+ server.mount_proc "/enough" do |_, _|
46
45
  clients.each do |client|
47
- Thread.new do
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"
@@ -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
@@ -4,7 +4,9 @@ require "net/http"
4
4
 
5
5
  module Servent
6
6
  class EventSource
7
- attr_reader :ready_state
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
- headers.each { |header, value| get[header] = value }
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
- # FIXME: response CAN have more than one mime type
56
- unless response["Content-Type"] == "text/event-stream"
57
- @ready_state = Servent::CLOSED
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
- handle_response response
68
+ open_connection response
63
69
  end
64
70
  end
65
71
 
66
- def handle_response(response)
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
- @message_blocks.each { |block| block.call chunk }
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
@@ -1,3 +1,3 @@
1
1
  module Servent
2
- VERSION = "0.0.1".freeze
2
+ VERSION = "0.1.0".freeze
3
3
  end
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.1
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-16 00:00:00.000000000 Z
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
- - hypotesis/consumer.rb
119
- - hypotesis/emitter.ru
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
@@ -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"