rage-rb 0.5.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: abee0787faa5947d8ecb03f9c5851cb669295bc2bd52abe5513e7b10940ddfff
4
- data.tar.gz: d167a4d6a48dbced809578f3c3a569c5ccdb913ed7bf51eba3e8d054b61e1c75
3
+ metadata.gz: 1c3041038ae63ae245e261a7a2096195ab56291d236414e086646ae96a4a6d16
4
+ data.tar.gz: a64817ded16716fe714310c7318d8e1d318454615da43d37055b333e76778fa2
5
5
  SHA512:
6
- metadata.gz: eea4d03aa25b675fdf1f19836037dceaeb7f38e2227af9cafc1609d59b8f322d3e3fa49da7905b4c0dd783615d16ce6e592aa11f2fa934056d711f03aaafbb43
7
- data.tar.gz: adf6eaaee9a88eef39631581ce52eaa311b5ac61797f481a95e28502ccabf166615a87e215b5f5b3260ebdfbb5c28f8c32e1e8342566d9d23836e56eb97b14d8
6
+ metadata.gz: 350f5150012852a3ca2532c031ce8ef28338da6d985ef66dff59095dea6b3dbbc4498826996876722bc5b7991683fed82c2fd3854313f7ed59f9fff20aaf4315
7
+ data.tar.gz: 84cd798056a43c8eb0e94809f72f6b0ac602076aa904831a3a049a8ed13352cad9321329233063343b73e65c0fbf1521564b0cee6f3696c89877a0af15191c94
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.7.0] - 2024-01-09
4
+
5
+ - Add conditional GET using `stale?` by [@tonekk](https://github.com/tonekk) (#55).
6
+ - Add Rails integration (#57).
7
+ - Add JSON log formatter (#59).
8
+
9
+ ## [0.6.0] - 2023-12-22
10
+
11
+ ### Added
12
+
13
+ - Implement after actions (#53).
14
+ - Zeitwerk autoloading and reloading by [@alex-rogachev](https://github.com/alex-rogachev) (#54).
15
+ - Support the `environment`, `binding`, `timeout`, and `max_clients` options when using `rage s` (#52).
16
+ - Add CORS middleware (#49).
17
+
18
+ ### Fixed
19
+
20
+ - Prevent `block` and `sleep` channels from conflicting (#51).
21
+
3
22
  ## [0.5.2] - 2023-12-11
4
23
 
5
24
  ### Added
data/CODE_OF_CONDUCT.md CHANGED
@@ -39,7 +39,7 @@ This Code of Conduct applies within all community spaces, and also applies when
39
39
 
40
40
  ## Enforcement
41
41
 
42
- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at roman.samoilov@wework.com. All complaints will be reviewed and investigated promptly and fairly.
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at developers@rage-rb.dev. All complaints will be reviewed and investigated promptly and fairly.
43
43
 
44
44
  All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
45
 
data/README.md CHANGED
@@ -52,6 +52,7 @@ Check out in-depth API docs for more information:
52
52
  - [Routing API](https://rage-rb.pages.dev/Rage/Router/DSL/Handler)
53
53
  - [Fiber API](https://rage-rb.pages.dev/Fiber)
54
54
  - [Logger API](https://rage-rb.pages.dev/Rage/Logger)
55
+ - [Configuration API](https://rage-rb.pages.dev/Rage/Configuration)
55
56
 
56
57
  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.
57
58
 
@@ -142,17 +143,17 @@ end
142
143
 
143
144
  ## Upcoming releases
144
145
 
145
- Version | Changes
146
- ------- |------------
147
- 0.2 :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;~~
148
- 0.3 :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.~~
149
- 0.4 :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;~~
150
- 0.5 :white_check_mark: | ~~Add request logging.~~
151
- 0.6 | Automatic code reloading in development with Zeitwerk.
152
- 0.7 | Expose the `send_data` and `send_file` methods.
153
- 0.8 | Support conditional get with `etag` and `last_modified`.
154
- 0.9 | Expose the `cookies` and `session` objects.
155
- 1.0 | Implement Iodine-based equivalent of Action Cable.
146
+ Status | Changes
147
+ -- | ------------
148
+ :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;~~
149
+ :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.~~
150
+ :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;~~
151
+ :white_check_mark: | ~~Add request logging.~~
152
+ :white_check_mark: | ~~Automatic code reloading in development with Zeitwerk.~~
153
+ | Expose the `send_data` and `send_file` methods.
154
+ | Support conditional get with `etag` and `last_modified`.
155
+ | Expose the `cookies` and `session` objects.
156
+ | Implement Iodine-based equivalent of Action Cable.
156
157
 
157
158
  ## Development
158
159
 
data/lib/rage/all.rb CHANGED
@@ -6,10 +6,11 @@ require_relative "fiber"
6
6
  require_relative "fiber_scheduler"
7
7
  require_relative "configuration"
8
8
  require_relative "request"
9
+ require_relative "response"
9
10
  require_relative "uploaded_file"
10
11
  require_relative "errors"
11
12
  require_relative "params_parser"
12
- require_relative "fiber_wrapper"
13
+ require_relative "code_loader"
13
14
 
14
15
  require_relative "router/strategies/host"
15
16
  require_relative "router/backend"
@@ -21,8 +22,13 @@ require_relative "router/node"
21
22
  require_relative "controller/api"
22
23
 
23
24
  require_relative "logger/text_formatter"
25
+ require_relative "logger/json_formatter"
24
26
  require_relative "logger/logger"
25
27
 
28
+ require_relative "middleware/fiber_wrapper"
29
+ require_relative "middleware/cors"
30
+ require_relative "middleware/reloader"
31
+
26
32
  if defined?(Sidekiq)
27
33
  require_relative "sidekiq_session"
28
34
  end
@@ -3,6 +3,7 @@
3
3
  class Rage::Application
4
4
  def initialize(router)
5
5
  @router = router
6
+ @exception_app = build_exception_app
6
7
  end
7
8
 
8
9
  def call(env)
@@ -17,10 +18,11 @@ class Rage::Application
17
18
  [404, {}, ["Not Found"]]
18
19
  end
19
20
 
21
+ rescue Rage::Errors::BadRequest => e
22
+ response = @exception_app.call(400, e)
23
+
20
24
  rescue Exception => e
21
- exception_str = "#{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}"
22
- Rage.logger.error(exception_str)
23
- response = [500, {}, [exception_str]]
25
+ response = @exception_app.call(500, e)
24
26
 
25
27
  ensure
26
28
  finalize_logger(env, response, params)
@@ -50,4 +52,20 @@ class Rage::Application
50
52
  Rage.logger.info("")
51
53
  logger[:final] = nil
52
54
  end
55
+
56
+ def build_exception_app
57
+ if Rage.env.development?
58
+ ->(status, e) do
59
+ exception_str = "#{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}"
60
+ Rage.logger.error(exception_str)
61
+ [status, {}, [exception_str]]
62
+ end
63
+ else
64
+ ->(status, e) do
65
+ exception_str = "#{e.class} (#{e.message}):\n#{e.backtrace.join("\n")}"
66
+ Rage.logger.error(exception_str)
67
+ [status, {}, []]
68
+ end
69
+ end
70
+ end
53
71
  end
data/lib/rage/cli.rb CHANGED
@@ -16,18 +16,24 @@ module Rage
16
16
 
17
17
  desc "s", "Start the app server."
18
18
  option :port, aliases: "-p", desc: "Runs Rage on the specified port - defaults to 3000."
19
+ option :environment, aliases: "-e", desc: "Specifies the environment to run this server under (test/development/production)."
20
+ option :binding, aliases: "-b", desc: "Binds Rails to the specified IP - defaults to 'localhost' in development and '0.0.0.0' in other environments."
21
+ option :config, aliases: "-c", desc: "Uses a custom rack configuration."
22
+ option :help, aliases: "-h", desc: "Show this message."
19
23
  def server
20
- app = ::Rack::Builder.parse_file("config.ru")
24
+ return help("server") if options.help?
25
+
26
+ set_env(options)
27
+
28
+ app = ::Rack::Builder.parse_file(options[:config] || "config.ru")
21
29
  app = app[0] if app.is_a?(Array)
22
30
 
23
- unless app.is_a?(Rage::FiberWrapper)
24
- raise <<-ERR
25
- Couldn't find the default middleware. Make sure to add the following line to your config.ru file:
26
- Rage.load_middlewares(self)
27
- ERR
28
- end
31
+ port = options[:port] || Rage.config.server.port
32
+ address = options[:binding] || (Rage.env.production? ? "0.0.0.0" : "localhost")
33
+ timeout = Rage.config.server.timeout
34
+ max_clients = Rage.config.server.max_clients
29
35
 
30
- ::Iodine.listen service: :http, handler: app, port: options[:port] || Rage.config.server.port
36
+ ::Iodine.listen service: :http, handler: app, port: port, address: address, timeout: timeout, max_clients: max_clients
31
37
  ::Iodine.threads = Rage.config.server.threads_count
32
38
  ::Iodine.workers = Rage.config.server.workers_count
33
39
 
@@ -36,12 +42,15 @@ module Rage
36
42
 
37
43
  desc 'routes', 'List all routes.'
38
44
  option :grep, aliases: "-g", desc: "Filter routes by pattern"
45
+ option :help, aliases: "-h", desc: "Show this message."
39
46
  def routes
47
+ return help("routes") if options.help?
40
48
  # the result would be something like this:
41
49
  # Verb Path Controller#Action
42
50
  # GET / application#index
43
51
 
44
52
  # load config/application.rb
53
+ set_env(options)
45
54
  environment
46
55
 
47
56
  routes = Rage.__router.routes
@@ -90,7 +99,12 @@ module Rage
90
99
  end
91
100
 
92
101
  desc "c", "Start the app console."
102
+ option :help, aliases: "-h", desc: "Show this message."
93
103
  def console
104
+ return help("console") if options.help?
105
+
106
+ set_env(options)
107
+
94
108
  require "irb"
95
109
  environment
96
110
  ARGV.clear
@@ -101,6 +115,16 @@ module Rage
101
115
 
102
116
  def environment
103
117
  require File.expand_path("config/application.rb", Dir.pwd)
118
+
119
+ # in Rails mode we delegate code loading to Rails, and thus need
120
+ # to manually load application code for CLI utilities to work
121
+ if Rage.config.internal.rails_mode
122
+ require "rage/setup"
123
+ end
124
+ end
125
+
126
+ def set_env(options)
127
+ ENV["RAGE_ENV"] = options[:environment] || ENV["RAGE_ENV"] || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
104
128
  end
105
129
  end
106
130
 
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+
5
+ class Rage::CodeLoader
6
+ def initialize
7
+ @reloading = false
8
+ end
9
+
10
+ def setup
11
+ @loader = Zeitwerk::Loader.new
12
+
13
+ autoload_path = "#{Rage.root}/app"
14
+ enable_reloading = Rage.env.development?
15
+ enable_eager_loading = !Rage.env.development? && !Rage.env.test?
16
+
17
+ @loader.push_dir(autoload_path)
18
+ # The first level of directories in app directory won't be treated as modules
19
+ # e.g. app/controllers/pages_controller.rb will be linked to PagesController class
20
+ # instead of Controllers::PagesController
21
+ @loader.collapse("#{Rage.root}/app/*")
22
+ @loader.enable_reloading if enable_reloading
23
+ @loader.setup
24
+ @loader.eager_load if enable_eager_loading
25
+ end
26
+
27
+ # in standalone mode - reload the code and the routes
28
+ def reload
29
+ return unless @loader
30
+
31
+ @reloading = true
32
+ @loader.reload
33
+ Rage.__router.reset_routes
34
+ load("#{Rage.root}/config/routes.rb")
35
+ end
36
+
37
+ # in Rails mode - reset the routes; everything else will be done by Rails
38
+ def rails_mode_reload
39
+ return if @loader
40
+
41
+ @reloading = true
42
+ Rage.__router.reset_routes
43
+ end
44
+
45
+ def reloading?
46
+ @reloading
47
+ end
48
+ end
@@ -1,9 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # # General Configuration
5
+ #
6
+ # • _config.logger_
7
+ #
8
+ # > The logger that will be used for `Rage.logger` and any related `Rage` logging. Custom loggers should implement Ruby's {https://ruby-doc.org/3.2.2/stdlibs/logger/Logger.html#class-Logger-label-Entries Logger} interface.
9
+ #
10
+ # • _config.log_formatter_
11
+ #
12
+ # > The formatter of the Rage logger. Built in options include `Rage::TextFormatter` and `Rage::JSONFormatter`. Defaults to an instance of `Rage::TextFormatter`.
13
+ #
14
+ # • _config.log_level_
15
+ #
16
+ # > Defines the verbosity of the Rage logger. This option defaults to `:debug` for all environments except production, where it defaults to `:info`. The available log levels are: `:debug`, `:info`, `:warn`, `:error`, `:fatal`, and `:unknown`.
17
+ #
18
+ # # Middleware Configuration
19
+ #
20
+ # • _config.middleware.use_
21
+ #
22
+ # > Adds a middleware to the top of the middleware stack. **This is the preferred way of adding a middleware.**
23
+ # > ```
24
+ # config.middleware.use Rack::Cors do
25
+ # allow do
26
+ # origins "*"
27
+ # resource "*", headers: :any
28
+ # end
29
+ # end
30
+ # > ```
31
+ #
32
+ # • _config.middleware.insert_before_
33
+ #
34
+ # > Adds middleware at a specified position before another middleware. The position can be either an index or another middleware.
35
+ #
36
+ # > **_❗️Heads up:_** By default, Rage always uses the `Rage::FiberWrapper` middleware, which wraps every request in a separate fiber. Make sure to always have this middleware in the top of the stack. Placing other middlewares in front may lead to undefined behavior.
37
+ #
38
+ # > ```
39
+ # config.middleware.insert_before Rack::Head, Magical::Unicorns
40
+ # config.middleware.insert_before 0, Magical::Unicorns
41
+ # > ```
42
+ #
43
+ # • _config.middleware.insert_after_
44
+ #
45
+ # > Adds middleware at a specified position after another middleware. The position can be either an index or another middleware.
46
+ #
47
+ # > ```
48
+ # config.middleware.insert_after Rack::Head, Magical::Unicorns
49
+ # > ```
50
+ #
51
+ # # Server Configuration
52
+ #
53
+ # _• config.server.max_clients_
54
+ #
55
+ # > Limits the number of simultaneous connections the server can accept. Defaults to the maximum number of open files.
56
+ #
57
+ # > **_❗️Heads up:_** Decreasing this number is almost never a good idea. Depending on your application specifics, you are encouraged to use other methods to limit the number of concurrent connections:
58
+ #
59
+ # > 1. If your application is exposed to the public, you may want to use a cloud rate limiter, like {https://developers.cloudflare.com/waf Cloudflare WAF} or {https://docs.fastly.com/en/ngwaf Fastly WAF}.
60
+ # > 2. Otherwise, consider using tools like {https://github.com/rack/rack-attack Rack::Attack} or {https://github.com/mperham/connection_pool connection_pool}.
61
+ #
62
+ # > ```
63
+ # # Limit the amount of connections your application can accept
64
+ # config.middleware.use Rack::Attack
65
+ # Rack::Attack.throttle("req/ip", limit: 300, period: 5.minutes) do |req|
66
+ # req.ip
67
+ # end
68
+ # #
69
+ # # Limit the amount of connections to a specific resource
70
+ # HTTP = ConnectionPool.new(size: 5, timeout: 5) { Net::HTTP }
71
+ # HTTP.with do |conn|
72
+ # conn.get("/my-resource")
73
+ # end
74
+ # > ```
75
+ #
76
+ # • _config.server.port_
77
+ #
78
+ # > Specifies what port the server will listen on.
79
+ #
80
+ # • _config.server.workers_count_
81
+ #
82
+ # > Specifies the number of server processes to run.
83
+ #
84
+ # • _config.server.timeout_
85
+ #
86
+ # > Specifies connection timeout.
87
+ #
1
88
  class Rage::Configuration
2
- attr_accessor :logger, :log_formatter, :log_level
89
+ attr_accessor :logger
90
+ attr_reader :log_formatter, :log_level
3
91
 
4
92
  # used in DSL
5
93
  def config = self
6
94
 
95
+ def log_formatter=(formatter)
96
+ raise "Custom log formatter should respond to `#call`" unless formatter.respond_to?(:call)
97
+ @log_formatter = formatter
98
+ end
99
+
100
+ def log_level=(level)
101
+ @log_level = level.is_a?(Symbol) ? Logger.const_get(level.to_s.upcase) : level
102
+ end
103
+
7
104
  def server
8
105
  @server ||= Server.new
9
106
  end
@@ -12,8 +109,12 @@ class Rage::Configuration
12
109
  @middleware ||= Middleware.new
13
110
  end
14
111
 
112
+ def internal
113
+ @internal ||= Internal.new
114
+ end
115
+
15
116
  class Server
16
- attr_accessor :port, :workers_count
117
+ attr_accessor :port, :workers_count, :timeout, :max_clients
17
118
  attr_reader :threads_count
18
119
 
19
120
  def initialize
@@ -36,6 +137,9 @@ class Rage::Configuration
36
137
 
37
138
  def insert_before(existing_middleware, new_middleware, *args, &block)
38
139
  index = find_middleware_index(existing_middleware)
140
+ if index == 0 && @middlewares[0][0] == Rage::FiberWrapper
141
+ puts("Warning: inserting #{new_middleware} before Rage::FiberWrapper may lead to undefined behavior.")
142
+ end
39
143
  @middlewares = (@middlewares[0...index] + [[new_middleware, args, block]] + @middlewares[index..]).uniq(&:first)
40
144
  end
41
145
 
@@ -60,9 +164,22 @@ class Rage::Configuration
60
164
  end
61
165
  end
62
166
 
167
+ # @private
168
+ class Internal
169
+ attr_accessor :rails_mode, :rails_console
170
+
171
+ def inspect
172
+ "#<#{self.class.name}>"
173
+ end
174
+ end
175
+
176
+ # @private
63
177
  def __finalize
64
- @logger ||= Rage::Logger.new(nil)
65
- @logger.formatter = @log_formatter if @logger && @log_formatter
66
- @logger.level = @log_level if @logger && @log_level
178
+ if @logger
179
+ @logger.formatter = @log_formatter if @log_formatter
180
+ @logger.level = @log_level if @log_level
181
+ else
182
+ @logger = Rage::Logger.new(nil)
183
+ end
67
184
  end
68
185
  end