rage-rb 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -5
- data/Gemfile +3 -0
- data/README.md +14 -12
- data/lib/rage/all.rb +27 -0
- data/lib/rage/application.rb +35 -6
- data/lib/rage/cli.rb +43 -44
- data/lib/rage/configuration.rb +22 -6
- data/lib/rage/controller/api.rb +52 -4
- data/lib/rage/errors.rb +4 -0
- data/lib/rage/fiber.rb +58 -10
- data/lib/rage/fiber_scheduler.rb +59 -32
- data/lib/rage/logger/logger.rb +164 -0
- data/lib/rage/logger/text_formatter.rb +46 -0
- data/lib/rage/params_parser.rb +45 -0
- data/lib/rage/request.rb +44 -0
- data/lib/rage/router/backend.rb +37 -4
- data/lib/rage/router/dsl.rb +236 -6
- data/lib/rage/router/handler_storage.rb +8 -5
- data/lib/rage/sidekiq_session.rb +72 -0
- data/lib/rage/templates/Gemfile +5 -1
- data/lib/rage/templates/Rakefile +1 -0
- data/lib/rage/templates/config-application.rb +6 -1
- data/lib/rage/templates/config-environments-development.rb +6 -3
- data/lib/rage/templates/config-environments-production.rb +7 -3
- data/lib/rage/templates/config-environments-test.rb +6 -3
- data/lib/rage/uploaded_file.rb +70 -0
- data/lib/rage/version.rb +1 -1
- data/lib/rage-rb.rb +7 -16
- data/rage.gemspec +1 -1
- metadata +13 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 22f98c39badb0c2128b270ce3092d183f62db4bead34e8df627cf322a3697c71
|
4
|
+
data.tar.gz: ea49aeae3da7630f52e8c704bc042752a66b0fefc03a67d713ead33eec1fd006
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e9e57e1eafda6e41112b8af0cc8d23eac629e0964b441622c48173f936f45aac95d0482b3545c5feb047dee755889c30ac63d0f63332e59d36cdff5cb1800198
|
7
|
+
data.tar.gz: 481b9e52062350a9923c3b0195aea555ca0b8cb0c95fa95e76d3e7cd45a27e21a817ad259aed6e1cf9d90c74c17e99b8398066e84deda7e422247a1ea59254b9
|
data/CHANGELOG.md
CHANGED
@@ -1,14 +1,44 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] - 2023-11-25
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Add sessions for compatibility with `Sidekiq::Web` (#35).
|
8
|
+
- Add logger (#33).
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
|
12
|
+
- Fixes for `FiberScheduler#io_wait` and `FiberScheduler#io_read` (#32).
|
13
|
+
- Correctly handle exceptions in inner fibers (#34).
|
14
|
+
- Fixes for `FiberScheduler#kernel_sleep` (#36).
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
|
18
|
+
- Use config namespaces (#25).
|
19
|
+
- Update `Fiber.await` signature (#36).
|
20
|
+
|
21
|
+
## [0.4.0] - 2023-10-31
|
22
|
+
|
23
|
+
### Added
|
24
|
+
|
25
|
+
- Expose the `params` object (#23).
|
26
|
+
- Support header authentication with `authenticate_with_http_token` (#21).
|
27
|
+
- Add the `resources` route helper (#20).
|
28
|
+
- Add the `namespace` route helper by [@arikarim](https://github.com/arikarim) (#17).
|
29
|
+
- Add the `mount` and `match` route helpers by [@arikarim](https://github.com/arikarim) (#18) (#14).
|
30
|
+
- Allow to access request headers by [@arikarim](https://github.com/arikarim) (#15).
|
31
|
+
- Support custom ports when starting the app with `rage s`.
|
32
|
+
|
3
33
|
## [0.3.0] - 2023-10-08
|
4
34
|
|
5
35
|
### Added
|
6
36
|
|
7
|
-
- CLI `routes` task.
|
8
|
-
- CLI `console` task.
|
9
|
-
- `:if` and `:unless` options in `before_action
|
10
|
-
- Allow to set response headers.
|
11
|
-
- Block version of `before_action
|
37
|
+
- CLI `routes` task by [@arikarim](https://github.com/arikarim) (#9).
|
38
|
+
- CLI `console` task (#12).
|
39
|
+
- `:if` and `:unless` options in `before_action` (#10).
|
40
|
+
- Allow to set response headers (#11).
|
41
|
+
- Block version of `before_action` by [@heysyam99](https://github.com/heysyam99) (#8).
|
12
42
|
|
13
43
|
## [0.2.0] - 2023-09-27
|
14
44
|
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -11,7 +11,7 @@ Inspired by [Deno](https://deno.com) and built on top of [Iodine](https://github
|
|
11
11
|
|
12
12
|
* **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
13
|
|
14
|
-
* **API-only** - the only technology we should be using to create web UI is JavaScript. Check out [Vite](https://vitejs.dev) if you don't know where to start.
|
14
|
+
* **API-only** - the only technology we should be using to create web UI is JavaScript. Using native technologies is always the most flexible, scalable, and simple solution in the long run. Check out [Vite](https://vitejs.dev) if you don't know where to start.
|
15
15
|
|
16
16
|
* **Acceptance of modern Ruby** - the framework includes a fiber scheduler, which means your code never blocks while waiting on IO.
|
17
17
|
|
@@ -46,9 +46,10 @@ This gem is designed to be a drop-in replacement for Rails in API mode. Public A
|
|
46
46
|
|
47
47
|
Check out in-depth API docs for more information:
|
48
48
|
|
49
|
-
- [Controller API](https://rage-rb.
|
50
|
-
- [Routing API](https://rage-rb.
|
51
|
-
- [Fiber API](https://rage-rb.
|
49
|
+
- [Controller API](https://rage-rb.pages.dev/RageController/API)
|
50
|
+
- [Routing API](https://rage-rb.pages.dev/Rage/Router/DSL/Handler)
|
51
|
+
- [Fiber API](https://rage-rb.pages.dev/Fiber)
|
52
|
+
- [Logger API](https://rage-rb.pages.dev/Rage/Logger)
|
52
53
|
|
53
54
|
Also, see the [changelog](https://github.com/rage-rb/rage/blob/master/CHANGELOG.md) and [upcoming-releases](https://github.com/rage-rb/rage#upcoming-releases) for currently supported and planned features.
|
54
55
|
|
@@ -98,10 +99,10 @@ require "net/http"
|
|
98
99
|
|
99
100
|
class PagesController < RageController::API
|
100
101
|
def index
|
101
|
-
pages = Fiber.await(
|
102
|
+
pages = Fiber.await([
|
102
103
|
Fiber.schedule { Net::HTTP.get(URI("https://httpbin.org/json")) },
|
103
104
|
Fiber.schedule { Net::HTTP.get(URI("https://httpbin.org/html")) },
|
104
|
-
)
|
105
|
+
])
|
105
106
|
|
106
107
|
render json: { pages: pages }
|
107
108
|
end
|
@@ -143,12 +144,13 @@ Version | Changes
|
|
143
144
|
------- |------------
|
144
145
|
0.2 :white_check_mark: | ~~Gem configuration by env.<br>Add `skip_before_action`.<br>Add `rescue_from`.<br>Router updates:<br> • make the `root` helper work correctly with `scope`;<br> • support the `defaults` option;~~
|
145
146
|
0.3 :white_check_mark: | ~~CLI updates:<br> • `routes` task;<br> • `console` task;<br>Support the `:if` and `:unless` options in `before_action`.<br>Allow to set response headers.~~
|
146
|
-
0.4 | Expose the `params` object.<br>Support header authentication with `authenticate_with_http_token`.<br>Router updates:<br> • add the `resources` route helper;<br> • add the `namespace` route helper
|
147
|
-
0.5 |
|
148
|
-
0.6 |
|
149
|
-
0.7 |
|
150
|
-
0.8 |
|
151
|
-
0.9 |
|
147
|
+
0.4 :white_check_mark: | ~~Expose the `params` object.<br>Support header authentication with `authenticate_with_http_token`.<br>Router updates:<br> • add the `resources` route helper;<br> • add the `namespace` route helper;~~
|
148
|
+
0.5 :white_check_mark: | ~~Add request logging.~~
|
149
|
+
0.6 | Automatic code reloading in development with Zeitwerk.
|
150
|
+
0.7 | Expose the `send_data` and `send_file` methods.
|
151
|
+
0.8 | Support conditional get with `etag` and `last_modified`.
|
152
|
+
0.9 | Expose the `cookies` and `session` objects.
|
153
|
+
1.0 | Implement Iodine-based equivalent of Action Cable.
|
152
154
|
|
153
155
|
## Development
|
154
156
|
|
data/lib/rage/all.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative "../rage-rb"
|
2
|
+
|
3
|
+
require_relative "version"
|
4
|
+
require_relative "application"
|
5
|
+
require_relative "fiber"
|
6
|
+
require_relative "fiber_scheduler"
|
7
|
+
require_relative "configuration"
|
8
|
+
require_relative "request"
|
9
|
+
require_relative "uploaded_file"
|
10
|
+
require_relative "errors"
|
11
|
+
require_relative "params_parser"
|
12
|
+
|
13
|
+
require_relative "router/strategies/host"
|
14
|
+
require_relative "router/backend"
|
15
|
+
require_relative "router/constrainer"
|
16
|
+
require_relative "router/dsl"
|
17
|
+
require_relative "router/handler_storage"
|
18
|
+
require_relative "router/node"
|
19
|
+
|
20
|
+
require_relative "controller/api"
|
21
|
+
|
22
|
+
require_relative "logger/text_formatter"
|
23
|
+
require_relative "logger/logger"
|
24
|
+
|
25
|
+
if defined?(Sidekiq)
|
26
|
+
require_relative "sidekiq_session"
|
27
|
+
end
|
data/lib/rage/application.rb
CHANGED
@@ -10,20 +10,25 @@ class Rage::Application
|
|
10
10
|
|
11
11
|
def call(env)
|
12
12
|
fiber = Fiber.schedule do
|
13
|
+
init_logger
|
14
|
+
|
13
15
|
handler = @router.lookup(env)
|
14
16
|
|
15
|
-
if handler
|
16
|
-
|
17
|
+
response = if handler
|
18
|
+
params = Rage::ParamsParser.prepare(env, handler[:params])
|
19
|
+
handler[:handler].call(env, params)
|
17
20
|
else
|
18
21
|
[404, {}, ["Not Found"]]
|
19
22
|
end
|
20
23
|
|
21
|
-
rescue => e
|
22
|
-
|
24
|
+
rescue Exception => e
|
25
|
+
exception_str = "#{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}"
|
26
|
+
Rage.logger.error(exception_str)
|
27
|
+
response = [500, {}, [exception_str]]
|
23
28
|
|
24
29
|
ensure
|
25
|
-
|
26
|
-
Iodine.publish(env["IODINE_REQUEST_ID"], "")
|
30
|
+
finalize_logger(env, response, params)
|
31
|
+
Iodine.publish(env["IODINE_REQUEST_ID"], "") # notify Iodine the request can now be served
|
27
32
|
end
|
28
33
|
|
29
34
|
# the fiber encountered blocking IO and yielded; instruct Iodine to pause the request;
|
@@ -33,4 +38,28 @@ class Rage::Application
|
|
33
38
|
fiber.__get_result
|
34
39
|
end
|
35
40
|
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
DEFAULT_LOG_CONTEXT = {}.freeze
|
45
|
+
private_constant :DEFAULT_LOG_CONTEXT
|
46
|
+
|
47
|
+
def init_logger
|
48
|
+
Thread.current[:rage_logger] = {
|
49
|
+
tags: [Iodine::Rack::Utils.gen_request_tag],
|
50
|
+
context: DEFAULT_LOG_CONTEXT,
|
51
|
+
request_start: Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
def finalize_logger(env, response, params)
|
56
|
+
logger = Thread.current[:rage_logger]
|
57
|
+
|
58
|
+
duration = (
|
59
|
+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) - logger[:request_start]) * 1000
|
60
|
+
).round(2)
|
61
|
+
|
62
|
+
logger[:final] = { env:, params:, response:, duration: }
|
63
|
+
Rage.logger.info("")
|
64
|
+
end
|
36
65
|
end
|
data/lib/rage/cli.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "thor"
|
3
|
-
require "
|
4
|
-
require "irb"
|
3
|
+
require "rack"
|
5
4
|
|
6
5
|
module Rage
|
7
6
|
class CLI < Thor
|
@@ -11,17 +10,19 @@ module Rage
|
|
11
10
|
|
12
11
|
desc "new PATH", "Create a new application."
|
13
12
|
def new(path)
|
13
|
+
require "rage/all"
|
14
14
|
NewAppGenerator.start([path])
|
15
15
|
end
|
16
16
|
|
17
17
|
desc "s", "Start the app server."
|
18
|
+
option :port, aliases: "-p", desc: "Runs Rage on the specified port - defaults to 3000."
|
18
19
|
def server
|
19
20
|
app = ::Rack::Builder.parse_file("config.ru")
|
20
21
|
app = app[0] if app.is_a?(Array)
|
21
22
|
|
22
|
-
::Iodine.listen service: :http, handler: app, port: Rage.config.port
|
23
|
-
::Iodine.threads = Rage.config.threads_count
|
24
|
-
::Iodine.workers = Rage.config.workers_count
|
23
|
+
::Iodine.listen service: :http, handler: app, port: options[:port] || Rage.config.server.port
|
24
|
+
::Iodine.threads = Rage.config.server.threads_count
|
25
|
+
::Iodine.workers = Rage.config.server.workers_count
|
25
26
|
|
26
27
|
::Iodine.start
|
27
28
|
end
|
@@ -30,62 +31,60 @@ module Rage
|
|
30
31
|
option :grep, aliases: "-g", desc: "Filter routes by pattern"
|
31
32
|
def routes
|
32
33
|
# the result would be something like this:
|
33
|
-
#
|
34
|
-
#
|
34
|
+
# Verb Path Controller#Action
|
35
|
+
# GET / application#index
|
35
36
|
|
36
37
|
# load config/application.rb
|
37
38
|
environment
|
38
39
|
|
39
40
|
routes = Rage.__router.routes
|
40
|
-
|
41
41
|
pattern = options[:grep]
|
42
|
+
routes.unshift({ method: "Verb", path: "Path", meta: { raw_handler: "Controller#Action" } })
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
route[:path].match?(pattern) || route[:raw_handler].to_s.match?(pattern) || route[:method].match?(pattern)
|
44
|
+
grouped_routes = routes.each_with_object({}) do |route, memo|
|
45
|
+
if pattern && !memo.empty?
|
46
|
+
next unless route[:path].match?(pattern) || route[:meta][:raw_handler].to_s.match?(pattern) || route[:method].match?(pattern)
|
47
|
+
end
|
48
|
+
|
49
|
+
key = [route[:path], route[:meta][:raw_handler]]
|
50
|
+
|
51
|
+
if route[:meta][:mount]
|
52
|
+
memo[key] = route.merge(method: "") unless route[:path].end_with?("*")
|
53
|
+
next
|
54
|
+
end
|
55
|
+
|
56
|
+
if memo[key]
|
57
|
+
memo[key][:method] += "|#{route[:method]}"
|
58
|
+
else
|
59
|
+
memo[key] = route
|
46
60
|
end
|
47
61
|
end
|
48
62
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
# longest_path is either the length of the longest path or 5
|
55
|
-
longest_path = routes.map { |route| route[:path].length }.max + 3
|
56
|
-
longest_path = longest_path > 5 ? longest_path : 5
|
57
|
-
|
58
|
-
longest_verb = routes.map { |route| route[:method].length }.max + 3
|
59
|
-
longest_verb = longest_verb > 4 ? longest_verb : 7
|
60
|
-
|
61
|
-
# longest_handler is either the length of the longest handler or 7, since DELETE is the longest HTTP method
|
62
|
-
longest_handler = routes.map { |route| route[:raw_handler].is_a?(Proc) ? 7 : route[:raw_handler].split('#').last.length }.max + 3
|
63
|
-
longest_handler = longest_handler > 7 ? longest_handler : 7
|
64
|
-
|
65
|
-
# longest_controller is either the length of the longest controller or 12, since Controller#{length} is the longest controller
|
66
|
-
longest_controller = routes.map { |route| route[:raw_handler].is_a?(Proc) ? 7 : route[:raw_handler].to_s.length }.max + 3
|
67
|
-
longest_controller = longest_controller > 12 ? longest_controller : 12
|
68
|
-
|
69
|
-
routes.each do |route|
|
70
|
-
table << [
|
71
|
-
format("%- #{longest_handler}s", route[:raw_handler].is_a?(Proc) ? 'Lambda' : route[:raw_handler].split('#').last),
|
72
|
-
format("%- #{longest_verb}s", route[:method]),
|
73
|
-
format("%- #{longest_path}s", route[:path]),
|
74
|
-
format("%- #{longest_controller}s", route[:raw_handler].is_a?(Proc) ? 'Lambda' : route[:raw_handler])
|
75
|
-
]
|
63
|
+
longest_path = longest_method = 0
|
64
|
+
grouped_routes.each do |_, route|
|
65
|
+
longest_path = route[:path].length if route[:path].length > longest_path
|
66
|
+
longest_method = route[:method].length if route[:method].length > longest_method
|
76
67
|
end
|
77
68
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
69
|
+
margin = 3
|
70
|
+
longest_path += margin
|
71
|
+
longest_method += margin
|
72
|
+
|
73
|
+
grouped_routes.each_with_index do |(_, route), i|
|
74
|
+
meta = route[:constraints]
|
75
|
+
meta.merge!(route[:defaults]) if route[:defaults]
|
76
|
+
|
77
|
+
handler = route[:meta][:raw_handler]
|
78
|
+
handler = "#{handler} #{meta}" unless meta&.empty?
|
79
|
+
|
80
|
+
puts format("%-#{longest_method}s%-#{longest_path}s%s", route[:method], route[:path], handler)
|
81
|
+
puts "\n" if i == 0
|
84
82
|
end
|
85
83
|
end
|
86
84
|
|
87
85
|
desc "c", "Start the app console."
|
88
86
|
def console
|
87
|
+
require "irb"
|
89
88
|
environment
|
90
89
|
ARGV.clear
|
91
90
|
IRB.start
|
data/lib/rage/configuration.rb
CHANGED
@@ -1,10 +1,26 @@
|
|
1
1
|
class Rage::Configuration
|
2
|
-
attr_accessor :
|
3
|
-
attr_reader :threads_count
|
2
|
+
attr_accessor :logger, :log_formatter, :log_level
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
# used in DSL
|
5
|
+
def config = self
|
6
|
+
|
7
|
+
def server
|
8
|
+
@server ||= Server.new
|
9
|
+
end
|
10
|
+
|
11
|
+
class Server
|
12
|
+
attr_accessor :port, :workers_count
|
13
|
+
attr_reader :threads_count
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@threads_count = 1
|
17
|
+
@workers_count = -1
|
18
|
+
@port = 3000
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def __finalize
|
23
|
+
@logger.formatter = @log_formatter if @logger && @log_formatter
|
24
|
+
@logger.level = @log_level if @logger && @log_level
|
9
25
|
end
|
10
26
|
end
|
data/lib/rage/controller/api.rb
CHANGED
@@ -50,7 +50,7 @@ class RageController::API
|
|
50
50
|
""
|
51
51
|
end
|
52
52
|
|
53
|
-
class_eval <<-RUBY
|
53
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
54
54
|
def __run_#{action}
|
55
55
|
#{before_actions_chunk}
|
56
56
|
#{action}
|
@@ -103,7 +103,7 @@ class RageController::API
|
|
103
103
|
# rescue_from User::NotAuthorized do |_|
|
104
104
|
# head :forbidden
|
105
105
|
# end
|
106
|
-
# @note Unlike Rails, the handler must always take an argument. Use `_` if you don't care about the actual exception.
|
106
|
+
# @note Unlike in Rails, the handler must always take an argument. Use `_` if you don't care about the actual exception.
|
107
107
|
def rescue_from(*klasses, with: nil, &block)
|
108
108
|
unless with
|
109
109
|
if block_given?
|
@@ -167,7 +167,7 @@ class RageController::API
|
|
167
167
|
if: _if,
|
168
168
|
unless: _unless
|
169
169
|
}
|
170
|
-
|
170
|
+
|
171
171
|
action[:if] = define_tmp_method(action[:if]) if action[:if].is_a?(Proc)
|
172
172
|
action[:unless] = define_tmp_method(action[:unless]) if action[:unless].is_a?(Proc)
|
173
173
|
|
@@ -189,7 +189,7 @@ class RageController::API
|
|
189
189
|
# skip_before_action :find_photo, only: :create
|
190
190
|
def skip_before_action(action_name, only: nil, except: nil)
|
191
191
|
i = @__before_actions&.find_index { |a| a[:name] == action_name }
|
192
|
-
raise "The following action was specified to be skipped but
|
192
|
+
raise "The following action was specified to be skipped but couldn't be found: #{self}##{action_name}" unless i
|
193
193
|
|
194
194
|
@__before_actions = @__before_actions.dup if @__before_actions.frozen?
|
195
195
|
|
@@ -221,6 +221,11 @@ class RageController::API
|
|
221
221
|
@__rendered = false
|
222
222
|
end
|
223
223
|
|
224
|
+
# Get the request object. See {Rage::Request}.
|
225
|
+
def request
|
226
|
+
@request ||= Rage::Request.new(@__env)
|
227
|
+
end
|
228
|
+
|
224
229
|
# Send a response to the client.
|
225
230
|
#
|
226
231
|
# @param json [String, Object] send a json response to the client; objects like arrays will be serialized automatically
|
@@ -283,4 +288,47 @@ class RageController::API
|
|
283
288
|
@__headers = {}.merge!(@__headers) if DEFAULT_HEADERS.equal?(@__headers)
|
284
289
|
@__headers
|
285
290
|
end
|
291
|
+
|
292
|
+
# Authenticate using an HTTP Bearer token. Returns the value of the block if a token is found. Returns `nil` if no token is found.
|
293
|
+
#
|
294
|
+
# @yield [token] token value extracted from the `Authorization` header
|
295
|
+
# @example
|
296
|
+
# user = authenticate_with_http_token do |token|
|
297
|
+
# User.find_by(key: token)
|
298
|
+
# end
|
299
|
+
def authenticate_with_http_token
|
300
|
+
auth_header = @__env["HTTP_AUTHORIZATION"]
|
301
|
+
|
302
|
+
if auth_header&.start_with?("Bearer")
|
303
|
+
yield auth_header[7..]
|
304
|
+
elsif auth_header&.start_with?("Token")
|
305
|
+
yield auth_header[6..]
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
if !defined?(::ActionController::Parameters)
|
310
|
+
# Get the request data. The keys inside the hash are symbols, so `params.keys` returns an array of `Symbol`.<br>
|
311
|
+
# You can also load Strong Params to have Rage automatically wrap `params` in an instance of `ActionController::Parameters`.<br>
|
312
|
+
# At the same time, if you are not implementing complex filtering rules or working with nested structures, consider using native `Hash#fetch` and `Hash#slice` instead.
|
313
|
+
#
|
314
|
+
# For multipart file uploads, the uploaded files are represented by an instance of {Rage::UploadedFile}.
|
315
|
+
#
|
316
|
+
# @return [Hash{Symbol=>String,Array,Hash,Numeric,NilClass,TrueClass,FalseClass}]
|
317
|
+
# @example
|
318
|
+
# # make sure to load strong params before the `require "rage/all"` call
|
319
|
+
# require "active_support/all"
|
320
|
+
# require "action_controller/metal/strong_parameters"
|
321
|
+
#
|
322
|
+
# params.permit(:user).require(:full_name, :dob)
|
323
|
+
# @example
|
324
|
+
# # without strong params
|
325
|
+
# params.fetch(:user).slice(:full_name, :dob)
|
326
|
+
def params
|
327
|
+
@__params
|
328
|
+
end
|
329
|
+
else
|
330
|
+
def params
|
331
|
+
@params ||= ActionController::Parameters.new(@__params)
|
332
|
+
end
|
333
|
+
end
|
286
334
|
end
|
data/lib/rage/errors.rb
ADDED
data/lib/rage/fiber.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Fiber
|
4
|
+
AWAIT_ERROR_MESSAGE = "err"
|
5
|
+
|
2
6
|
# @private
|
3
7
|
def __set_result(result)
|
4
8
|
@__result = result
|
@@ -9,30 +13,74 @@ class Fiber
|
|
9
13
|
@__result
|
10
14
|
end
|
11
15
|
|
16
|
+
# @private
|
17
|
+
def __set_err(err)
|
18
|
+
@__err = err
|
19
|
+
end
|
20
|
+
|
21
|
+
# @private
|
22
|
+
def __get_err
|
23
|
+
@__err
|
24
|
+
end
|
25
|
+
|
26
|
+
# @private
|
27
|
+
# pause a fiber and resume in the next iteration of the event loop
|
28
|
+
def self.pause
|
29
|
+
f = Fiber.current
|
30
|
+
Iodine.defer { f.resume }
|
31
|
+
Fiber.yield
|
32
|
+
end
|
33
|
+
|
12
34
|
# Wait on several fibers at the same time. Calling this method will automatically pause the current fiber, allowing the
|
13
35
|
# server to process other requests. Once all fibers have completed, the current fiber will be automatically resumed.
|
14
36
|
#
|
15
37
|
# @param fibers [Fiber, Array<Fiber>] one or several fibers to wait on. The fibers must be created using the `Fiber.schedule` call.
|
16
38
|
# @example
|
17
|
-
# Fiber.await(
|
39
|
+
# Fiber.await([
|
18
40
|
# Fiber.schedule { request_1 },
|
19
41
|
# Fiber.schedule { request_2 },
|
20
|
-
# )
|
42
|
+
# ])
|
21
43
|
# @note This method should only be used when multiple fibers have to be processed in parallel. There's no need to use `Fiber.await` for single IO calls.
|
22
|
-
def self.await(
|
23
|
-
f = Fiber.current
|
44
|
+
def self.await(fibers)
|
45
|
+
f, fibers = Fiber.current, Array(fibers)
|
24
46
|
|
25
|
-
|
26
|
-
|
47
|
+
# check which fibers are alive (i.e. have yielded) and which have errored out
|
48
|
+
i, err, num_wait_for = 0, nil, 0
|
49
|
+
while i < fibers.length
|
50
|
+
if fibers[i].alive?
|
51
|
+
num_wait_for += 1
|
52
|
+
else
|
53
|
+
err = fibers[i].__get_err
|
54
|
+
break if err
|
55
|
+
end
|
56
|
+
i += 1
|
57
|
+
end
|
58
|
+
|
59
|
+
# raise if one of the fibers has errored out or return the result if none have yielded
|
60
|
+
if err
|
61
|
+
raise err
|
62
|
+
elsif num_wait_for == 0
|
63
|
+
return fibers.map!(&:__get_result)
|
64
|
+
end
|
27
65
|
|
28
|
-
|
29
|
-
|
30
|
-
|
66
|
+
# wait on async fibers; resume right away if one of the fibers errors out
|
67
|
+
Iodine.subscribe("await:#{f.object_id}") do |_, err|
|
68
|
+
if err == AWAIT_ERROR_MESSAGE
|
69
|
+
f.resume
|
70
|
+
else
|
71
|
+
num_wait_for -= 1
|
72
|
+
f.resume if num_wait_for == 0
|
73
|
+
end
|
31
74
|
end
|
32
75
|
|
33
76
|
Fiber.yield
|
34
77
|
Iodine.defer { Iodine.unsubscribe("await:#{f.object_id}") }
|
35
78
|
|
36
|
-
|
79
|
+
# if num_wait_for is not 0 means we exited prematurely because of an error
|
80
|
+
if num_wait_for > 0
|
81
|
+
raise fibers.find(&:__get_err).__get_err
|
82
|
+
else
|
83
|
+
fibers.map!(&:__get_result)
|
84
|
+
end
|
37
85
|
end
|
38
86
|
end
|