rage-rb 1.23.0 → 1.25.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 +4 -4
- data/CHANGELOG.md +38 -0
- data/CONTRIBUTING.md +240 -0
- data/README.md +66 -123
- data/lib/rage/application.rb +1 -0
- data/lib/rage/cable/cable.rb +20 -15
- data/lib/rage/cable/channel.rb +2 -1
- data/lib/rage/configuration.rb +166 -29
- data/lib/rage/controller/api.rb +10 -34
- data/lib/rage/cookies.rb +1 -1
- data/lib/rage/deferred/deferred.rb +7 -0
- data/lib/rage/deferred/metadata.rb +8 -0
- data/lib/rage/deferred/scheduler.rb +25 -0
- data/lib/rage/deferred/task.rb +19 -5
- data/lib/rage/errors.rb +83 -0
- data/lib/rage/events/subscriber.rb +6 -1
- data/lib/rage/fiber.rb +14 -23
- data/lib/rage/fiber_scheduler.rb +51 -6
- data/lib/rage/internal.rb +15 -6
- data/lib/rage/middleware/fiber_wrapper.rb +1 -0
- data/lib/rage/openapi/builder.rb +1 -1
- data/lib/rage/openapi/converter.rb +5 -1
- data/lib/rage/openapi/nodes/method.rb +2 -1
- data/lib/rage/openapi/nodes/root.rb +2 -1
- data/lib/rage/openapi/openapi.rb +33 -1
- data/lib/rage/openapi/parser.rb +73 -2
- data/lib/rage/openapi/parsers/ext/alba.rb +30 -2
- data/lib/rage/openapi/parsers/ext/blueprinter.rb +110 -0
- data/lib/rage/openapi/parsers/request.rb +2 -2
- data/lib/rage/openapi/parsers/response.rb +3 -2
- data/lib/rage/openapi/parsers/yaml.rb +27 -5
- data/lib/rage/params_parser.rb +2 -2
- data/lib/rage/pubsub/adapters/redis.rb +2 -1
- data/lib/rage/router/constrainer.rb +1 -1
- data/lib/rage/router/dsl.rb +7 -2
- data/lib/rage/sse/application.rb +1 -0
- data/lib/rage/telemetry/tracer.rb +1 -0
- data/lib/rage/version.rb +1 -1
- data/lib/rage-rb.rb +6 -0
- metadata +6 -4
- data/lib/rage/cable/adapters/base.rb +0 -16
- data/lib/rage/cable/adapters/redis.rb +0 -128
data/lib/rage/params_parser.rb
CHANGED
|
@@ -14,9 +14,9 @@ class Rage::ParamsParser
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
request_params = if content_type.start_with?("application/json")
|
|
17
|
-
json_parse(env["rack.input"].read)
|
|
17
|
+
json_parse(env["rack.input"].tap { |io| io.rewind }.read)
|
|
18
18
|
elsif content_type.start_with?("application/x-www-form-urlencoded")
|
|
19
|
-
Iodine::Rack::Utils.parse_urlencoded_nested_query(env["rack.input"].read)
|
|
19
|
+
Iodine::Rack::Utils.parse_urlencoded_nested_query(env["rack.input"].tap { |io| io.rewind }.read)
|
|
20
20
|
elsif content_type.start_with?("multipart/form-data")
|
|
21
21
|
Iodine::Rack::Utils.parse_multipart(env["rack.input"], content_type)
|
|
22
22
|
end
|
|
@@ -40,7 +40,7 @@ class Rage::PubSub::Adapters::Redis
|
|
|
40
40
|
|
|
41
41
|
@trimming_strategy = redis_version < Gem::Version.create("6.2.0") ? :maxlen : :minid
|
|
42
42
|
|
|
43
|
-
Rage::Internal.pick_a_worker do
|
|
43
|
+
Rage::Internal.pick_a_worker(purpose: "redis-pubsub") do
|
|
44
44
|
puts("INFO: #{Process.pid} is managing Redis subscriptions.") if Rage.logger.info?
|
|
45
45
|
poll
|
|
46
46
|
end
|
|
@@ -136,6 +136,7 @@ class Rage::PubSub::Adapters::Redis
|
|
|
136
136
|
|
|
137
137
|
rescue RedisClient::Error => e
|
|
138
138
|
Rage.logger.error("Subscriber error: #{e.message} (#{e.class})")
|
|
139
|
+
Rage::Errors.report(e)
|
|
139
140
|
sleep error_backoff_intervals.next
|
|
140
141
|
rescue SystemCallError => e
|
|
141
142
|
@stopping ? break : raise(e)
|
|
@@ -69,7 +69,7 @@ class Rage::Router::Constrainer
|
|
|
69
69
|
# Optimization: inline the derivation for the common built in constraints
|
|
70
70
|
if !strategy.custom?
|
|
71
71
|
if key == :host
|
|
72
|
-
lines << " host: env['HTTP_HOST'.freeze],"
|
|
72
|
+
lines << " host: env['HTTP_HOST'.freeze]&.sub(/:\\d+\\z/, ''.freeze),"
|
|
73
73
|
else
|
|
74
74
|
raise ArgumentError, "unknown non-custom strategy for compiling constraint derivation function"
|
|
75
75
|
end
|
data/lib/rage/router/dsl.rb
CHANGED
|
@@ -62,6 +62,8 @@ class Rage::Router::DSL
|
|
|
62
62
|
@router = router
|
|
63
63
|
|
|
64
64
|
@default_actions = %i(index create show update destroy)
|
|
65
|
+
@default_actions += %i(new edit) if Rage.config.router.form_actions
|
|
66
|
+
|
|
65
67
|
@default_match_methods = %i(get post put patch delete head)
|
|
66
68
|
@scope_opts = %i(module path controller)
|
|
67
69
|
|
|
@@ -359,6 +361,8 @@ class Rage::Router::DSL
|
|
|
359
361
|
get("/:#{_param}", to: "#{resource}#show") if actions.include?(:show)
|
|
360
362
|
patch("/:#{_param}", to: "#{resource}#update") if actions.include?(:update)
|
|
361
363
|
put("/:#{_param}", to: "#{resource}#update") if actions.include?(:update)
|
|
364
|
+
get("/new", to: "#{resource}#new") if actions.include?(:new)
|
|
365
|
+
get("/:#{_param}/edit", to: "#{resource}#edit") if actions.include?(:edit)
|
|
362
366
|
delete("/:#{_param}", to: "#{resource}#destroy") if actions.include?(:destroy)
|
|
363
367
|
|
|
364
368
|
scope(path: ":#{to_singular(resource)}_#{_param}", controller: resource, &block) if block
|
|
@@ -379,7 +383,6 @@ class Rage::Router::DSL
|
|
|
379
383
|
# # PATCH /photo => photos#update
|
|
380
384
|
# # PUT /photo => photos#update
|
|
381
385
|
# # DELETE /photo => photos#destroy
|
|
382
|
-
# @note This helper doesn't generate the `new` and `edit` routes.
|
|
383
386
|
# @note :param is not supported for singular resources.
|
|
384
387
|
def resource(*_resources, **opts, &block)
|
|
385
388
|
if _resources.length > 1
|
|
@@ -389,7 +392,7 @@ class Rage::Router::DSL
|
|
|
389
392
|
|
|
390
393
|
_module, _path, _only, _except = opts.values_at(:module, :path, :only, :except)
|
|
391
394
|
|
|
392
|
-
actions = __filter_actions(
|
|
395
|
+
actions = __filter_actions(@default_actions - [:index], _only, _except)
|
|
393
396
|
|
|
394
397
|
resource_name = _resources[0].to_s
|
|
395
398
|
controller_name = to_plural(resource_name)
|
|
@@ -398,6 +401,8 @@ class Rage::Router::DSL
|
|
|
398
401
|
get("/", to: "#{controller_name}#show") if actions.include?(:show)
|
|
399
402
|
patch("/", to: "#{controller_name}#update") if actions.include?(:update)
|
|
400
403
|
put("/", to: "#{controller_name}#update") if actions.include?(:update)
|
|
404
|
+
get("/new", to: "#{controller_name}#new") if actions.include?(:new)
|
|
405
|
+
get("/edit", to: "#{controller_name}#edit") if actions.include?(:edit)
|
|
401
406
|
delete("/", to: "#{controller_name}#destroy") if actions.include?(:destroy)
|
|
402
407
|
|
|
403
408
|
scope(controller: controller_name, &block) if block
|
data/lib/rage/sse/application.rb
CHANGED
data/lib/rage/version.rb
CHANGED
data/lib/rage-rb.rb
CHANGED
|
@@ -40,6 +40,12 @@ module Rage
|
|
|
40
40
|
Rage::Events
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
+
# Shorthand to access {Rage::Errors Rage::Errors}.
|
|
44
|
+
# @return [Rage::Errors]
|
|
45
|
+
def self.errors
|
|
46
|
+
Rage::Errors
|
|
47
|
+
end
|
|
48
|
+
|
|
43
49
|
# Shorthand to access {Rage::SSE Rage::SSE}.
|
|
44
50
|
# @return [Rage::SSE]
|
|
45
51
|
def self.sse
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rage-rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.25.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Roman Samoilov
|
|
@@ -134,6 +134,7 @@ files:
|
|
|
134
134
|
- Appraisals
|
|
135
135
|
- CHANGELOG.md
|
|
136
136
|
- CODE_OF_CONDUCT.md
|
|
137
|
+
- CONTRIBUTING.md
|
|
137
138
|
- Gemfile
|
|
138
139
|
- LICENSE.txt
|
|
139
140
|
- README.md
|
|
@@ -143,8 +144,6 @@ files:
|
|
|
143
144
|
- lib/rage.rb
|
|
144
145
|
- lib/rage/all.rb
|
|
145
146
|
- lib/rage/application.rb
|
|
146
|
-
- lib/rage/cable/adapters/base.rb
|
|
147
|
-
- lib/rage/cable/adapters/redis.rb
|
|
148
147
|
- lib/rage/cable/cable.rb
|
|
149
148
|
- lib/rage/cable/channel.rb
|
|
150
149
|
- lib/rage/cable/connection.rb
|
|
@@ -167,6 +166,7 @@ files:
|
|
|
167
166
|
- lib/rage/deferred/middleware_chain.rb
|
|
168
167
|
- lib/rage/deferred/proxy.rb
|
|
169
168
|
- lib/rage/deferred/queue.rb
|
|
169
|
+
- lib/rage/deferred/scheduler.rb
|
|
170
170
|
- lib/rage/deferred/task.rb
|
|
171
171
|
- lib/rage/env.rb
|
|
172
172
|
- lib/rage/errors.rb
|
|
@@ -199,6 +199,7 @@ files:
|
|
|
199
199
|
- lib/rage/openapi/parser.rb
|
|
200
200
|
- lib/rage/openapi/parsers/ext/active_record.rb
|
|
201
201
|
- lib/rage/openapi/parsers/ext/alba.rb
|
|
202
|
+
- lib/rage/openapi/parsers/ext/blueprinter.rb
|
|
202
203
|
- lib/rage/openapi/parsers/request.rb
|
|
203
204
|
- lib/rage/openapi/parsers/response.rb
|
|
204
205
|
- lib/rage/openapi/parsers/shared_reference.rb
|
|
@@ -293,5 +294,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
293
294
|
requirements: []
|
|
294
295
|
rubygems_version: 3.6.9
|
|
295
296
|
specification_version: 4
|
|
296
|
-
summary:
|
|
297
|
+
summary: Fiber-based Ruby web framework combining Rails ergonomics with a unified
|
|
298
|
+
runtime
|
|
297
299
|
test_files: []
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
class Rage::Cable::Adapters::Base
|
|
4
|
-
def pick_a_worker(&block)
|
|
5
|
-
_lock, lock_path = Tempfile.new.yield_self { |file| [file, file.path] }
|
|
6
|
-
|
|
7
|
-
Iodine.on_state(:on_start) do
|
|
8
|
-
if File.new(lock_path).flock(File::LOCK_EX | File::LOCK_NB)
|
|
9
|
-
if Rage.logger.debug?
|
|
10
|
-
puts "INFO: #{Process.pid} is managing #{self.class.name.split("::").last} subscriptions."
|
|
11
|
-
end
|
|
12
|
-
block.call
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "securerandom"
|
|
4
|
-
|
|
5
|
-
if !defined?(RedisClient)
|
|
6
|
-
fail <<~ERR
|
|
7
|
-
|
|
8
|
-
Redis adapter depends on the `redis-client` gem. Add the following line to your Gemfile:
|
|
9
|
-
gem "redis-client"
|
|
10
|
-
|
|
11
|
-
ERR
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
class Rage::Cable::Adapters::Redis < Rage::Cable::Adapters::Base
|
|
15
|
-
REDIS_STREAM_NAME = "rage:cable:messages"
|
|
16
|
-
DEFAULT_REDIS_OPTIONS = { reconnect_attempts: [0.05, 0.1, 0.5] }
|
|
17
|
-
REDIS_MIN_VERSION_SUPPORTED = Gem::Version.create(6)
|
|
18
|
-
|
|
19
|
-
def initialize(config)
|
|
20
|
-
@redis_stream = if (prefix = config.delete(:channel_prefix))
|
|
21
|
-
"#{prefix}:#{REDIS_STREAM_NAME}"
|
|
22
|
-
else
|
|
23
|
-
REDIS_STREAM_NAME
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
@redis_config = RedisClient.config(**DEFAULT_REDIS_OPTIONS.merge(config))
|
|
27
|
-
@server_uuid = SecureRandom.uuid
|
|
28
|
-
|
|
29
|
-
redis_version = get_redis_version
|
|
30
|
-
if redis_version < REDIS_MIN_VERSION_SUPPORTED
|
|
31
|
-
raise "Redis adapter only supports Redis 6+. Detected Redis version: #{redis_version}."
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
@trimming_strategy = redis_version < Gem::Version.create("6.2.0") ? :maxlen : :minid
|
|
35
|
-
|
|
36
|
-
pick_a_worker { poll }
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def publish(stream_name, data)
|
|
40
|
-
message_uuid = SecureRandom.uuid
|
|
41
|
-
|
|
42
|
-
publish_redis.call(
|
|
43
|
-
"XADD",
|
|
44
|
-
@redis_stream,
|
|
45
|
-
trimming_method, "~", trimming_value,
|
|
46
|
-
"*",
|
|
47
|
-
"1", stream_name,
|
|
48
|
-
"2", data.to_json,
|
|
49
|
-
"3", @server_uuid,
|
|
50
|
-
"4", message_uuid
|
|
51
|
-
)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
private
|
|
55
|
-
|
|
56
|
-
def publish_redis
|
|
57
|
-
@publish_redis ||= @redis_config.new_client
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def trimming_method
|
|
61
|
-
@trimming_strategy == :maxlen ? "MAXLEN" : "MINID"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def trimming_value
|
|
65
|
-
@trimming_strategy == :maxlen ? "10000" : ((Time.now.to_f - 5 * 60) * 1000).to_i
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def get_redis_version
|
|
69
|
-
service_redis = @redis_config.new_client
|
|
70
|
-
version = service_redis.call("INFO").match(/redis_version:([[:graph:]]+)/)[1]
|
|
71
|
-
|
|
72
|
-
Gem::Version.create(version)
|
|
73
|
-
|
|
74
|
-
rescue RedisClient::Error => e
|
|
75
|
-
puts "FATAL: Couldn't connect to Redis - all broadcasts will be limited to the current server."
|
|
76
|
-
puts e.backtrace.join("\n")
|
|
77
|
-
REDIS_MIN_VERSION_SUPPORTED
|
|
78
|
-
|
|
79
|
-
ensure
|
|
80
|
-
service_redis.close
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def error_backoff_intervals
|
|
84
|
-
@error_backoff_intervals ||= Enumerator.new do |y|
|
|
85
|
-
y << 0.2 << 0.5 << 1 << 2 << 5
|
|
86
|
-
loop { y << 10 }
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def poll
|
|
91
|
-
unless Fiber.scheduler
|
|
92
|
-
Fiber.set_scheduler(Rage::FiberScheduler.new)
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
Iodine.on_state(:start_shutdown) do
|
|
96
|
-
@stopping = true
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
Fiber.schedule do
|
|
100
|
-
read_redis = @redis_config.new_client
|
|
101
|
-
last_id = (Time.now.to_f * 1000).to_i
|
|
102
|
-
last_message_uuid = nil
|
|
103
|
-
|
|
104
|
-
loop do
|
|
105
|
-
data = read_redis.blocking_call(5, "XREAD", "COUNT", "100", "BLOCK", "5000", "STREAMS", @redis_stream, last_id)
|
|
106
|
-
|
|
107
|
-
if data
|
|
108
|
-
data[@redis_stream].each do |id, (_, stream_name, _, serialized_data, _, broadcaster_uuid, _, message_uuid)|
|
|
109
|
-
if broadcaster_uuid != @server_uuid && message_uuid != last_message_uuid
|
|
110
|
-
Rage.cable.__protocol.broadcast(stream_name, JSON.parse(serialized_data))
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
last_id = id
|
|
114
|
-
last_message_uuid = message_uuid
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
rescue RedisClient::Error => e
|
|
119
|
-
Rage.logger.error("Subscriber error: #{e.message} (#{e.class})")
|
|
120
|
-
sleep error_backoff_intervals.next
|
|
121
|
-
rescue => e
|
|
122
|
-
@stopping ? break : raise(e)
|
|
123
|
-
else
|
|
124
|
-
error_backoff_intervals.rewind
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|