rage-rb 1.19.2 → 1.20.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +1 -0
  3. data/Appraisals +19 -0
  4. data/CHANGELOG.md +15 -1
  5. data/CODE_OF_CONDUCT.md +13 -17
  6. data/Gemfile +3 -0
  7. data/README.md +60 -63
  8. data/Rakefile +14 -0
  9. data/lib/rage/all.rb +3 -0
  10. data/lib/rage/cable/cable.rb +11 -7
  11. data/lib/rage/cable/channel.rb +6 -1
  12. data/lib/rage/cable/connection.rb +4 -0
  13. data/lib/rage/cable/router.rb +14 -9
  14. data/lib/rage/configuration.rb +235 -21
  15. data/lib/rage/controller/api.rb +49 -44
  16. data/lib/rage/deferred/context.rb +30 -2
  17. data/lib/rage/deferred/deferred.rb +18 -6
  18. data/lib/rage/deferred/metadata.rb +39 -0
  19. data/lib/rage/deferred/middleware_chain.rb +67 -0
  20. data/lib/rage/deferred/task.rb +45 -17
  21. data/lib/rage/events/events.rb +3 -3
  22. data/lib/rage/events/subscriber.rb +36 -25
  23. data/lib/rage/fiber.rb +33 -31
  24. data/lib/rage/fiber_scheduler.rb +6 -2
  25. data/lib/rage/logger/logger.rb +7 -1
  26. data/lib/rage/middleware/body_finalizer.rb +14 -0
  27. data/lib/rage/response.rb +10 -5
  28. data/lib/rage/rspec.rb +17 -17
  29. data/lib/rage/setup.rb +2 -2
  30. data/lib/rage/telemetry/handler.rb +131 -0
  31. data/lib/rage/telemetry/spans/await_fiber.rb +50 -0
  32. data/lib/rage/telemetry/spans/broadcast_cable_stream.rb +50 -0
  33. data/lib/rage/telemetry/spans/create_websocket_connection.rb +50 -0
  34. data/lib/rage/telemetry/spans/dispatch_fiber.rb +48 -0
  35. data/lib/rage/telemetry/spans/enqueue_deferred_task.rb +52 -0
  36. data/lib/rage/telemetry/spans/process_cable_action.rb +56 -0
  37. data/lib/rage/telemetry/spans/process_cable_connection.rb +56 -0
  38. data/lib/rage/telemetry/spans/process_controller_action.rb +56 -0
  39. data/lib/rage/telemetry/spans/process_deferred_task.rb +54 -0
  40. data/lib/rage/telemetry/spans/process_event_subscriber.rb +54 -0
  41. data/lib/rage/telemetry/spans/publish_event.rb +54 -0
  42. data/lib/rage/telemetry/spans/spawn_fiber.rb +50 -0
  43. data/lib/rage/telemetry/telemetry.rb +121 -0
  44. data/lib/rage/telemetry/tracer.rb +97 -0
  45. data/lib/rage/version.rb +1 -1
  46. data/rage.gemspec +4 -3
  47. metadata +38 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9c49338e5b69de7d3c5667af08785441a4988710da64e84946023a660cc31560
4
- data.tar.gz: 841f4c508fb3d0739fd912967d370f5b40e69dbe8089727ea8cc56b123abd9ff
3
+ metadata.gz: a730f86d3b220732b6359247b4a0f3a44d72dd80aef857553f9daabb530753e6
4
+ data.tar.gz: 68254c6dcad99882a9bd4996051ac35713cad21f62405de59b311f01afc77616
5
5
  SHA512:
6
- metadata.gz: 73c04963af90446e29b52564020c42048735a3df8a17e071dd744f139094e68a64cabf66396433b05dd1cd51fa1b91374fe06133754b2d802171641cf2f44956
7
- data.tar.gz: d7561754ef307415e7d848e9ac0fc2731c03281285664eafd3c2865ebc7234bb03826c22cb803e05d48aaa3c4fce05d568a13b199f4d5ee5eb8d2613ba13c4b9
6
+ metadata.gz: 118c2ad29d1f3f2f91a8c2d153c020cc053b2e975ca876f3e0b50e18f2707a639193762c3cf2f7a25685b0b609bfa83225c19507d0f4b8f8ff4b7ca926026e51
7
+ data.tar.gz: 6c988a0e2bd16f12ad614c5e40fd4fb6d5441ad1adc0135253f52e4a971b3614fa00b5b5627d04bd37140aa55079e8bacd939970e7ac4f594dff929a6ef64be0
data/.rspec CHANGED
@@ -1,3 +1,4 @@
1
1
  --format documentation
