roda-sse 0.1.0 → 0.3.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: 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: []