roda-sse 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81bbd096f5155da3f8593b8e3d37771daedd543a9cf4816d5bab36f3192bb203
4
- data.tar.gz: 88366dcfba9fb10fcf5a810c540c3ae5b77c9f52989cdea5c03ea1b0a9aa9641
3
+ metadata.gz: 5e98e9c2879bb55ccadf0400d243d97ecff39fa4d6535a044cae739e3410878c
4
+ data.tar.gz: aa3d255a8cb4ecab1ddb9b5d71c916f6907c36529a934c38467ca57b4e3cb1a7
5
5
  SHA512:
6
- metadata.gz: 8375dd84ecfefe94dafb591d5edfb1e22a3413aa0f574b7c7a965b1adcf7ef62cd70397fb133a8f0e2468183edae44f82208742d357f45a43a69e53d9f352e6c
7
- data.tar.gz: 9bf26ed27a75bb355f1468baa668e0c3b569b9313d1468b60ac9aa6bbe7a2c142592011bacb7f931232e18a2bb90c932011b375a6b73e7d654a942ae0bb57210
6
+ metadata.gz: dee16a19a420769ed068a7bf977b4ef3a05b1d5425e1f9bf4ab2899697e0e13ac2255dc96af84178cb18ada3d6e85645ce91b5b7b4d91508213ecb422fc9a08b
7
+ data.tar.gz: 946654c542c416ae617e641e5dff7b3cb16d9fa8c5873946db293f5ff98b1d6043fdf9e71c6caa16acdf9f655affe0a14b38cf10fa89edeea0c774ebe1af02c8
data/README.md CHANGED
@@ -30,7 +30,5 @@ In your routing block, you can then use `r.sse` to stream with the correct heade
30
30
  r.sse do |stream|
31
31
  stream << "data: hello\n\n"
32
32
  stream << "data: world\n\n"
33
- ensure
34
- stream.close
35
33
  end
36
34
  ```
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'async'
4
+ require 'async/barrier'
5
+
3
6
  class Roda
4
7
  module RodaPlugins
5
8
  # Example:
@@ -11,22 +14,69 @@ class Roda
11
14
  # # GET /
12
15
  # r.sse do |stream|
13
16
  # stream.write "data: hola\n\n"
14
- # ensure
15
- # stream.close
16
17
  # end
17
18
  # end
18
19
  # end
19
20
  module SSE
21
+ class Stream
22
+ def initialize(&block)
23
+ @block = block
24
+ @barrier = Async::Barrier.new
25
+ end
26
+
27
+ def write(message)
28
+ data = message.to_s
29
+
30
+ @barrier.async do
31
+ @stream.write(data)
32
+ rescue Errno::ECONNRESET, Errno::EPIPE
33
+ @barrier.close
34
+ end
35
+
36
+ data.bytesize
37
+ end
38
+
39
+ def <<(message)
40
+ write(message)
41
+ self
42
+ end
43
+
44
+ def call(stream)
45
+ Sync do
46
+ @stream = stream
47
+ @block.call(stream)
48
+ ensure
49
+ close
50
+ end
51
+ end
52
+
53
+ def close
54
+ return if @closed
55
+
56
+ @barrier.stop
57
+ @stream&.close
58
+ @closed = true
59
+ end
60
+
61
+ def closed? = @closed
62
+ end
63
+
20
64
  module RequestMethods
21
65
  def sse(&block)
22
- response['Content-Type'] = 'text/event-stream'
23
- response['Cache-Control'] = 'no-cache'
66
+ get do
67
+ response['Content-Type'] = 'text/event-stream'
68
+ response['Cache-Control'] = 'no-cache'
24
69
 
25
- always do
26
- halt response.finish_with_body(block)
70
+ halt response.finish_with_body(Stream.new(&block))
27
71
  end
28
72
  end
29
73
  end
74
+
75
+ module InstanceMethods
76
+ def last_event_id
77
+ env['HTTP_LAST_EVENT_ID']
78
+ end
79
+ end
30
80
  end
31
81
 
32
82
  register_plugin(:sse, SSE)
@@ -12,9 +12,8 @@ class App < Roda
12
12
  route do |r|
13
13
  r.root do
14
14
  r.sse do |stream|
15
+ last_event_id
15
16
  stream.write "data: hola\n\n"
16
- ensure
17
- stream.close
18
17
  end
19
18
  end
20
19
  end
@@ -23,34 +22,66 @@ end
23
22
  def app = App.freeze.app
24
23
 
25
24
  describe 'roda-sse plugin' do
26
- include Rack::Test::Methods
27
-
28
25
  prove_it!
29
26
 
30
- it 'responds 200 OK' do
31
- get '/'
27
+ describe 'roda app' do
28
+ include Rack::Test::Methods
32
29
 
33
- assert last_response.ok?
34
- end
30
+ it 'responds 200 OK' do
31
+ get '/'
32
+
33
+ assert last_response.ok?
34
+ end
35
+
36
+ it 'does not respond to PUT' do
37
+ post '/'
35
38
 
36
- it 'has SSE headers' do
37
- get '/'
39
+ refute last_response.ok?
40
+ end
41
+
42
+ it 'has SSE headers' do
43
+ get '/'
44
+
45
+ headers = {'content-type' => 'text/event-stream', 'cache-control' => 'no-cache'}
46
+ assert_equal headers, last_response.headers
47
+ end
38
48
 
39
- headers = {'content-type' => 'text/event-stream', 'cache-control' => 'no-cache'}
40
- assert_equal headers, last_response.headers
49
+ it 'streams the body' do
50
+ get '/'
51
+
52
+ stream = Minitest::Mock.new
53
+ stream.expect(:write, nil, ["data: hola\n\n"])
54
+ stream.expect(:close, nil)
55
+ response_body = last_response.instance_variable_get(:@body)
56
+ assert_instance_of Roda::RodaPlugins::SSE::Stream, response_body
57
+
58
+ response_body.call(stream)
59
+
60
+ stream.verify
61
+ end
41
62
  end
42
63
 
43
- it 'streams the body' do
44
- get '/'
64
+ describe 'stream class' do
65
+ before do
66
+ @stream = Roda::RodaPlugins::SSE::Stream.new do |stream|
67
+ stream << 42
68
+ stream.write(43)
69
+ end
70
+ end
45
71
 
46
- stream = Minitest::Mock.new
47
- stream.expect(:write, nil, ["data: hola\n\n"])
48
- stream.expect(:close, nil)
49
- response_body = last_response.instance_variable_get(:@body)
50
- assert_instance_of Proc, response_body
72
+ it 'opens' do
73
+ refute @stream.closed?
74
+ end
51
75
 
52
- response_body.call(stream)
76
+ it 'streams and closes' do
77
+ stream = Minitest::Mock.new
78
+ stream.expect(:<<, nil, [42])
79
+ stream.expect(:write, nil, [43])
80
+ stream.expect(:close, nil)
81
+ @stream.call(stream)
82
+ assert_instance_of Roda::RodaPlugins::SSE::Stream, @stream
53
83
 
54
- stream.verify
84
+ stream.verify
85
+ end
55
86
  end
56
87
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda-sse
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shannon Skipper
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2024-10-18 00:00:00.000000000 Z
10
+ date: 2024-11-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: roda
@@ -15,14 +15,28 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '3.0'
18
+ version: '3.85'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '3.0'
25
+ version: '3.85'
26
+ - !ruby/object:Gem::Dependency
27
+ name: async
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.18'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.18'
26
40
  description: The roda-sse gem integrates simple SSE streaming into the roda web toolkit.
27
41
  email: shannonskipper@gmail.com
28
42
  executables: []