roda-sse 0.1.0 → 0.3.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
  SHA256:
3
- metadata.gz: 81bbd096f5155da3f8593b8e3d37771daedd543a9cf4816d5bab36f3192bb203
4
- data.tar.gz: 88366dcfba9fb10fcf5a810c540c3ae5b77c9f52989cdea5c03ea1b0a9aa9641
3
+ metadata.gz: e63c433081f53c50061c380222d40ba9ea30bedd06b5d95982c3dcf9bbd15fa3
4
+ data.tar.gz: 5bd7d45309f2903e6a953d59b9c02e58c6e965d758859f990ba435eed1372f23
5
5
  SHA512:
6
- metadata.gz: 8375dd84ecfefe94dafb591d5edfb1e22a3413aa0f574b7c7a965b1adcf7ef62cd70397fb133a8f0e2468183edae44f82208742d357f45a43a69e53d9f352e6c
7
- data.tar.gz: 9bf26ed27a75bb355f1468baa668e0c3b569b9313d1468b60ac9aa6bbe7a2c142592011bacb7f931232e18a2bb90c932011b375a6b73e7d654a942ae0bb57210
6
+ metadata.gz: 18b52b5dd1fc76864d52c74ee81ede34cdc266f3dde68e83c7235aae3f96fdb0a9af1c6451afe6be0a63ee4bd68bc830b275ae371d7cb6ae3fb81b56bc2bf882
7
+ data.tar.gz: b862451ef7df47f104892033286e14de121a5716c2857f8b31ce3bda2db1d66d130cbd6bcef22ddeedfdcd166372ca6c2505704cf2750efd752362ecf371027c
data/README.md CHANGED
@@ -1,6 +1,10 @@
1
1
  # roda-sse
2
2
 
3
- The roda-sse Roda plugin adds SSE headers and provides Rack 3 streaming for you to send your own events.
3
+ The roda-sse Roda plugin adds SSE provides a streaming interface for
4
+ server-sent events. Each stream is wrapped in an [Async](https://github.com/socketry/async) reactor
5
+ and events are sent asyncronously via tasks. The roda-sse plugin sets
6
+ appropriate SSE headers, handles disconnection errors, ensures
7
+ streams are properly closed and provies a `last_event_id` helper.
4
8
 
5
9
  ## Installation
6
10
 
@@ -15,6 +19,8 @@ https://github.com/havenwood/roda-sse
15
19
 
16
20
  ## Usage
17
21
 
22
+ See the [examples/](https://github.com/havenwood/roda-sse/tree/main/example) directory for an example app.
23
+
18
24
  roda-sse is a Roda plugin, so you need to load it into your Roda
19
25
  application similar to other plugins:
20
26
 
@@ -30,7 +36,5 @@ In your routing block, you can then use `r.sse` to stream with the correct heade
30
36
  r.sse do |stream|
31
37
  stream << "data: hello\n\n"
32
38
  stream << "data: world\n\n"
33
- ensure
34
- stream.close
35
39
  end
36
40
  ```
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'async'
4
+
3
5
  class Roda
4
6
  module RodaPlugins
5
7
  # Example:
@@ -11,22 +13,66 @@ class Roda
11
13
  # # GET /
12
14
  # r.sse do |stream|
13
15
  # stream.write "data: hola\n\n"
14
- # ensure
15
- # stream.close
16
16
  # end
17
17
  # end
18
18
  # end
19
19
  module SSE
20
+ class Output
21
+ def initialize(stream)
22
+ @stream = stream
23
+ end
24
+
25
+ def write(message)
26
+ data = message.to_s
27
+ @stream.write(data)
28
+ return data.bytesize
29
+ end
30
+
31
+ def <<(message)
32
+ write(message)
33
+ self
34
+ end
35
+
36
+ def close(error = nil)
37
+ if stream = @stream
38
+ @stream = nil
39
+ stream.close_write(error)
40
+ end
41
+ end
42
+ end
43
+
44
+ class Body
45
+ def initialize(block)
46
+ @block = block
47
+ end
48
+
49
+ def call(stream)
50
+ output = Output.new(stream)
51
+ @block.call(output)
52
+ rescue => error
53
+ ensure
54
+ stream.close_write(error)
55
+ end
56
+ end
57
+
20
58
  module RequestMethods
21
- def sse(&block)
22
- response['Content-Type'] = 'text/event-stream'
23
- response['Cache-Control'] = 'no-cache'
59
+ HEADERS = {
60
+ 'content-type' => 'text/event-stream',
61
+ 'cache-control' => 'no-cache',
62
+ }.freeze
24
63
 
25
- always do
26
- halt response.finish_with_body(block)
64
+ def sse(&block)
65
+ get do
66
+ halt [200, HEADERS.dup, Body.new(block)]
27
67
  end
28
68
  end
29
69
  end
70
+
71
+ module InstanceMethods
72
+ def last_event_id
73
+ env['HTTP_LAST_EVENT_ID']
74
+ end
75
+ end
30
76
  end
31
77
 
32
78
  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,42 @@ 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 '/'
35
32
 
36
- it 'has SSE headers' do
37
- get '/'
33
+ assert last_response.ok?
34
+ end
38
35
 
39
- headers = {'content-type' => 'text/event-stream', 'cache-control' => 'no-cache'}
40
- assert_equal headers, last_response.headers
41
- end
36
+ it 'does not respond to PUT' do
37
+ post '/'
38
+
39
+ refute last_response.ok?
40
+ end
41
+
42
+ it 'has SSE headers' do
43
+ get '/'
42
44
 
43
- it 'streams the body' do
44
- get '/'
45
+ headers = {'content-type' => 'text/event-stream', 'cache-control' => 'no-cache'}
46
+ assert_equal headers, last_response.headers
47
+ end
45
48
 
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
49
+ it 'streams the body' do
50
+ get '/'
51
51
 
52
- response_body.call(stream)
52
+ stream = Minitest::Mock.new
53
+ stream.expect(:write, nil, ["data: hola\n\n"])
54
+ stream.expect(:close_write, nil, [nil])
55
+ response_body = last_response.instance_variable_get(:@body)
56
+ assert_instance_of Roda::RodaPlugins::SSE::Body, response_body
53
57
 
54
- stream.verify
58
+ response_body.call(stream)
59
+
60
+ stream.verify
61
+ end
55
62
  end
56
63
  end
metadata CHANGED
@@ -1,13 +1,14 @@
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.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shannon Skipper
8
+ - Samuel Williams
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 2024-10-18 00:00:00.000000000 Z
11
+ date: 2024-11-04 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: roda
@@ -15,14 +16,28 @@ dependencies:
15
16
  requirements:
16
17
  - - "~>"
17
18
  - !ruby/object:Gem::Version
18
- version: '3.0'
19
+ version: '3.85'
19
20
  type: :runtime
20
21
  prerelease: false
21
22
  version_requirements: !ruby/object:Gem::Requirement
22
23
  requirements:
23
24
  - - "~>"
24
25
  - !ruby/object:Gem::Version
25
- version: '3.0'
26
+ version: '3.85'
27
+ - !ruby/object:Gem::Dependency
28
+ name: async
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.18'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.18'
26
41
  description: The roda-sse gem integrates simple SSE streaming into the roda web toolkit.
27
42
  email: shannonskipper@gmail.com
28
43
  executables: []