rage-rb 0.2.0 → 0.4.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 +21 -0
- data/Gemfile +1 -3
- data/README.md +6 -6
- data/lib/rage/all.rb +20 -0
- data/lib/rage/application.rb +3 -2
- data/lib/rage/cli.rb +66 -4
- data/lib/rage/controller/api.rb +117 -18
- data/lib/rage/errors.rb +4 -0
- data/lib/rage/params_parser.rb +45 -0
- data/lib/rage/request.rb +44 -0
- data/lib/rage/router/backend.rb +6 -3
- data/lib/rage/router/dsl.rb +225 -6
- data/lib/rage/router/handler_storage.rb +2 -2
- data/lib/rage/setup.rb +20 -4
- data/lib/rage/templates/Gemfile +5 -1
- data/lib/rage/templates/config-application.rb +2 -2
- data/lib/rage/uploaded_file.rb +70 -0
- data/lib/rage/version.rb +1 -1
- data/lib/rage-rb.rb +5 -14
- data/rage.gemspec +1 -1
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: adfca83c806d24e4baf6e3c6401c7268e7e88495eaee9120da3a55fca08400b8
|
4
|
+
data.tar.gz: 7769fd22482975665a66741d0362ce5bf75318192bd6ee2850f98da952f5fe35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66667fbd047a4c27ebb840f4fc6fd2f60fb50d90b7f25b38048afeb5a92b1ad6f5310a4f0eacbab42b2d11a0c0de44ff4a131d197737abe13f1817a7b2267147
|
7
|
+
data.tar.gz: e75d8f5c615844fe837015ddaf7d2f9c4a96cdebc321ad63c79a3a1645924f2b3a7ef3fe75007243042a14bae18b001bd6cb0c919754719fb339ae42d0e90075
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,26 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.4.0] - 2023-10-31
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Expose the `params` object.
|
8
|
+
- Support header authentication with `authenticate_with_http_token`.
|
9
|
+
- Add the `resources` and `namespace` route helpers.
|
10
|
+
- Add the `mount` and `match` route helpers.
|
11
|
+
- Allow to access request headers.
|
12
|
+
- Support custom ports when starting the app with `rage s`.
|
13
|
+
|
14
|
+
## [0.3.0] - 2023-10-08
|
15
|
+
|
16
|
+
### Added
|
17
|
+
|
18
|
+
- CLI `routes` task.
|
19
|
+
- CLI `console` task.
|
20
|
+
- `:if` and `:unless` options in `before_action`.
|
21
|
+
- Allow to set response headers.
|
22
|
+
- Block version of `before_action`.
|
23
|
+
|
3
24
|
## [0.2.0] - 2023-09-27
|
4
25
|
|
5
26
|
### Added
|
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,9 @@ 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
52
|
|
53
53
|
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
54
|
|
@@ -142,8 +142,8 @@ end
|
|
142
142
|
Version | Changes
|
143
143
|
------- |------------
|
144
144
|
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
|
-
0.3 | 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;<br
|
145
|
+
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 :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;<br>~~ • support regexp constraints (postponed)
|
147
147
|
0.5 | Implement Iodine-based equivalent of `ActionController::Live`.<br>Use `ActionDispatch::RemoteIp`.
|
148
148
|
0.6 | Expose the `cookies` object.<br>Expose the `send_data` and `send_file` methods.<br>Support conditional get with `etag` and `last_modified`.
|
149
149
|
0.7 | Add request logging.
|
data/lib/rage/all.rb
ADDED
@@ -0,0 +1,20 @@
|
|
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"
|
data/lib/rage/application.rb
CHANGED
@@ -13,13 +13,14 @@ class Rage::Application
|
|
13
13
|
handler = @router.lookup(env)
|
14
14
|
|
15
15
|
if handler
|
16
|
-
|
16
|
+
params = Rage::ParamsParser.prepare(env, handler[:params])
|
17
|
+
handler[:handler].call(env, params)
|
17
18
|
else
|
18
19
|
[404, {}, ["Not Found"]]
|
19
20
|
end
|
20
21
|
|
21
22
|
rescue => e
|
22
|
-
[500, {}, ["#{e.class}:#{e.message}\n\n#{e.backtrace.join("\n")}"]]
|
23
|
+
[500, {}, ["#{e.class}:#{e.message}\n\n#{e.backtrace.join("\n")}"]]
|
23
24
|
|
24
25
|
ensure
|
25
26
|
# notify Iodine the request can now be served
|
data/lib/rage/cli.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
2
|
require "thor"
|
4
|
-
require "rage"
|
3
|
+
require "rage/all"
|
4
|
+
require "irb"
|
5
5
|
|
6
6
|
module Rage
|
7
7
|
class CLI < Thor
|
@@ -15,21 +15,83 @@ module Rage
|
|
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.listen service: :http, handler: app, port: options[:port] || Rage.config.port
|
23
24
|
::Iodine.threads = Rage.config.threads_count
|
24
25
|
::Iodine.workers = Rage.config.workers_count
|
25
26
|
|
26
27
|
::Iodine.start
|
27
28
|
end
|
29
|
+
|
30
|
+
desc 'routes', 'List all routes.'
|
31
|
+
option :grep, aliases: "-g", desc: "Filter routes by pattern"
|
32
|
+
def routes
|
33
|
+
# the result would be something like this:
|
34
|
+
# Verb Path Controller#Action
|
35
|
+
# GET / application#index
|
36
|
+
|
37
|
+
# load config/application.rb
|
38
|
+
environment
|
39
|
+
|
40
|
+
routes = Rage.__router.routes
|
41
|
+
pattern = options[:grep]
|
42
|
+
routes.unshift({ method: "Verb", path: "Path", raw_handler: "Controller#Action" })
|
43
|
+
|
44
|
+
grouped_routes = routes.each_with_object({}) do |route, memo|
|
45
|
+
if pattern && !memo.empty?
|
46
|
+
next unless route[:path].match?(pattern) || route[:raw_handler].to_s.match?(pattern) || route[:method].match?(pattern)
|
47
|
+
end
|
48
|
+
|
49
|
+
key = [route[:path], route[:raw_handler]]
|
50
|
+
if memo[key]
|
51
|
+
memo[key][:method] += "|#{route[:method]}"
|
52
|
+
else
|
53
|
+
memo[key] = route
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
longest_path = longest_method = 0
|
58
|
+
grouped_routes.each do |_, route|
|
59
|
+
longest_path = route[:path].length if route[:path].length > longest_path
|
60
|
+
longest_method = route[:method].length if route[:method].length > longest_method
|
61
|
+
end
|
62
|
+
|
63
|
+
margin = 3
|
64
|
+
longest_path += margin
|
65
|
+
longest_method += margin
|
66
|
+
|
67
|
+
grouped_routes.each_with_index do |(_, route), i|
|
68
|
+
meta = route[:constraints]
|
69
|
+
meta.merge!(route[:defaults]) if route[:defaults]
|
70
|
+
|
71
|
+
handler = route[:raw_handler]
|
72
|
+
handler = "#{handler} #{meta}" unless meta&.empty?
|
73
|
+
|
74
|
+
puts format("%-#{longest_method}s%-#{longest_path}s%s", route[:method], route[:path], handler)
|
75
|
+
puts "\n" if i == 0
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "c", "Start the app console."
|
80
|
+
def console
|
81
|
+
environment
|
82
|
+
ARGV.clear
|
83
|
+
IRB.start
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def environment
|
89
|
+
require File.expand_path("config/application.rb", Dir.pwd)
|
90
|
+
end
|
28
91
|
end
|
29
92
|
|
30
93
|
class NewAppGenerator < Thor::Group
|
31
94
|
include Thor::Actions
|
32
|
-
|
33
95
|
argument :path, type: :string
|
34
96
|
|
35
97
|
def self.source_root
|
data/lib/rage/controller/api.rb
CHANGED
@@ -12,13 +12,21 @@ class RageController::API
|
|
12
12
|
|
13
13
|
before_actions_chunk = if @__before_actions
|
14
14
|
filtered_before_actions = @__before_actions.select do |h|
|
15
|
-
(h[:only]
|
16
|
-
(h[:except]
|
15
|
+
(!h[:only] || h[:only].include?(action)) &&
|
16
|
+
(!h[:except] || !h[:except].include?(action))
|
17
17
|
end
|
18
18
|
|
19
19
|
lines = filtered_before_actions.map do |h|
|
20
|
+
condition = if h[:if] && h[:unless]
|
21
|
+
"if #{h[:if]} && !#{h[:unless]}"
|
22
|
+
elsif h[:if]
|
23
|
+
"if #{h[:if]}"
|
24
|
+
elsif h[:unless]
|
25
|
+
"unless #{h[:unless]}"
|
26
|
+
end
|
27
|
+
|
20
28
|
<<-RUBY
|
21
|
-
#{h[:name]}
|
29
|
+
#{h[:name]} #{condition}
|
22
30
|
return [@__status, @__headers, @__body] if @__rendered
|
23
31
|
RUBY
|
24
32
|
end
|
@@ -65,6 +73,16 @@ class RageController::API
|
|
65
73
|
klass.__rescue_handlers = @__rescue_handlers.freeze
|
66
74
|
end
|
67
75
|
|
76
|
+
# @private
|
77
|
+
@@__tmp_name_seed = ("a".."i").to_a.permutation
|
78
|
+
|
79
|
+
# @private
|
80
|
+
# define temporary method based on a block
|
81
|
+
def define_tmp_method(block)
|
82
|
+
name = @@__tmp_name_seed.next.join
|
83
|
+
define_method("__rage_tmp_#{name}", block)
|
84
|
+
end
|
85
|
+
|
68
86
|
############
|
69
87
|
#
|
70
88
|
# PUBLIC API
|
@@ -85,12 +103,11 @@ class RageController::API
|
|
85
103
|
# rescue_from User::NotAuthorized do |_|
|
86
104
|
# head :forbidden
|
87
105
|
# end
|
88
|
-
# @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.
|
89
107
|
def rescue_from(*klasses, with: nil, &block)
|
90
108
|
unless with
|
91
109
|
if block_given?
|
92
|
-
|
93
|
-
with = define_method("__#{name}", &block)
|
110
|
+
with = define_tmp_method(block)
|
94
111
|
else
|
95
112
|
raise "No handler provided. Pass the `with` keyword argument or provide a block."
|
96
113
|
end
|
@@ -107,21 +124,53 @@ class RageController::API
|
|
107
124
|
|
108
125
|
# Register a new `before_action` hook. Calls with the same `action_name` will overwrite the previous ones.
|
109
126
|
#
|
110
|
-
# @param action_name [String] the name of the callback to add
|
111
|
-
# @param
|
112
|
-
# @
|
127
|
+
# @param action_name [String, nil] the name of the callback to add
|
128
|
+
# @param [Hash] opts action options
|
129
|
+
# @option opts [Symbol, Array<Symbol>] :only restrict the callback to run only for specific actions
|
130
|
+
# @option opts [Symbol, Array<Symbol>] :except restrict the callback to run for all actions except specified
|
131
|
+
# @option opts [Symbol, Proc] :if only run the callback if the condition is true
|
132
|
+
# @option opts [Symbol, Proc] :unless only run the callback if the condition is false
|
113
133
|
# @example
|
114
134
|
# before_action :find_photo, only: :show
|
115
135
|
#
|
116
136
|
# def find_photo
|
117
137
|
# Photo.first
|
118
138
|
# end
|
119
|
-
|
139
|
+
# @example
|
140
|
+
# before_action :require_user, unless: :logged_in?
|
141
|
+
# @example
|
142
|
+
# before_action :set_locale, if: -> { params[:locale] != "en-US" }
|
143
|
+
# @example
|
144
|
+
# before_action do
|
145
|
+
# unless logged_in? # would be `controller.send(:logged_in?)` in Rails
|
146
|
+
# head :unauthorized
|
147
|
+
# end
|
148
|
+
# end
|
149
|
+
# @note The block form doesn't receive an argument and is executed on the controller level as if it was a regular method.
|
150
|
+
def before_action(action_name = nil, **opts, &block)
|
151
|
+
if block_given?
|
152
|
+
action_name = define_tmp_method(block)
|
153
|
+
elsif action_name.nil?
|
154
|
+
raise "No handler provided. Pass the `action_name` parameter or provide a block."
|
155
|
+
end
|
156
|
+
|
157
|
+
_only, _except, _if, _unless = opts.values_at(:only, :except, :if, :unless)
|
158
|
+
|
120
159
|
if @__before_actions && @__before_actions.frozen?
|
121
160
|
@__before_actions = @__before_actions.dup
|
122
161
|
end
|
123
162
|
|
124
|
-
action = {
|
163
|
+
action = {
|
164
|
+
name: action_name,
|
165
|
+
only: _only && Array(_only),
|
166
|
+
except: _except && Array(_except),
|
167
|
+
if: _if,
|
168
|
+
unless: _unless
|
169
|
+
}
|
170
|
+
|
171
|
+
action[:if] = define_tmp_method(action[:if]) if action[:if].is_a?(Proc)
|
172
|
+
action[:unless] = define_tmp_method(action[:unless]) if action[:unless].is_a?(Proc)
|
173
|
+
|
125
174
|
if @__before_actions.nil?
|
126
175
|
@__before_actions = [action]
|
127
176
|
elsif i = @__before_actions.find_index { |a| a[:name] == action_name }
|
@@ -140,7 +189,7 @@ class RageController::API
|
|
140
189
|
# skip_before_action :find_photo, only: :create
|
141
190
|
def skip_before_action(action_name, only: nil, except: nil)
|
142
191
|
i = @__before_actions&.find_index { |a| a[:name] == action_name }
|
143
|
-
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
|
144
193
|
|
145
194
|
@__before_actions = @__before_actions.dup if @__before_actions.frozen?
|
146
195
|
|
@@ -172,6 +221,11 @@ class RageController::API
|
|
172
221
|
@__rendered = false
|
173
222
|
end
|
174
223
|
|
224
|
+
# Get the request object. See {Rage::Request}.
|
225
|
+
def request
|
226
|
+
@request ||= Rage::Request.new(@__env)
|
227
|
+
end
|
228
|
+
|
175
229
|
# Send a response to the client.
|
176
230
|
#
|
177
231
|
# @param json [String, Object] send a json response to the client; objects like arrays will be serialized automatically
|
@@ -192,7 +246,7 @@ class RageController::API
|
|
192
246
|
@__body << if json
|
193
247
|
json.is_a?(String) ? json : json.to_json
|
194
248
|
else
|
195
|
-
|
249
|
+
headers["content-type"] = "text/plain; charset=utf-8"
|
196
250
|
plain.to_s
|
197
251
|
end
|
198
252
|
|
@@ -225,11 +279,56 @@ class RageController::API
|
|
225
279
|
end
|
226
280
|
end
|
227
281
|
|
228
|
-
|
282
|
+
# Set response headers.
|
283
|
+
#
|
284
|
+
# @example
|
285
|
+
# headers["Content-Type"] = "application/pdf"
|
286
|
+
def headers
|
287
|
+
# copy-on-write implementation for the headers object
|
288
|
+
@__headers = {}.merge!(@__headers) if DEFAULT_HEADERS.equal?(@__headers)
|
289
|
+
@__headers
|
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
|
229
308
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
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
|
234
333
|
end
|
235
334
|
end
|
data/lib/rage/errors.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rage::ParamsParser
|
4
|
+
def self.prepare(env, url_params)
|
5
|
+
has_body, query_string, content_type = env["IODINE_HAS_BODY"], env["QUERY_STRING"], env["CONTENT_TYPE"]
|
6
|
+
|
7
|
+
query_params = Iodine::Rack::Utils.parse_nested_query(query_string) if query_string != ""
|
8
|
+
unless has_body
|
9
|
+
if query_params
|
10
|
+
return query_params.merge!(url_params)
|
11
|
+
else
|
12
|
+
return url_params
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
request_params = if content_type.start_with?("application/json")
|
17
|
+
json_parse(env["rack.input"].read)
|
18
|
+
elsif content_type.start_with?("application/x-www-form-urlencoded")
|
19
|
+
Iodine::Rack::Utils.parse_urlencoded_nested_query(env["rack.input"].read)
|
20
|
+
else
|
21
|
+
Iodine::Rack::Utils.parse_multipart(env["rack.input"], content_type)
|
22
|
+
end
|
23
|
+
|
24
|
+
if request_params && !query_params
|
25
|
+
request_params.merge!(url_params)
|
26
|
+
elsif request_params && query_params
|
27
|
+
request_params.merge!(query_params, url_params)
|
28
|
+
else
|
29
|
+
url_params
|
30
|
+
end
|
31
|
+
|
32
|
+
rescue => e
|
33
|
+
raise Rage::Errors::BadRequest
|
34
|
+
end
|
35
|
+
|
36
|
+
if defined?(::FastJsonparser)
|
37
|
+
def self.json_parse(json)
|
38
|
+
FastJsonparser.parse(json, symbolize_keys: true)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
def self.json_parse(json)
|
42
|
+
JSON.parse(json, symbolize_names: true)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/rage/request.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Rage::Request
|
4
|
+
# @private
|
5
|
+
def initialize(env)
|
6
|
+
@env = env
|
7
|
+
end
|
8
|
+
|
9
|
+
# Get the request headers.
|
10
|
+
# @example
|
11
|
+
# request.headers["Content-Type"] # => "application/json"
|
12
|
+
# request.headers["Connection"] # => "keep-alive"
|
13
|
+
def headers
|
14
|
+
@headers ||= Headers.new(@env)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @private
|
18
|
+
class Headers
|
19
|
+
HTTP = "HTTP_"
|
20
|
+
|
21
|
+
def initialize(env)
|
22
|
+
@env = env
|
23
|
+
end
|
24
|
+
|
25
|
+
def [](requested_header)
|
26
|
+
if requested_header.start_with?(HTTP)
|
27
|
+
@env[requested_header]
|
28
|
+
else
|
29
|
+
(requested_header = requested_header.tr("-", "_")).upcase!
|
30
|
+
|
31
|
+
if "CONTENT_TYPE" == requested_header || "CONTENT_LENGTH" == requested_header
|
32
|
+
@env[requested_header]
|
33
|
+
else
|
34
|
+
@env["#{HTTP}#{requested_header}"]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def inspect
|
40
|
+
headers = @env.select { |k| k == "CONTENT_TYPE" || k == "CONTENT_LENGTH" || k.start_with?(HTTP) }
|
41
|
+
"#<#{self.class.name} @headers=#{headers.inspect}"
|
42
|
+
end
|
43
|
+
end # class Headers
|
44
|
+
end
|
data/lib/rage/router/backend.rb
CHANGED
@@ -3,6 +3,8 @@
|
|
3
3
|
require "uri"
|
4
4
|
|
5
5
|
class Rage::Router::Backend
|
6
|
+
attr_reader :routes
|
7
|
+
|
6
8
|
OPTIONAL_PARAM_REGEXP = /\/?\(\/?(:\w+)\/?\)/
|
7
9
|
STRING_HANDLER_REGEXP = /^([a-z0-9_\/]+)#([a-z_]+)$/
|
8
10
|
|
@@ -13,6 +15,7 @@ class Rage::Router::Backend
|
|
13
15
|
end
|
14
16
|
|
15
17
|
def on(method, path, handler, constraints: {}, defaults: nil)
|
18
|
+
raw_handler = handler
|
16
19
|
raise "Path could not be empty" if path&.empty?
|
17
20
|
|
18
21
|
if match_index = (path =~ OPTIONAL_PARAM_REGEXP)
|
@@ -42,7 +45,7 @@ class Rage::Router::Backend
|
|
42
45
|
handler = ->(env, _params) { orig_handler.call(env) }
|
43
46
|
end
|
44
47
|
|
45
|
-
__on(method, path, handler, constraints, defaults)
|
48
|
+
__on(method, path, handler, raw_handler, constraints, defaults)
|
46
49
|
end
|
47
50
|
|
48
51
|
def lookup(env)
|
@@ -52,7 +55,7 @@ class Rage::Router::Backend
|
|
52
55
|
|
53
56
|
private
|
54
57
|
|
55
|
-
def __on(method, path, handler, constraints, defaults)
|
58
|
+
def __on(method, path, handler, raw_handler, constraints, defaults)
|
56
59
|
@constrainer.validate_constraints(constraints)
|
57
60
|
# Let the constrainer know if any constraints are being used now
|
58
61
|
@constrainer.note_usage(constraints)
|
@@ -159,7 +162,7 @@ class Rage::Router::Backend
|
|
159
162
|
end
|
160
163
|
end
|
161
164
|
|
162
|
-
route = { method: method, path: path, pattern: pattern, params: params, constraints: constraints, handler: handler, defaults: defaults }
|
165
|
+
route = { method: method, path: path, pattern: pattern, params: params, constraints: constraints, handler: handler, raw_handler: raw_handler, defaults: defaults }
|
163
166
|
@routes << route
|
164
167
|
current_node.add_route(route, @constrainer)
|
165
168
|
end
|
data/lib/rage/router/dsl.rb
CHANGED
@@ -9,14 +9,54 @@ class Rage::Router::DSL
|
|
9
9
|
Handler.new(@router).instance_eval(&block)
|
10
10
|
end
|
11
11
|
|
12
|
+
##
|
13
|
+
# This class implements routing logic for your application, providing API similar to Rails.
|
14
|
+
#
|
15
|
+
# Compared to Rails router, the most notable difference is that a wildcard segment can only be in the last section of the path and cannot be named.
|
16
|
+
# Example:
|
17
|
+
# ```ruby
|
18
|
+
# get "/photos/*"
|
19
|
+
# ```
|
20
|
+
#
|
21
|
+
# Also, as this is an API-only framework, route helpers, like `photos_path` or `photos_url` are not being generated.
|
22
|
+
#
|
23
|
+
# #### Constraints
|
24
|
+
#
|
25
|
+
# Currently, the only constraint supported is the `host` constraint. The constraint value can be either string or a regular expression.
|
26
|
+
# Example:
|
27
|
+
# ```ruby
|
28
|
+
# get "/photos", to: "photos#index", constraints: { host: "myhost.com" }
|
29
|
+
# ```
|
30
|
+
#
|
31
|
+
# Parameter constraints are likely to be added in the future versions. Custom/lambda constraints are unlikely to be ever added.
|
32
|
+
#
|
33
|
+
# @example Set up a root handler
|
34
|
+
# root to: "pages#main"
|
35
|
+
# @example Set up multiple resources
|
36
|
+
# resources :magazines do
|
37
|
+
# resources :ads
|
38
|
+
# end
|
39
|
+
# @example Scope a set of routes to the given default options.
|
40
|
+
# scope path: ":account_id" do
|
41
|
+
# resources :projects
|
42
|
+
# end
|
43
|
+
# @example Scope routes to a specific namespace.
|
44
|
+
# namespace :admin do
|
45
|
+
# resources :posts
|
46
|
+
# end
|
12
47
|
class Handler
|
13
48
|
# @private
|
14
49
|
def initialize(router)
|
15
50
|
@router = router
|
16
51
|
|
52
|
+
@default_actions = %i(index create show update destroy)
|
53
|
+
@default_match_methods = %i(get post put patch delete head)
|
54
|
+
@scope_opts = %i(module path controller)
|
55
|
+
|
17
56
|
@path_prefixes = []
|
18
57
|
@module_prefixes = []
|
19
58
|
@defaults = []
|
59
|
+
@controllers = []
|
20
60
|
end
|
21
61
|
|
22
62
|
# Register a new GET route.
|
@@ -29,7 +69,7 @@ class Rage::Router::DSL
|
|
29
69
|
# get "/photos/:id", to: "photos#show", constraints: { host: /myhost/ }
|
30
70
|
# @example
|
31
71
|
# get "/photos(/:id)", to: "photos#show", defaults: { id: "-1" }
|
32
|
-
def get(path, to
|
72
|
+
def get(path, to: nil, constraints: nil, defaults: nil)
|
33
73
|
__on("GET", path, to, constraints, defaults)
|
34
74
|
end
|
35
75
|
|
@@ -43,7 +83,7 @@ class Rage::Router::DSL
|
|
43
83
|
# post "/photos", to: "photos#create", constraints: { host: /myhost/ }
|
44
84
|
# @example
|
45
85
|
# post "/photos", to: "photos#create", defaults: { format: "jpg" }
|
46
|
-
def post(path, to
|
86
|
+
def post(path, to: nil, constraints: nil, defaults: nil)
|
47
87
|
__on("POST", path, to, constraints, defaults)
|
48
88
|
end
|
49
89
|
|
@@ -57,7 +97,7 @@ class Rage::Router::DSL
|
|
57
97
|
# put "/photos/:id", to: "photos#update", constraints: { host: /myhost/ }
|
58
98
|
# @example
|
59
99
|
# put "/photos(/:id)", to: "photos#update", defaults: { id: "-1" }
|
60
|
-
def put(path, to
|
100
|
+
def put(path, to: nil, constraints: nil, defaults: nil)
|
61
101
|
__on("PUT", path, to, constraints, defaults)
|
62
102
|
end
|
63
103
|
|
@@ -71,7 +111,7 @@ class Rage::Router::DSL
|
|
71
111
|
# patch "/photos/:id", to: "photos#update", constraints: { host: /myhost/ }
|
72
112
|
# @example
|
73
113
|
# patch "/photos(/:id)", to: "photos#update", defaults: { id: "-1" }
|
74
|
-
def patch(path, to
|
114
|
+
def patch(path, to: nil, constraints: nil, defaults: nil)
|
75
115
|
__on("PATCH", path, to, constraints, defaults)
|
76
116
|
end
|
77
117
|
|
@@ -85,7 +125,7 @@ class Rage::Router::DSL
|
|
85
125
|
# delete "/photos/:id", to: "photos#destroy", constraints: { host: /myhost/ }
|
86
126
|
# @example
|
87
127
|
# delete "/photos(/:id)", to: "photos#destroy", defaults: { id: "-1" }
|
88
|
-
def delete(path, to
|
128
|
+
def delete(path, to: nil, constraints: nil, defaults: nil)
|
89
129
|
__on("DELETE", path, to, constraints, defaults)
|
90
130
|
end
|
91
131
|
|
@@ -98,6 +138,70 @@ class Rage::Router::DSL
|
|
98
138
|
__on("GET", "/", to, nil, nil)
|
99
139
|
end
|
100
140
|
|
141
|
+
# Match a URL pattern to one or more routes.
|
142
|
+
#
|
143
|
+
# @param path [String] the path for the route handler
|
144
|
+
# @param to [String, #call] the route handler in the format of "controller#action" or a callable
|
145
|
+
# @param constraints [Hash] a hash of constraints for the route
|
146
|
+
# @param defaults [Hash] a hash of default parameters for the route
|
147
|
+
# @param via [Symbol, Array<Symbol>] an array of HTTP methods to accept
|
148
|
+
# @example
|
149
|
+
# match "/photos/:id", to: "photos#show", via: [:get, :post]
|
150
|
+
# @example
|
151
|
+
# match "/photos/:id", to: "photos#show", via: :all
|
152
|
+
# @example
|
153
|
+
# match "/health", to: -> (env) { [200, {}, ["healthy"]] }
|
154
|
+
def match(path, to:, constraints: {}, defaults: nil, via: :all)
|
155
|
+
# via is either nil, or an array of symbols or its :all
|
156
|
+
http_methods = via
|
157
|
+
# if its :all or nil, then we use the default HTTP methods
|
158
|
+
if via == :all || via.nil?
|
159
|
+
http_methods = @default_match_methods
|
160
|
+
else
|
161
|
+
# if its an array of symbols, then we use the symbols as HTTP methods
|
162
|
+
http_methods = Array(via)
|
163
|
+
# then we check if the HTTP methods are valid
|
164
|
+
http_methods.each do |method|
|
165
|
+
raise ArgumentError, "Invalid HTTP method: #{method}" unless @default_match_methods.include?(method)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
http_methods.each do |method|
|
170
|
+
__on(method.to_s.upcase, path, to, constraints, defaults)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Register a new namespace.
|
175
|
+
#
|
176
|
+
# @param path [String] the path for the namespace
|
177
|
+
# @param options [Hash] a hash of options for the namespace
|
178
|
+
# @option options [String] :module the module name for the namespace
|
179
|
+
# @option options [String] :path the path for the namespace
|
180
|
+
# @example
|
181
|
+
# namespace :admin do
|
182
|
+
# get "/photos", to: "photos#index"
|
183
|
+
# end
|
184
|
+
# @example
|
185
|
+
# namespace :admin, path: "panel" do
|
186
|
+
# get "/photos", to: "photos#index"
|
187
|
+
# end
|
188
|
+
# @example
|
189
|
+
# namespace :admin, module: "admin" do
|
190
|
+
# get "/photos", to: "photos#index"
|
191
|
+
# end
|
192
|
+
def namespace(path, **options, &block)
|
193
|
+
path_prefix = options[:path] || path
|
194
|
+
module_prefix = options[:module] || path
|
195
|
+
|
196
|
+
@path_prefixes << path_prefix
|
197
|
+
@module_prefixes << module_prefix
|
198
|
+
|
199
|
+
instance_eval &block
|
200
|
+
|
201
|
+
@path_prefixes.pop
|
202
|
+
@module_prefixes.pop
|
203
|
+
end
|
204
|
+
|
101
205
|
# Scopes a set of routes to the given default options.
|
102
206
|
#
|
103
207
|
# @param [Hash] opts scope options.
|
@@ -120,15 +224,17 @@ class Rage::Router::DSL
|
|
120
224
|
# end
|
121
225
|
# end
|
122
226
|
def scope(opts, &block)
|
123
|
-
raise ArgumentError, "only
|
227
|
+
raise ArgumentError, "only :module, :path, and :controller options are accepted" if (opts.keys - @scope_opts).any?
|
124
228
|
|
125
229
|
@path_prefixes << opts[:path].delete_prefix("/").delete_suffix("/") if opts[:path]
|
126
230
|
@module_prefixes << opts[:module] if opts[:module]
|
231
|
+
@controllers << opts[:controller] if opts[:controller]
|
127
232
|
|
128
233
|
instance_eval &block
|
129
234
|
|
130
235
|
@path_prefixes.pop if opts[:path]
|
131
236
|
@module_prefixes.pop if opts[:module]
|
237
|
+
@controllers.pop if opts[:controller]
|
132
238
|
end
|
133
239
|
|
134
240
|
# Specify default parameters for a set of routes.
|
@@ -144,14 +250,109 @@ class Rage::Router::DSL
|
|
144
250
|
@defaults.pop
|
145
251
|
end
|
146
252
|
|
253
|
+
# Add a route to the collection.
|
254
|
+
#
|
255
|
+
# @example Add a `photos/search` path instead of `photos/:photo_id/search`
|
256
|
+
# resources :photos do
|
257
|
+
# collection do
|
258
|
+
# get "search"
|
259
|
+
# end
|
260
|
+
# end
|
261
|
+
def collection(&block)
|
262
|
+
orig_path_prefixes = @path_prefixes
|
263
|
+
@path_prefixes = @path_prefixes[0...-1] if @path_prefixes.last&.start_with?(":")
|
264
|
+
instance_eval &block
|
265
|
+
@path_prefixes = orig_path_prefixes
|
266
|
+
end
|
267
|
+
|
268
|
+
# Automatically create REST routes for a resource.
|
269
|
+
#
|
270
|
+
# @example Create five REST routes, all mapping to the `Photos` controller:
|
271
|
+
# resources :photos
|
272
|
+
# # GET /photos => photos#index
|
273
|
+
# # POST /photos => photos#create
|
274
|
+
# # GET /photos/:id => photos#show
|
275
|
+
# # PATCH/PUT /photos/:id => photos#update
|
276
|
+
# # DELETE /photos/:id => photos#destroy
|
277
|
+
# @note This helper doesn't generate the `new` and `edit` routes.
|
278
|
+
def resources(*_resources, **opts, &block)
|
279
|
+
# support calls with multiple resources, e.g. `resources :albums, :photos`
|
280
|
+
if _resources.length > 1
|
281
|
+
_resources.each { |_resource| resources(_resource, **opts, &block) }
|
282
|
+
return
|
283
|
+
end
|
284
|
+
|
285
|
+
_module, _path, _only, _except, _param = opts.values_at(:module, :path, :only, :except, :param)
|
286
|
+
raise ":param option can't contain colons" if _param&.include?(":")
|
287
|
+
|
288
|
+
_only = Array(_only) if _only
|
289
|
+
_except = Array(_except) if _except
|
290
|
+
actions = @default_actions.select do |action|
|
291
|
+
(_only.nil? || _only.include?(action)) && (_except.nil? || !_except.include?(action))
|
292
|
+
end
|
293
|
+
|
294
|
+
resource = _resources[0].to_s
|
295
|
+
_path ||= resource
|
296
|
+
_param ||= "id"
|
297
|
+
|
298
|
+
scope_opts = { path: _path }
|
299
|
+
scope_opts[:module] = _module if _module
|
300
|
+
|
301
|
+
scope(scope_opts) do
|
302
|
+
get("/", to: "#{resource}#index") if actions.include?(:index)
|
303
|
+
post("/", to: "#{resource}#create") if actions.include?(:create)
|
304
|
+
get("/:#{_param}", to: "#{resource}#show") if actions.include?(:show)
|
305
|
+
patch("/:#{_param}", to: "#{resource}#update") if actions.include?(:update)
|
306
|
+
put("/:#{_param}", to: "#{resource}#update") if actions.include?(:update)
|
307
|
+
delete("/:#{_param}", to: "#{resource}#destroy") if actions.include?(:destroy)
|
308
|
+
|
309
|
+
scope(path: ":#{to_singular(resource)}_#{_param}", controller: resource, &block) if block
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Mount a Rack-based application to be used within the application.
|
314
|
+
#
|
315
|
+
# @example
|
316
|
+
# mount Sidekiq::Web => "/sidekiq"
|
317
|
+
# @example
|
318
|
+
# mount Sidekiq::Web, at: "/sidekiq", via: :get
|
319
|
+
def mount(*args)
|
320
|
+
if args.first.is_a?(Hash)
|
321
|
+
app = args.first.keys.first
|
322
|
+
at = args.first.values.first
|
323
|
+
via = args[0][:via]
|
324
|
+
else
|
325
|
+
app = args.first
|
326
|
+
at = args[1][:at]
|
327
|
+
via = args[1][:via]
|
328
|
+
end
|
329
|
+
|
330
|
+
# Use match with via: :all to mount the Rack-based application
|
331
|
+
match(at, to: app, via:)
|
332
|
+
end
|
333
|
+
|
147
334
|
private
|
148
335
|
|
149
336
|
def __on(method, path, to, constraints, defaults)
|
337
|
+
# handle calls without controller inside resources:
|
338
|
+
# resources :comments do
|
339
|
+
# post :like
|
340
|
+
# end
|
341
|
+
if !to
|
342
|
+
if @controllers.any?
|
343
|
+
to = "#{@controllers.last}##{path}"
|
344
|
+
else
|
345
|
+
raise "Missing :to key on routes definition, please check your routes."
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
# process path to ensure it starts with "/" and doesn't end with "/"
|
150
350
|
if path != "/"
|
151
351
|
path = "/#{path}" unless path.start_with?("/")
|
152
352
|
path = path.delete_suffix("/") if path.end_with?("/")
|
153
353
|
end
|
154
354
|
|
355
|
+
# correctly process root helpers inside `scope` calls
|
155
356
|
if path == "/" && @path_prefixes.any?
|
156
357
|
path = ""
|
157
358
|
end
|
@@ -166,5 +367,23 @@ class Rage::Router::DSL
|
|
166
367
|
@router.on(method, "#{path_prefix}#{path}", to, constraints: constraints || {}, defaults: defaults)
|
167
368
|
end
|
168
369
|
end
|
370
|
+
|
371
|
+
def to_singular(str)
|
372
|
+
@active_support_loaded ||= str.respond_to?(:singularize) || :false
|
373
|
+
return str.singularize if @active_support_loaded != :false
|
374
|
+
|
375
|
+
@endings ||= {
|
376
|
+
"ves" => "fe",
|
377
|
+
"ies" => "y",
|
378
|
+
"i" => "us",
|
379
|
+
"zes" => "ze",
|
380
|
+
"ses" => "s",
|
381
|
+
"es" => "",
|
382
|
+
"s" => ""
|
383
|
+
}
|
384
|
+
@regexp ||= Regexp.new("(#{@endings.keys.join("|")})$")
|
385
|
+
|
386
|
+
str.sub(@regexp, @endings)
|
387
|
+
end
|
169
388
|
end
|
170
389
|
end
|
@@ -51,12 +51,12 @@ class Rage::Router::HandlerStorage
|
|
51
51
|
lines = []
|
52
52
|
|
53
53
|
param_keys.each_with_index do |key, i|
|
54
|
-
lines << "
|
54
|
+
lines << ":#{key} => param_values[#{i}]"
|
55
55
|
end
|
56
56
|
|
57
57
|
if defaults
|
58
58
|
defaults.except(*param_keys.map(&:to_sym)).each do |key, value|
|
59
|
-
lines << "
|
59
|
+
lines << ":#{key} => '#{value}'"
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
data/lib/rage/setup.rb
CHANGED
@@ -1,7 +1,23 @@
|
|
1
1
|
Iodine.patch_rack
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "#{Rage.root}/config/environments/#{Rage.env}"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
|
6
|
+
# load application files
|
7
|
+
app, bad = Dir["#{Rage.root}/app/**/*.rb"], []
|
8
|
+
|
9
|
+
loop do
|
10
|
+
path = app.shift
|
11
|
+
break if path.nil?
|
12
|
+
|
13
|
+
require_relative path
|
14
|
+
|
15
|
+
# push the file to the end of the list in case it depends on another file that has not yet been required;
|
16
|
+
# re-raise if only errored out files are left
|
17
|
+
rescue NameError
|
18
|
+
raise if (app - bad).empty?
|
19
|
+
app << path
|
20
|
+
bad << path
|
21
|
+
end
|
22
|
+
|
23
|
+
require_relative "#{Rage.root}/config/routes"
|
data/lib/rage/templates/Gemfile
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
source "https://rubygems.org"
|
2
2
|
|
3
|
-
gem "rage-rb"
|
3
|
+
gem "rage-rb", "<%= Rage::VERSION %>"
|
4
4
|
|
5
5
|
# Build JSON APIs with ease
|
6
6
|
# gem "alba"
|
7
|
+
|
8
|
+
# Get 50% to 150% boost when parsing JSON.
|
9
|
+
# Rage will automatically use FastJsonparser if it is available.
|
10
|
+
# gem "fast_jsonparser"
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# Models uploaded files.
|
5
|
+
#
|
6
|
+
# The actual file is accessible via the `file` accessor, though some
|
7
|
+
# of its interface is available directly for convenience.
|
8
|
+
#
|
9
|
+
# Rage will automatically unlink the files, so there is no need to clean them with a separate maintenance task.
|
10
|
+
class Rage::UploadedFile
|
11
|
+
# The basename of the file in the client.
|
12
|
+
attr_reader :original_filename
|
13
|
+
|
14
|
+
# A string with the MIME type of the file.
|
15
|
+
attr_reader :content_type
|
16
|
+
|
17
|
+
# A `File` object with the actual uploaded file. Note that some of its interface is available directly.
|
18
|
+
attr_reader :file
|
19
|
+
alias_method :tempfile, :file
|
20
|
+
|
21
|
+
def initialize(file, original_filename, content_type)
|
22
|
+
@file = file
|
23
|
+
@original_filename = original_filename
|
24
|
+
@content_type = content_type
|
25
|
+
end
|
26
|
+
|
27
|
+
# Shortcut for `file.read`.
|
28
|
+
def read(length = nil, buffer = nil)
|
29
|
+
@file.read(length, buffer)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Shortcut for `file.open`.
|
33
|
+
def open
|
34
|
+
@file.open
|
35
|
+
end
|
36
|
+
|
37
|
+
# Shortcut for `file.close`.
|
38
|
+
def close(unlink_now = false)
|
39
|
+
@file.close(unlink_now)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Shortcut for `file.path`.
|
43
|
+
def path
|
44
|
+
@file.path
|
45
|
+
end
|
46
|
+
|
47
|
+
# Shortcut for `file.to_path`.
|
48
|
+
def to_path
|
49
|
+
@file.to_path
|
50
|
+
end
|
51
|
+
|
52
|
+
# Shortcut for `file.rewind`.
|
53
|
+
def rewind
|
54
|
+
@file.rewind
|
55
|
+
end
|
56
|
+
|
57
|
+
# Shortcut for `file.size`.
|
58
|
+
def size
|
59
|
+
@file.size
|
60
|
+
end
|
61
|
+
|
62
|
+
# Shortcut for `file.eof?`.
|
63
|
+
def eof?
|
64
|
+
@file.eof?
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_io
|
68
|
+
@file.to_io
|
69
|
+
end
|
70
|
+
end
|
data/lib/rage/version.rb
CHANGED
data/lib/rage-rb.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require "rack"
|
4
4
|
require "json"
|
5
5
|
require "iodine"
|
6
|
+
require "pathname"
|
6
7
|
|
7
8
|
module Rage
|
8
9
|
def self.application
|
@@ -33,6 +34,10 @@ module Rage
|
|
33
34
|
[:default, Rage.env.to_sym]
|
34
35
|
end
|
35
36
|
|
37
|
+
def self.root
|
38
|
+
@root ||= Pathname.new(".").expand_path
|
39
|
+
end
|
40
|
+
|
36
41
|
module Router
|
37
42
|
module Strategies
|
38
43
|
end
|
@@ -41,17 +46,3 @@ end
|
|
41
46
|
|
42
47
|
module RageController
|
43
48
|
end
|
44
|
-
|
45
|
-
require_relative "rage/application"
|
46
|
-
require_relative "rage/fiber"
|
47
|
-
require_relative "rage/fiber_scheduler"
|
48
|
-
require_relative "rage/configuration"
|
49
|
-
|
50
|
-
require_relative "rage/router/strategies/host"
|
51
|
-
require_relative "rage/router/backend"
|
52
|
-
require_relative "rage/router/constrainer"
|
53
|
-
require_relative "rage/router/dsl"
|
54
|
-
require_relative "rage/router/handler_storage"
|
55
|
-
require_relative "rage/router/node"
|
56
|
-
|
57
|
-
require_relative "rage/controller/api"
|
data/rage.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rage-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roman Samoilov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '2.0'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '2.0'
|
55
55
|
description:
|
56
56
|
email:
|
57
57
|
- rsamoi@icloud.com
|
@@ -71,12 +71,16 @@ files:
|
|
71
71
|
- exe/rage
|
72
72
|
- lib/rage-rb.rb
|
73
73
|
- lib/rage.rb
|
74
|
+
- lib/rage/all.rb
|
74
75
|
- lib/rage/application.rb
|
75
76
|
- lib/rage/cli.rb
|
76
77
|
- lib/rage/configuration.rb
|
77
78
|
- lib/rage/controller/api.rb
|
79
|
+
- lib/rage/errors.rb
|
78
80
|
- lib/rage/fiber.rb
|
79
81
|
- lib/rage/fiber_scheduler.rb
|
82
|
+
- lib/rage/params_parser.rb
|
83
|
+
- lib/rage/request.rb
|
80
84
|
- lib/rage/router/README.md
|
81
85
|
- lib/rage/router/backend.rb
|
82
86
|
- lib/rage/router/constrainer.rb
|
@@ -96,6 +100,7 @@ files:
|
|
96
100
|
- lib/rage/templates/lib-.keep
|
97
101
|
- lib/rage/templates/log-.keep
|
98
102
|
- lib/rage/templates/public-.keep
|
103
|
+
- lib/rage/uploaded_file.rb
|
99
104
|
- lib/rage/version.rb
|
100
105
|
- rage.gemspec
|
101
106
|
homepage: https://github.com/rage-rb/rage
|