2
2
  --color
3
3
  --require spec_helper
4
+ --exclude-pattern 'spec/ext/**/**_spec.rb'
data/Appraisals ADDED
@@ -0,0 +1,19 @@
1
+ appraise "active_record_7_0" do
2
+ gem "activerecord", "~> 7.0.0"
3
+ end
4
+
5
+ appraise "active_record_7_1" do
6
+ gem "activerecord", "~> 7.1.0"
7
+ end
8
+
9
+ appraise "active_record_7_2" do
10
+ gem "activerecord", "~> 7.2.0"
11
+ end
12
+
13
+ appraise "active_record_8_0" do
14
+ gem "activerecord", "~> 8.0.0"
15
+ end
16
+
17
+ appraise "active_record_8_1" do
18
+ gem "activerecord", "~> 8.1.0"
19
+ end
data/CHANGELOG.md CHANGED
@@ -1,6 +1,20 @@
1
1
  ## [Unreleased]
2
2
 
3
- ## [1.19.2] - 2025-01-06
3
+ ## [1.20.0] - 2026-01-20
4
+
5
+ ### Added
6
+
7
+ - Add `Rage::Telemetry` (#186).
8
+ - Add the `Response#status` method. (#191).
9
+ - [Deferred] Expose deferred metadata (#188).
10
+ - [Deferred] Support deferred middleware (#184).
11
+ - Ensure compatibility with `Rack::Events` (#180).
12
+
13
+ ### Changed
14
+
15
+ - Refactor the implementation of `rescue_from` in `Rage::Events` (#185).
16
+
17
+ ## [1.19.2] - 2026-01-06
4
18
 
5
19
  ### Changed
6
20
 
data/CODE_OF_CONDUCT.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Our Pledge
4
4
 
5
- We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
6
6
 
7
7
  We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
8
 
@@ -10,22 +10,19 @@ We pledge to act and interact in ways that contribute to an open, welcoming, div
10
10
 
11
11
  Examples of behavior that contributes to a positive environment for our community include:
12
12
 
13
- * Demonstrating empathy and kindness toward other people
14
- * Being respectful of differing opinions, viewpoints, and experiences
15
- * Giving and gracefully accepting constructive feedback
16
- * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
- * Focusing on what is best not just for us as individuals, but for the overall community
13
+ - Demonstrating empathy and kindness toward other people
14
+ - Being respectful of differing opinions, viewpoints, and experiences
15
+ - Giving and gracefully accepting constructive feedback
16
+ - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ - Focusing on what is best not just for us as individuals, but for the overall community
18
18
 
19
19
  Examples of unacceptable behavior include:
20
20
 
21
- * The use of sexualized language or imagery, and sexual attention or
22
- advances of any kind
23
- * Trolling, insulting or derogatory comments, and personal or political attacks
24
- * Public or private harassment
25
- * Publishing others' private information, such as a physical or email
26
- address, without their explicit permission
27
- * Other conduct which could reasonably be considered inappropriate in a
28
- professional setting
21
+ - The use of sexualized language or imagery, and sexual attention or advances of any kind
22
+ - Trolling, insulting or derogatory comments, and personal or political attacks
23
+ - Public or private harassment
24
+ - Publishing others’ private information, such as a physical or email address, without their explicit permission
25
+ - Other conduct which could reasonably be considered inappropriate in a professional setting
29
26
 
30
27
  ## Enforcement Responsibilities
31
28
 
@@ -67,14 +64,13 @@ Community leaders will follow these Community Impact Guidelines in determining t
67
64
 
68
65
  ### 4. Permanent Ban
69
66
 
70
- **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
67
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
68
 
72
69
  **Consequence**: A permanent ban from any sort of public interaction within the community.
73
70
 
74
71
  ## Attribution
75
72
 
76
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
- available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
73
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
78
74
 
79
75
  Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
76
 
data/Gemfile CHANGED
@@ -12,6 +12,8 @@ gem "yard"
12
12
  gem "rubocop", "~> 1.65.0", require: false
13
13
 
14
14
  group :test do
15
+ gem "ostruct"
16
+ gem "benchmark"
15
17
  gem "activesupport"
16
18
  gem "http"
17
19
  gem "pg"
@@ -23,4 +25,5 @@ group :test do
23
25
  gem "websocket-client-simple"
24
26
  gem "prism"
25
27
  gem "redis-client"
28
+ gem "appraisal", "~> 2.5"
26
29
  end
data/README.md CHANGED
@@ -6,74 +6,66 @@
6
6
  ![Tests](https://github.com/rage-rb/rage/actions/workflows/main.yml/badge.svg)
7
7
  ![Ruby Requirement](https://img.shields.io/badge/Ruby-3.2%2B-%23f40000)
8
8
 
9
- Rage is a high-performance framework compatible with Rails, featuring [WebSocket](https://github.com/rage-rb/rage/wiki/WebSockets-guide) support and automatic generation of [OpenAPI](https://github.com/rage-rb/rage/wiki/OpenAPI-Guide) documentation for your APIs. The framework is built on top of [Iodine](https://github.com/rage-rb/iodine) and is based on the following design principles:
9
+ Rage is a high-performance Ruby web framework that combines the developer experience of Rails with the scalability of fiber-based concurrency. Designed for API-first applications, it allows you to handle massive traffic loads using standard synchronous Ruby code - no complex async/await syntax required.
10
10
 
11
- * **Rails compatible API** - Rails' API is clean, straightforward, and simply makes sense. It was one of the reasons why Rails was so successful in the past.
11
+ If you love Rails but need better performance for I/O-heavy workloads, Rage provides the perfect balance: familiar conventions, low overhead, and a commitment to stability.
12
12
 
13
- * **High performance** - some think performance is not a major metric for a framework, but it's not true. Poor performance is a risk, and in today's world, companies refuse to use risky technologies.
13
+ ## Why Rage?
14
14
 
15
- * **API-only** - separation of concerns is one of the most fundamental principles in software development. Backend and frontend are very different layers with different goals and paths to those goals. Separating BE code from FE code results in a much more sustainable architecture compared with classic Rails monoliths.
15
+ Building high-performance APIs in Ruby shouldn't mean abandoning the conventions you know. Rage gives you Rails-like controllers, routing, and patterns, but runs on **fiber-based concurrency** that makes your application naturally non-blocking. When your code waits on database queries, HTTP calls, or other I/O, Rage automatically handles thousands of other requests instead of sitting idle.
16
16
 
17
- * **Acceptance of modern Ruby** - the framework includes a fiber scheduler, which means your code never blocks while waiting on I/O.
17
+ Rage was built to solve the performance and stability gaps that often drive teams to migrate away from Ruby, providing a modern engine that keeps the ecosystem competitive.
18
+
19
+ **Key capabilities:**
20
+
21
+ - **Rails compatibility** - Familiar controller API, routing DSL, and conventions. Migrate gradually or start fresh.
22
+ - **True concurrency** - Fiber-based architecture handles I/O without threads, locks, or async/await syntax. Your code looks synchronous but runs concurrently.
23
+ - **Zero-dependency WebSockets** - Action Cable-compatible real-time features that work out-of-the-box without Redis, even in multi-process mode.
24
+ - **Auto-generated OpenAPI** - Documentation generated from your controllers using simple comment tags.
25
+ - **In-process Background jobs** - A durable, persistent queue that runs inside your app process. No Redis or separate worker processes required.
26
+ - **Built-in Observability** - Track and measure application behavior with `Rage::Telemetry`. Integrate with external monitoring platforms or build custom observability solutions.
27
+ - **Stable and focused** - Our goal is that the task "Upgrade Rage" never appears in your ticketing system. We focus strictly on APIs, maintain long-term deprecation cycles, and ensure that most updates are as simple as a `bundle update`.
28
+
29
+ Rage is API-only by design. Modern applications benefit from clear separation between backend and frontend, and Rage focuses exclusively on doing APIs well.
18
30
 
19
31
  ## Installation
20
32
 
21
33
  Install the gem:
34
+
22
35
  ```
23
36
  $ gem install rage-rb
24
37
  ```
25
38
 
26
39
  Create a new app:
40
+
27
41
  ```
28
42
  $ rage new my_app
29
43
  ```
30
44
 
31
45
  Switch to your new application and install dependencies:
46
+
32
47
  ```
33
48
  $ cd my_app
34
49
  $ bundle
35
50
  ```
36
51
 
37
52
  Start up the server and visit http://localhost:3000.
53
+
38
54
  ```
39
55
  $ rage s
40
56
  ```
41
57
 
42
58
  Start coding!
43
59
 
44
- ## Getting Started
45
-
46
- This gem is designed to be a drop-in replacement for Rails in API mode. Public API is expected to fully match Rails.
47
-
48
- A Rage application can operate in two modes:
49
-
50
- * **Rails Mode**: Integrate Rage into an existing Rails application to improve throughput and better handle traffic spikes. For more information, see [Rails Integration](https://github.com/rage-rb/rage/wiki/Rails-integration).
51
- * **Standalone Mode**: Build high-performance services with minimal setup using Rage. To get started, run `rage new --help` for more details.
52
-
53
- Check out in-depth API docs for more information:
54
-
55
- - [Controller API](https://rage-rb.pages.dev/RageController/API)
56
- - [Routing API](https://rage-rb.pages.dev/Rage/Router/DSL/Handler)
57
- - [Fiber API](https://rage-rb.pages.dev/Fiber)
58
- - [Logger API](https://rage-rb.pages.dev/Rage/Logger)
59
- - [Configuration API](https://rage-rb.pages.dev/Rage/Configuration)
60
-
61
- Built-in middleware:
62
- - [CORS](https://rage-rb.pages.dev/Rage/Cors)
63
- - [RequestId](https://rage-rb.pages.dev/Rage/RequestId)
60
+ ## How It Works
64
61
 
65
- Also, see the following guides:
62
+ Rage runs each request in a separate fiber. When your code performs I/O operations - HTTP requests, database queries, file reads - the fiber automatically pauses, and Rage processes other requests. When the I/O completes, the fiber resumes exactly where it left off.
66
63
 
67
- - [Rails Integration](https://github.com/rage-rb/rage/wiki/Rails-integration)
68
- - [RSpec Integration](https://github.com/rage-rb/rage/wiki/RSpec-integration)
69
- - [WebSockets Guide](https://github.com/rage-rb/rage/wiki/WebSockets-guide)
70
- - [Background Tasks Guide](https://github.com/rage-rb/rage/wiki/Background-Tasks-Guide)
71
-
72
- If you are a first-time contributor, make sure to check the [overview doc](https://github.com/rage-rb/rage/blob/master/OVERVIEW.md) that shows how Rage's core components interact with each other.
64
+ This happens transparently. You write normal Ruby code, and Rage handles the concurrency.
73
65
 
74
66
  ### Example
75
67
 
76
- A sample controller could look like this:
68
+ Here's a controller that fetches data from an external API:
77
69
 
78
70
  ```ruby
79
71
  require "net/http"
@@ -98,9 +90,9 @@ class PagesController < RageController::API
98
90
  end
99
91
  ```
100
92
 
101
- Apart from `RageController::API` as a parent class, this is mostly a regular Rails controller. However, the main difference is under the hood - Rage runs every request in a separate fiber. During the call to `Net::HTTP.get`, the fiber is automatically paused, enabling the server to process other requests. Once the HTTP request is finished, the fiber will be resumed, potentially allowing to process hundreds of requests simultaneously.
93
+ This looks like a standard Rails controller, and it is - except during `Net::HTTP.get`, Rage automatically pauses this fiber and processes other requests. When the HTTP call completes, Rage resumes exactly where it left off. This happens automatically for HTTP requests, PostgreSQL, MySQL, and other I/O operations.
102
94
 
103
- To make this controller work, we would also need to update `config/routes.rb`. In this case, the file would look the following way:
95
+ The routes are equally familiar:
104
96
 
105
97
  ```ruby
106
98
  Rage.routes.draw do
@@ -108,9 +100,9 @@ Rage.routes.draw do
108
100
  end
109
101
  ```
110
102
 
111
- :information_source: **Note**: Rage will automatically pause a fiber and continue to process other fibers on HTTP, PostgreSQL, and MySQL calls. Calls to `Thread.join` and `Ractor.join` will also automatically pause the current fiber.
103
+ ### Parallel Execution
112
104
 
113
- Additionally, `Fiber.await` can be used to run several requests in parallel:
105
+ Need to make multiple I/O calls? Use `Fiber.await` to run them concurrently:
114
106
 
115
107
  ```ruby
116
108
  require "net/http"
@@ -127,11 +119,31 @@ class PagesController < RageController::API
127
119
  end
128
120
  ```
129
121
 
130
- :information_source: **Note**: When using `Fiber.await`, it is important to wrap every argument into a fiber using `Fiber.schedule`.
122
+ Instead of waiting for each request sequentially, Rage executes them concurrently and waits for all to complete.
123
+
124
+ ## Two Ways to Use Rage
125
+
126
+ **Standalone**: Create new services with `rage new`. You get a clean project structure, CLI tools, and everything needed to build high-performance APIs from scratch.
127
+
128
+ **Rails Integration**: Add Rage to existing Rails applications for gradual migration. Use Rage for new endpoints or high-traffic routes while keeping the rest of your Rails app unchanged. See the [Rails Integration guide](https://rage-rb.dev/docs/rails) for details.
129
+
130
+ ## Documentation
131
+
132
+ - [Getting Started](https://rage-rb.dev/docs/intro/) - Core concepts and setup
133
+ - [Controllers](https://rage-rb.dev/docs/controllers/) - Request handling and callbacks
134
+ - [Routing](https://rage-rb.dev/docs/routing/) - RESTful routes and namespaces
135
+ - [WebSockets](https://rage-rb.dev/docs/websockets/) - Real-time communication
136
+ - [OpenAPI](https://rage-rb.dev/docs/openapi/) - Auto-generated documentation
137
+ - [Background Jobs](https://rage-rb.dev/docs/deferred/) - In-process queue system
138
+ - [API Reference](https://rage-rb.dev/api/) - Detailed API documentation
139
+
140
+ For contributors, check the [architecture doc](https://github.com/rage-rb/rage/blob/main/ARCHITECTURE.md) to understand how Rage's components work together.
131
141
 
132
- ## Benchmarks
142
+ ## Performance
133
143
 
134
- #### Hello World
144
+ Rage's fiber-based architecture delivers high throughput with minimal overhead. By stripping away the "framework tax", Rage gives your team more leeway to write slow-but-maintainable Ruby code without compromising the end-user experience.
145
+
146
+ #### Simple JSON responses
135
147
 
136
148
  ```ruby
137
149
  class BenchmarksController < ApplicationController
@@ -141,13 +153,12 @@ class BenchmarksController < ApplicationController
141
153
  end
142
154
  ```
143
155
 
144
- ![Requests per second](https://github.com/user-attachments/assets/a7f864ae-0dfb-4628-a420-265a10d8591d)
156
+ ![Requests per second](https://github.com/user-attachments/assets/7bb783f8-5d1b-4e7d-b14d-dafe370d1acc)
145
157
 
146
- #### Waiting on I/O
147
158
 
148
- ```ruby
149
- require "net/http"
159
+ #### I/O-bound operations
150
160
 
161
+ ```ruby
151
162
  class BenchmarksController < ApplicationController
152
163
  def index
153
164
  Net::HTTP.get(URI("<endpoint-that-responds-in-one-second>"))
@@ -156,9 +167,9 @@ class BenchmarksController < ApplicationController
156
167
  end
157
168
  ```
158
169
 
159
- ![Time to complete 100 requests](https://github.com/user-attachments/assets/4f4feda3-bd88-43d8-8999-268534c2f9de)
170
+ ![Time to complete 100 requests](https://github.com/user-attachments/assets/5155a65f-2f11-4303-b5e4-a74d3d123c16)
160
171
 
161
- #### Using ActiveRecord
172
+ #### Database Queries
162
173
 
163
174
  ```ruby
164
175
  class BenchmarksController < ApplicationController
@@ -168,21 +179,7 @@ class BenchmarksController < ApplicationController
168
179
  end
169
180
  ```
170
181
 
171
- ![Requests per second](https://github.com/user-attachments/assets/04678788-0034-4db4-9582-d0bc16fd9e28)
172
-
173
- ## Upcoming releases
174
-
175
- Status | Changes
176
- -- | ------------
177
- :white_check_mark: | ~~Gem configuration by env.<br>Add `skip_before_action`.<br>Add `rescue_from`.<br>Router updates:<br>&emsp;• make the `root` helper work correctly with `scope`;<br>&emsp;• support the `defaults` option;~~
178
- :white_check_mark: | ~~CLI updates:<br>&emsp;• `routes` task;<br>&emsp;• `console` task;<br>Support the `:if` and `:unless` options in `before_action`.<br>Allow to set response headers.~~
179
- :white_check_mark: | ~~Expose the `params` object.<br>Support header authentication with `authenticate_with_http_token`.<br>Router updates:<br>&emsp;• add the `resources` route helper;<br>&emsp;• add the `namespace` route helper;~~
180
- :white_check_mark: | ~~Add request logging.~~
181
- :white_check_mark: | ~~Automatic code reloading in development with Zeitwerk.~~
182
- :white_check_mark: | ~~Support conditional get with `etag` and `last_modified`.~~
183
- :white_check_mark: | ~~Expose the `cookies` and `session` objects.~~
184
- :white_check_mark: | ~~Implement Iodine-based equivalent of Action Cable.~~
185
- ⏳ | Expose the `send_data` and `send_file` methods.
182
+ ![Requests per second-2](https://github.com/user-attachments/assets/06f64a08-316f-4b24-ba2d-39ac395366aa)
186
183
 
187
184
  ## Development
188
185
 
@@ -192,7 +189,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
192
189
 
193
190
  ## Contributing
194
191
 
195
- Bug reports and pull requests are welcome on GitHub at https://github.com/rage-rb/rage. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/rage-rb/rage/blob/master/CODE_OF_CONDUCT.md).
192
+ Bug reports and pull requests are welcome on GitHub at https://github.com/rage-rb/rage. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/rage-rb/rage/blob/main/CODE_OF_CONDUCT.md).
196
193
 
197
194
  ## License
198
195
 
@@ -200,4 +197,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
200
197
 
201
198
  ## Code of Conduct
202
199
 
203
- Everyone interacting in the Rage project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/rage-rb/rage/blob/master/CODE_OF_CONDUCT.md).
200
+ Everyone interacting in the Rage project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/rage-rb/rage/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile CHANGED
@@ -6,3 +6,17 @@ require "rspec/core/rake_task"
6
6
  RSpec::Core::RakeTask.new(:spec)
7
7
 
8
8
  task default: :spec
9
+
10
+ task :appraise do |_, args|
11
+ ext_versions = `appraisal list`.split("\n")
12
+
13
+ # Since we want to test against the main branch separately, we remove it from the list.
14
+ ext_versions.reject! { |version| version.end_with?("_head") }
15
+
16
+ ext_versions.each do |ext_version|
17
+ puts ">> Appraising #{ext_version}"
18
+
19
+ gem_name = ext_version.sub(/_\d+(_\d+)*$/, "")
20
+ system "bundle exec appraisal #{ext_version} rspec spec/ext/#{gem_name}/"
21
+ end
22
+ end
data/lib/rage/all.rb CHANGED
@@ -33,6 +33,9 @@ require_relative "middleware/fiber_wrapper"
33
33
  require_relative "middleware/cors"
34
34
  require_relative "middleware/reloader"
35
35
  require_relative "middleware/request_id"
36
+ require_relative "middleware/body_finalizer"
37
+
38
+ require_relative "telemetry/telemetry"
36
39
 
37
40
  if defined?(Sidekiq)
38
41
  require_relative "sidekiq_session"
@@ -43,11 +43,13 @@ module Rage::Cable
43
43
  accept_response = [0, __protocol.protocol_definition, []]
44
44
 
45
45
  application = ->(env) do
46
- if env["rack.upgrade?"] == :websocket
47
- env["rack.upgrade"] = handler
48
- accept_response
49
- else
50
- [426, { "connection" => "upgrade", "upgrade" => "websocket" }, []]
46
+ Rage::Telemetry.tracer.span_cable_websocket_handshake(env:) do
47
+ if env["rack.upgrade?"] == :websocket
48
+ env["rack.upgrade"] = handler
49
+ accept_response
50
+ else
51
+ [426, { "connection" => "upgrade", "upgrade" => "websocket" }, []]
52
+ end
51
53
  end
52
54
  end
53
55
 
@@ -133,8 +135,10 @@ module Rage::Cable
133
135
  # @example
134
136
  # Rage.cable.broadcast("chat", { message: "A new member has joined!" })
135
137
  def self.broadcast(stream, data)
136
- __protocol.broadcast(stream, data)
137
- __adapter&.publish(stream, data)
138
+ Rage::Telemetry.tracer.span_cable_stream_broadcast(stream:) do
139
+ __protocol.broadcast(stream, data)
140
+ __adapter&.publish(stream, data)
141
+ end
138
142
 
139
143
  true
140
144
  end
@@ -372,7 +372,9 @@ class Rage::Cable::Channel
372
372
 
373
373
  # @private
374
374
  def __run_action(action_name, data = nil)
375
- self.class.__prepared_actions[action_name].call(self, data)
375
+ Rage::Telemetry.tracer.span_cable_action_process(channel: self, action: action_name, data:) do
376
+ self.class.__prepared_actions[action_name].call(self, data)
377
+ end
376
378
  end
377
379
 
378
380
  # @private
@@ -382,6 +384,9 @@ class Rage::Cable::Channel
382
384
  @__identified_by = identified_by
383
385
  end
384
386
 
387
+ # @private
388
+ attr_reader :__connection
389
+
385
390
  # Get the params hash passed in during the subscription process.
386
391
  #
387
392
  # @return [Hash{Symbol=>String,Array,Hash,Numeric,NilClass,TrueClass,FalseClass}]
@@ -32,6 +32,10 @@ class Rage::Cable::Connection
32
32
  def connect
33
33
  end
34
34
 
35
+ # @private
36
+ def disconnect
37
+ end
38
+
35
39
  # Reject the WebSocket connection.
36
40
  def reject_unauthorized_connection
37
41
  @rejected = true
@@ -14,14 +14,18 @@ class Rage::Cable::Router
14
14
  # @return [true] if the connection was accepted
15
15
  # @return [false] if the connection was rejected
16
16
  def process_connection(connection)
17
- cable_connection = @connection_class.new(connection.env)
18
- cable_connection.connect
17
+ env = connection.env
18
+
19
+ cable_connection = @connection_class.new(env)
20
+ Rage::Telemetry.tracer.span_cable_connection_process(connection: cable_connection, action: :connect, env:) do
21
+ cable_connection.connect
22
+ end
19
23
 
20
24
  if cable_connection.rejected?
21
25
  Rage.logger.debug { "An unauthorized connection attempt was rejected" }
22
26
  else
23
- connection.env["rage.identified_by"] = cable_connection.__identified_by_map
24
- connection.env["rage.cable"] = {}
27
+ env["rage.identified_by"] = cable_connection.__identified_by_map
28
+ env["rage.cable"] = {}
25
29
  end
26
30
 
27
31
  !cable_connection.rejected?
@@ -105,12 +109,15 @@ class Rage::Cable::Router
105
109
  #
106
110
  # @param connection [Rage::Cable::WebSocketConnection] the connection object
107
111
  def process_disconnection(connection)
108
- connection.env["rage.cable"]&.each do |_, channel|
112
+ env = connection.env
113
+
114
+ env["rage.cable"]&.each do |_, channel|
109
115
  channel.__run_action(:unsubscribed)
110
116
  end
111
117
 
112
- if @connection_can_disconnect
113
- cable_connection = @connection_class.new(connection.env, connection.env["rage.identified_by"])
118
+ cable_connection = @connection_class.new(env, env["rage.identified_by"])
119
+
120
+ Rage::Telemetry.tracer.span_cable_connection_process(connection: cable_connection, action: :disconnect, env:) do
114
121
  cable_connection.disconnect
115
122
  end
116
123
  end
@@ -132,7 +139,5 @@ class Rage::Cable::Router
132
139
  puts "WARNING: Could not find the RageCable connection class! All connections will be accepted by default."
133
140
  Rage::Cable::Connection
134
141
  end
135
-
136
- @connection_can_disconnect = @connection_class.method_defined?(:disconnect)
137
142
  end
138
143
  end