actionpack 8.1.0.beta1 → 8.1.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 +105 -1
- data/lib/abstract_controller/base.rb +2 -1
- data/lib/abstract_controller/helpers.rb +1 -1
- data/lib/action_controller/api.rb +1 -0
- data/lib/action_controller/base.rb +1 -0
- data/lib/action_controller/log_subscriber.rb +11 -3
- data/lib/action_controller/metal/live.rb +9 -18
- data/lib/action_controller/metal/rate_limiting.rb +9 -3
- data/lib/action_controller/metal/redirecting.rb +45 -9
- data/lib/action_controller/railtie.rb +25 -2
- data/lib/action_controller/structured_event_subscriber.rb +112 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +55 -1
- data/lib/action_dispatch/http/url.rb +11 -11
- data/lib/action_dispatch/journey/gtg/transition_table.rb +9 -7
- data/lib/action_dispatch/log_subscriber.rb +7 -3
- data/lib/action_dispatch/middleware/remote_ip.rb +9 -4
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +3 -0
- data/lib/action_dispatch/railtie.rb +4 -0
- data/lib/action_dispatch/routing/inspector.rb +79 -59
- data/lib/action_dispatch/routing/mapper.rb +4 -2
- data/lib/action_dispatch/routing/redirection.rb +10 -7
- data/lib/action_dispatch/routing/routes_proxy.rb +1 -0
- data/lib/action_dispatch/structured_event_subscriber.rb +20 -0
- data/lib/action_dispatch/testing/integration.rb +3 -4
- data/lib/action_dispatch/testing/request_encoder.rb +9 -9
- data/lib/action_dispatch.rb +8 -0
- data/lib/action_pack/gem_version.rb +1 -1
- metadata +12 -10
|
@@ -202,24 +202,24 @@ module ActionDispatch
|
|
|
202
202
|
|
|
203
203
|
def build_host_url(host, port, protocol, options, path)
|
|
204
204
|
if match = host.match(HOST_REGEXP)
|
|
205
|
-
|
|
206
|
-
host
|
|
207
|
-
port
|
|
205
|
+
protocol_from_host = match[1] if protocol.nil?
|
|
206
|
+
host = match[2]
|
|
207
|
+
port = match[3] unless options.key? :port
|
|
208
208
|
end
|
|
209
209
|
|
|
210
|
-
protocol = normalize_protocol
|
|
210
|
+
protocol = protocol_from_host || normalize_protocol(protocol).dup
|
|
211
211
|
host = normalize_host(host, options)
|
|
212
|
+
port = normalize_port(port, protocol)
|
|
212
213
|
|
|
213
|
-
result = protocol
|
|
214
|
+
result = protocol
|
|
214
215
|
|
|
215
216
|
if options[:user] && options[:password]
|
|
216
217
|
result << "#{Rack::Utils.escape(options[:user])}:#{Rack::Utils.escape(options[:password])}@"
|
|
217
218
|
end
|
|
218
219
|
|
|
219
220
|
result << host
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
221
|
+
|
|
222
|
+
result << ":" << port.to_s if port
|
|
223
223
|
|
|
224
224
|
result.concat path
|
|
225
225
|
end
|
|
@@ -265,11 +265,11 @@ module ActionDispatch
|
|
|
265
265
|
return unless port
|
|
266
266
|
|
|
267
267
|
case protocol
|
|
268
|
-
when "//" then
|
|
268
|
+
when "//" then port
|
|
269
269
|
when "https://"
|
|
270
|
-
|
|
270
|
+
port unless port.to_i == 443
|
|
271
271
|
else
|
|
272
|
-
|
|
272
|
+
port unless port.to_i == 80
|
|
273
273
|
end
|
|
274
274
|
end
|
|
275
275
|
end
|
|
@@ -13,7 +13,6 @@ module ActionDispatch
|
|
|
13
13
|
attr_reader :memos
|
|
14
14
|
|
|
15
15
|
DEFAULT_EXP = /[^.\/?]+/
|
|
16
|
-
DEFAULT_EXP_ANCHORED = /\A#{DEFAULT_EXP}\Z/
|
|
17
16
|
|
|
18
17
|
def initialize
|
|
19
18
|
@stdparam_states = {}
|
|
@@ -111,10 +110,10 @@ module ActionDispatch
|
|
|
111
110
|
end
|
|
112
111
|
|
|
113
112
|
{
|
|
114
|
-
regexp_states: simple_regexp,
|
|
115
|
-
string_states: @string_states,
|
|
116
|
-
stdparam_states: @stdparam_states,
|
|
117
|
-
accepting: @accepting
|
|
113
|
+
regexp_states: simple_regexp.stringify_keys,
|
|
114
|
+
string_states: @string_states.stringify_keys,
|
|
115
|
+
stdparam_states: @stdparam_states.stringify_keys,
|
|
116
|
+
accepting: @accepting.stringify_keys
|
|
118
117
|
}
|
|
119
118
|
end
|
|
120
119
|
|
|
@@ -193,12 +192,15 @@ module ActionDispatch
|
|
|
193
192
|
end
|
|
194
193
|
|
|
195
194
|
def transitions
|
|
195
|
+
# double escaped because dot evaluates escapes
|
|
196
|
+
default_exp_anchored = "\\\\A#{DEFAULT_EXP.source}\\\\Z"
|
|
197
|
+
|
|
196
198
|
@string_states.flat_map { |from, hash|
|
|
197
199
|
hash.map { |s, to| [from, s, to] }
|
|
198
200
|
} + @stdparam_states.map { |from, to|
|
|
199
|
-
[from,
|
|
201
|
+
[from, default_exp_anchored, to]
|
|
200
202
|
} + @regexp_states.flat_map { |from, hash|
|
|
201
|
-
hash.map { |
|
|
203
|
+
hash.map { |r, to| [from, r.source.gsub("\\") { "\\\\" }, to] }
|
|
202
204
|
}
|
|
203
205
|
end
|
|
204
206
|
end
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# :markup: markdown
|
|
4
|
-
|
|
5
3
|
module ActionDispatch
|
|
6
|
-
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
4
|
+
class LogSubscriber < ActiveSupport::LogSubscriber # :nodoc:
|
|
5
|
+
class_attribute :backtrace_cleaner, default: ActiveSupport::BacktraceCleaner.new
|
|
6
|
+
|
|
7
7
|
def redirect(event)
|
|
8
8
|
payload = event.payload
|
|
9
9
|
|
|
10
10
|
info { "Redirected to #{payload[:location]}" }
|
|
11
11
|
|
|
12
|
+
if ActionDispatch.verbose_redirect_logs
|
|
13
|
+
info { "↳ #{payload[:source_location]}" }
|
|
14
|
+
end
|
|
15
|
+
|
|
12
16
|
info do
|
|
13
17
|
status = payload[:status]
|
|
14
18
|
|
|
@@ -44,6 +44,8 @@ module ActionDispatch
|
|
|
44
44
|
"10.0.0.0/8", # private IPv4 range 10.x.x.x
|
|
45
45
|
"172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
|
|
46
46
|
"192.168.0.0/16", # private IPv4 range 192.168.x.x
|
|
47
|
+
"169.254.0.0/16", # link-local IPv4 range 169.254.x.x
|
|
48
|
+
"fe80::/10", # link-local IPv6 range fe80::/10
|
|
47
49
|
].map { |proxy| IPAddr.new(proxy) }
|
|
48
50
|
|
|
49
51
|
attr_reader :check_ip, :proxies
|
|
@@ -126,11 +128,11 @@ module ActionDispatch
|
|
|
126
128
|
# left, which was presumably set by one of those proxies.
|
|
127
129
|
def calculate_ip
|
|
128
130
|
# Set by the Rack web server, this is a single value.
|
|
129
|
-
remote_addr = ips_from(@req.remote_addr).last
|
|
131
|
+
remote_addr = sanitize_ips(ips_from(@req.remote_addr)).last
|
|
130
132
|
|
|
131
133
|
# Could be a CSV list and/or repeated headers that were concatenated.
|
|
132
|
-
client_ips = ips_from(@req.client_ip).reverse!
|
|
133
|
-
forwarded_ips =
|
|
134
|
+
client_ips = sanitize_ips(ips_from(@req.client_ip)).reverse!
|
|
135
|
+
forwarded_ips = sanitize_ips(@req.forwarded_for || []).reverse!
|
|
134
136
|
|
|
135
137
|
# `Client-Ip` and `X-Forwarded-For` should not, generally, both be set. If they
|
|
136
138
|
# are both set, it means that either:
|
|
@@ -176,7 +178,10 @@ module ActionDispatch
|
|
|
176
178
|
def ips_from(header) # :doc:
|
|
177
179
|
return [] unless header
|
|
178
180
|
# Split the comma-separated list into an array of strings.
|
|
179
|
-
|
|
181
|
+
header.strip.split(/[,\s]+/)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def sanitize_ips(ips) # :doc:
|
|
180
185
|
ips.select! do |ip|
|
|
181
186
|
# Only return IPs that are valid according to the IPAddr#new method.
|
|
182
187
|
range = IPAddr.new(ip).to_range
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
<main role="main" id="container">
|
|
12
12
|
<h2>
|
|
13
13
|
<%= h @exception.message %>
|
|
14
|
+
<% if defined?(ActionText) && @exception.message.match?(%r{#{ActionText::RichText.table_name}}) %>
|
|
15
|
+
<br />To resolve this issue run: bin/rails action_text:install
|
|
16
|
+
<% end %>
|
|
14
17
|
<% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
|
|
15
18
|
<br />To resolve this issue run: bin/rails active_storage:install
|
|
16
19
|
<% end %>
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
<% end %>
|
|
5
5
|
|
|
6
6
|
<%= @exception.message %>
|
|
7
|
+
<% if defined?(ActionText) && @exception.message.match?(%r{#{ActionText::RichText.table_name}}) %>
|
|
8
|
+
To resolve this issue run: bin/rails action_text:install
|
|
9
|
+
<% end %>
|
|
7
10
|
<% if defined?(ActiveStorage) && @exception.message.match?(%r{#{ActiveStorage::Blob.table_name}|#{ActiveStorage::Attachment.table_name}}) %>
|
|
8
11
|
To resolve this issue run: bin/rails active_storage:install
|
|
9
12
|
<% end %>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
require "action_dispatch"
|
|
6
6
|
require "action_dispatch/log_subscriber"
|
|
7
|
+
require "action_dispatch/structured_event_subscriber"
|
|
7
8
|
require "active_support/messages/rotation_configuration"
|
|
8
9
|
|
|
9
10
|
module ActionDispatch
|
|
@@ -33,6 +34,7 @@ module ActionDispatch
|
|
|
33
34
|
|
|
34
35
|
config.action_dispatch.ignore_leading_brackets = nil
|
|
35
36
|
config.action_dispatch.strict_query_string_separator = nil
|
|
37
|
+
config.action_dispatch.verbose_redirect_logs = false
|
|
36
38
|
|
|
37
39
|
config.action_dispatch.default_headers = {
|
|
38
40
|
"X-Frame-Options" => "SAMEORIGIN",
|
|
@@ -66,6 +68,8 @@ module ActionDispatch
|
|
|
66
68
|
ActionDispatch::QueryParser.strict_query_string_separator = app.config.action_dispatch.strict_query_string_separator
|
|
67
69
|
end
|
|
68
70
|
|
|
71
|
+
ActionDispatch.verbose_redirect_logs = app.config.action_dispatch.verbose_redirect_logs
|
|
72
|
+
|
|
69
73
|
ActiveSupport.on_load(:action_dispatch_request) do
|
|
70
74
|
self.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
|
|
71
75
|
ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
|
|
@@ -64,6 +64,14 @@ module ActionDispatch
|
|
|
64
64
|
def engine?
|
|
65
65
|
app.engine?
|
|
66
66
|
end
|
|
67
|
+
|
|
68
|
+
def to_h
|
|
69
|
+
{ name: name,
|
|
70
|
+
verb: verb,
|
|
71
|
+
path: path,
|
|
72
|
+
reqs: reqs,
|
|
73
|
+
source_location: source_location }
|
|
74
|
+
end
|
|
67
75
|
end
|
|
68
76
|
|
|
69
77
|
##
|
|
@@ -72,33 +80,51 @@ module ActionDispatch
|
|
|
72
80
|
# not use this class.
|
|
73
81
|
class RoutesInspector # :nodoc:
|
|
74
82
|
def initialize(routes)
|
|
75
|
-
@
|
|
76
|
-
@
|
|
83
|
+
@routes = wrap_routes(routes)
|
|
84
|
+
@engines = load_engines_routes
|
|
77
85
|
end
|
|
78
86
|
|
|
79
87
|
def format(formatter, filter = {})
|
|
80
|
-
|
|
81
|
-
routes = collect_routes(routes_to_display)
|
|
82
|
-
if routes.none?
|
|
83
|
-
formatter.no_routes(collect_routes(@routes), filter)
|
|
84
|
-
return formatter.result
|
|
85
|
-
end
|
|
88
|
+
all_routes = { nil => @routes }.merge(@engines)
|
|
86
89
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
@engines.each do |name, engine_routes|
|
|
91
|
-
formatter.section_title "Routes for #{name}"
|
|
92
|
-
if engine_routes.any?
|
|
93
|
-
formatter.header engine_routes
|
|
94
|
-
formatter.section engine_routes
|
|
95
|
-
end
|
|
90
|
+
all_routes.each do |engine_name, routes|
|
|
91
|
+
format_routes(formatter, filter, engine_name, routes)
|
|
96
92
|
end
|
|
97
93
|
|
|
98
94
|
formatter.result
|
|
99
95
|
end
|
|
100
96
|
|
|
101
97
|
private
|
|
98
|
+
def format_routes(formatter, filter, engine_name, routes)
|
|
99
|
+
routes = filter_routes(routes, normalize_filter(filter)).map(&:to_h)
|
|
100
|
+
|
|
101
|
+
formatter.section_title "Routes for #{engine_name || "application"}" if @engines.any?
|
|
102
|
+
if routes.any?
|
|
103
|
+
formatter.header routes
|
|
104
|
+
formatter.section routes
|
|
105
|
+
else
|
|
106
|
+
formatter.no_routes engine_name, routes, filter
|
|
107
|
+
end
|
|
108
|
+
formatter.footer routes
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def wrap_routes(routes)
|
|
112
|
+
routes.routes.map { |route| RouteWrapper.new(route) }.reject(&:internal?)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def load_engines_routes
|
|
116
|
+
engine_routes = @routes.select(&:engine?)
|
|
117
|
+
|
|
118
|
+
engines = engine_routes.to_h do |engine_route|
|
|
119
|
+
engine_app_routes = engine_route.rack_app.routes
|
|
120
|
+
engine_app_routes = engine_app_routes.routes if engine_app_routes.is_a?(ActionDispatch::Routing::RouteSet)
|
|
121
|
+
|
|
122
|
+
[engine_route.endpoint, wrap_routes(engine_app_routes)]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
engines
|
|
126
|
+
end
|
|
127
|
+
|
|
102
128
|
def normalize_filter(filter)
|
|
103
129
|
if filter[:controller]
|
|
104
130
|
{ controller: /#{filter[:controller].underscore.sub(/_?controller\z/, "")}/ }
|
|
@@ -118,39 +144,13 @@ module ActionDispatch
|
|
|
118
144
|
end
|
|
119
145
|
end
|
|
120
146
|
|
|
121
|
-
def filter_routes(filter)
|
|
147
|
+
def filter_routes(routes, filter)
|
|
122
148
|
if filter
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
filter.any? { |filter_type, value| route_wrapper.matches_filter?(filter_type, value) }
|
|
149
|
+
routes.select do |route|
|
|
150
|
+
filter.any? { |filter_type, value| route.matches_filter?(filter_type, value) }
|
|
126
151
|
end
|
|
127
152
|
else
|
|
128
|
-
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def collect_routes(routes)
|
|
133
|
-
routes.collect do |route|
|
|
134
|
-
RouteWrapper.new(route)
|
|
135
|
-
end.reject(&:internal?).collect do |route|
|
|
136
|
-
collect_engine_routes(route)
|
|
137
|
-
|
|
138
|
-
{ name: route.name,
|
|
139
|
-
verb: route.verb,
|
|
140
|
-
path: route.path,
|
|
141
|
-
reqs: route.reqs,
|
|
142
|
-
source_location: route.source_location }
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def collect_engine_routes(route)
|
|
147
|
-
name = route.endpoint
|
|
148
|
-
return unless route.engine?
|
|
149
|
-
return if @engines[name]
|
|
150
|
-
|
|
151
|
-
routes = route.rack_app.routes
|
|
152
|
-
if routes.is_a?(ActionDispatch::Routing::RouteSet)
|
|
153
|
-
@engines[name] = collect_routes(routes.routes)
|
|
153
|
+
routes
|
|
154
154
|
end
|
|
155
155
|
end
|
|
156
156
|
end
|
|
@@ -174,27 +174,36 @@ module ActionDispatch
|
|
|
174
174
|
def header(routes)
|
|
175
175
|
end
|
|
176
176
|
|
|
177
|
-
def
|
|
178
|
-
|
|
179
|
-
if routes.none?
|
|
180
|
-
<<~MESSAGE
|
|
181
|
-
You don't have any routes defined!
|
|
177
|
+
def footer(routes)
|
|
178
|
+
end
|
|
182
179
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
180
|
+
def no_routes(engine, routes, filter)
|
|
181
|
+
@buffer <<
|
|
182
|
+
if filter.key?(:controller)
|
|
186
183
|
"No routes were found for this controller."
|
|
187
184
|
elsif filter.key?(:grep)
|
|
188
185
|
"No routes were found for this grep pattern."
|
|
186
|
+
elsif routes.none?
|
|
187
|
+
if engine
|
|
188
|
+
"No routes defined."
|
|
189
|
+
else
|
|
190
|
+
<<~MESSAGE
|
|
191
|
+
You don't have any routes defined!
|
|
192
|
+
|
|
193
|
+
Please add some routes in config/routes.rb.
|
|
194
|
+
MESSAGE
|
|
195
|
+
end
|
|
189
196
|
end
|
|
190
197
|
|
|
191
|
-
|
|
198
|
+
unless engine
|
|
199
|
+
@buffer << "For more information about routes, see the Rails guide: https://guides.rubyonrails.org/routing.html."
|
|
200
|
+
end
|
|
192
201
|
end
|
|
193
202
|
end
|
|
194
203
|
|
|
195
204
|
class Sheet < Base
|
|
196
205
|
def section_title(title)
|
|
197
|
-
@buffer << "
|
|
206
|
+
@buffer << "#{title}:"
|
|
198
207
|
end
|
|
199
208
|
|
|
200
209
|
def section(routes)
|
|
@@ -205,6 +214,10 @@ module ActionDispatch
|
|
|
205
214
|
@buffer << draw_header(routes)
|
|
206
215
|
end
|
|
207
216
|
|
|
217
|
+
def footer(routes)
|
|
218
|
+
@buffer << ""
|
|
219
|
+
end
|
|
220
|
+
|
|
208
221
|
private
|
|
209
222
|
def draw_section(routes)
|
|
210
223
|
header_lengths = ["Prefix", "Verb", "URI Pattern"].map(&:length)
|
|
@@ -235,13 +248,17 @@ module ActionDispatch
|
|
|
235
248
|
end
|
|
236
249
|
|
|
237
250
|
def section_title(title)
|
|
238
|
-
@buffer << "
|
|
251
|
+
@buffer << "#{"[ #{title} ]"}"
|
|
239
252
|
end
|
|
240
253
|
|
|
241
254
|
def section(routes)
|
|
242
255
|
@buffer << draw_expanded_section(routes)
|
|
243
256
|
end
|
|
244
257
|
|
|
258
|
+
def footer(routes)
|
|
259
|
+
@buffer << ""
|
|
260
|
+
end
|
|
261
|
+
|
|
245
262
|
private
|
|
246
263
|
def draw_expanded_section(routes)
|
|
247
264
|
routes.map.each_with_index do |r, i|
|
|
@@ -272,7 +289,7 @@ module ActionDispatch
|
|
|
272
289
|
super
|
|
273
290
|
end
|
|
274
291
|
|
|
275
|
-
def no_routes(routes, filter)
|
|
292
|
+
def no_routes(engine, routes, filter)
|
|
276
293
|
@buffer <<
|
|
277
294
|
if filter.none?
|
|
278
295
|
"No unused routes found."
|
|
@@ -303,6 +320,9 @@ module ActionDispatch
|
|
|
303
320
|
def header(routes)
|
|
304
321
|
end
|
|
305
322
|
|
|
323
|
+
def footer(routes)
|
|
324
|
+
end
|
|
325
|
+
|
|
306
326
|
def no_routes(*)
|
|
307
327
|
@buffer << <<~MESSAGE
|
|
308
328
|
<p>You don't have any routes defined!</p>
|
|
@@ -1948,8 +1948,10 @@ module ActionDispatch
|
|
|
1948
1948
|
end
|
|
1949
1949
|
|
|
1950
1950
|
scope_options = options.slice!(*RESOURCE_OPTIONS)
|
|
1951
|
-
|
|
1952
|
-
|
|
1951
|
+
scope_options[:shallow] = shallow unless shallow.nil?
|
|
1952
|
+
|
|
1953
|
+
unless scope_options.empty?
|
|
1954
|
+
scope(**scope_options) do
|
|
1953
1955
|
public_send(method, resources.pop, **options, &block)
|
|
1954
1956
|
end
|
|
1955
1957
|
return true
|
|
@@ -12,9 +12,10 @@ module ActionDispatch
|
|
|
12
12
|
class Redirect < Endpoint # :nodoc:
|
|
13
13
|
attr_reader :status, :block
|
|
14
14
|
|
|
15
|
-
def initialize(status, block)
|
|
15
|
+
def initialize(status, block, source_location)
|
|
16
16
|
@status = status
|
|
17
17
|
@block = block
|
|
18
|
+
@source_location = source_location
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
def redirect?; true; end
|
|
@@ -27,6 +28,7 @@ module ActionDispatch
|
|
|
27
28
|
payload[:status] = @status
|
|
28
29
|
payload[:location] = response.headers["Location"]
|
|
29
30
|
payload[:request] = request
|
|
31
|
+
payload[:source_location] = @source_location if @source_location
|
|
30
32
|
|
|
31
33
|
response.to_a
|
|
32
34
|
end
|
|
@@ -202,16 +204,17 @@ module ActionDispatch
|
|
|
202
204
|
# get 'accounts/:name' => redirect(SubdomainRedirector.new('api'))
|
|
203
205
|
#
|
|
204
206
|
def redirect(*args, &block)
|
|
205
|
-
options
|
|
206
|
-
status
|
|
207
|
-
path
|
|
207
|
+
options = args.extract_options!
|
|
208
|
+
status = options.delete(:status) || 301
|
|
209
|
+
path = args.shift
|
|
210
|
+
source_location = caller[0] if ActionDispatch.verbose_redirect_logs
|
|
208
211
|
|
|
209
|
-
return OptionRedirect.new(status, options) if options.any?
|
|
210
|
-
return PathRedirect.new(status, path) if String === path
|
|
212
|
+
return OptionRedirect.new(status, options, source_location) if options.any?
|
|
213
|
+
return PathRedirect.new(status, path, source_location) if String === path
|
|
211
214
|
|
|
212
215
|
block = path if path.respond_to? :call
|
|
213
216
|
raise ArgumentError, "redirection argument not supported" unless block
|
|
214
|
-
Redirect.new status, block
|
|
217
|
+
Redirect.new status, block, source_location
|
|
215
218
|
end
|
|
216
219
|
end
|
|
217
220
|
end
|
|
@@ -54,6 +54,7 @@ module ActionDispatch
|
|
|
54
54
|
# dependent part.
|
|
55
55
|
def merge_script_names(previous_script_name, new_script_name)
|
|
56
56
|
return new_script_name unless previous_script_name
|
|
57
|
+
new_script_name = new_script_name.chomp("/")
|
|
57
58
|
|
|
58
59
|
resolved_parts = new_script_name.count("/")
|
|
59
60
|
previous_parts = previous_script_name.count("/")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActionDispatch
|
|
4
|
+
class StructuredEventSubscriber < ActiveSupport::StructuredEventSubscriber # :nodoc:
|
|
5
|
+
def redirect(event)
|
|
6
|
+
payload = event.payload
|
|
7
|
+
status = payload[:status]
|
|
8
|
+
|
|
9
|
+
emit_event("action_dispatch.redirect", {
|
|
10
|
+
location: payload[:location],
|
|
11
|
+
status: status,
|
|
12
|
+
status_name: Rack::Utils::HTTP_STATUS_CODES[status],
|
|
13
|
+
duration_ms: event.duration.round(2),
|
|
14
|
+
source_location: payload[:source_location]
|
|
15
|
+
})
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
ActionDispatch::StructuredEventSubscriber.attach_to :action_dispatch
|
|
@@ -604,9 +604,8 @@ module ActionDispatch
|
|
|
604
604
|
# end
|
|
605
605
|
# end
|
|
606
606
|
#
|
|
607
|
-
# See the [request helpers documentation]
|
|
608
|
-
#
|
|
609
|
-
# on how to use `get`, etc.
|
|
607
|
+
# See the [request helpers documentation](rdoc-ref:ActionDispatch::Integration::RequestHelpers)
|
|
608
|
+
# for help on how to use `get`, etc.
|
|
610
609
|
#
|
|
611
610
|
# ### Changing the request encoding
|
|
612
611
|
#
|
|
@@ -622,7 +621,7 @@ module ActionDispatch
|
|
|
622
621
|
# end
|
|
623
622
|
#
|
|
624
623
|
# assert_response :success
|
|
625
|
-
# assert_equal({ id
|
|
624
|
+
# assert_equal({ "id" => Article.last.id, "title" => "Ahoy!" }, response.parsed_body)
|
|
626
625
|
# end
|
|
627
626
|
# end
|
|
628
627
|
#
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# :markup: markdown
|
|
4
4
|
|
|
5
5
|
require "nokogiri"
|
|
6
|
+
require "action_dispatch/http/mime_type"
|
|
6
7
|
|
|
7
8
|
module ActionDispatch
|
|
8
9
|
class RequestEncoder # :nodoc:
|
|
@@ -15,9 +16,9 @@ module ActionDispatch
|
|
|
15
16
|
|
|
16
17
|
@encoders = { identity: IdentityEncoder.new }
|
|
17
18
|
|
|
18
|
-
attr_reader :response_parser
|
|
19
|
+
attr_reader :response_parser, :content_type
|
|
19
20
|
|
|
20
|
-
def initialize(mime_name, param_encoder, response_parser)
|
|
21
|
+
def initialize(mime_name, param_encoder, response_parser, content_type)
|
|
21
22
|
@mime = Mime[mime_name]
|
|
22
23
|
|
|
23
24
|
unless @mime
|
|
@@ -27,10 +28,7 @@ module ActionDispatch
|
|
|
27
28
|
|
|
28
29
|
@response_parser = response_parser || -> body { body }
|
|
29
30
|
@param_encoder = param_encoder || :"to_#{@mime.symbol}".to_proc
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def content_type
|
|
33
|
-
@mime.to_s
|
|
31
|
+
@content_type = content_type || @mime.to_s
|
|
34
32
|
end
|
|
35
33
|
|
|
36
34
|
def accept_header
|
|
@@ -50,11 +48,13 @@ module ActionDispatch
|
|
|
50
48
|
@encoders[name] || @encoders[:identity]
|
|
51
49
|
end
|
|
52
50
|
|
|
53
|
-
def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil)
|
|
54
|
-
@encoders[mime_name] = new(mime_name, param_encoder, response_parser)
|
|
51
|
+
def self.register_encoder(mime_name, param_encoder: nil, response_parser: nil, content_type: nil)
|
|
52
|
+
@encoders[mime_name] = new(mime_name, param_encoder, response_parser, content_type)
|
|
55
53
|
end
|
|
56
54
|
|
|
57
|
-
register_encoder :html, response_parser: -> body { Rails::Dom::Testing.html_document.parse(body) }
|
|
55
|
+
register_encoder :html, response_parser: -> body { Rails::Dom::Testing.html_document.parse(body) },
|
|
56
|
+
param_encoder: -> param { param },
|
|
57
|
+
content_type: Mime[:url_encoded_form].to_s
|
|
58
58
|
register_encoder :json, response_parser: -> body { JSON.parse(body, object_class: ActiveSupport::HashWithIndifferentAccess) }
|
|
59
59
|
end
|
|
60
60
|
end
|
data/lib/action_dispatch.rb
CHANGED
|
@@ -138,6 +138,14 @@ module ActionDispatch
|
|
|
138
138
|
|
|
139
139
|
autoload :SystemTestCase, "action_dispatch/system_test_case"
|
|
140
140
|
|
|
141
|
+
##
|
|
142
|
+
# :singleton-method:
|
|
143
|
+
#
|
|
144
|
+
# Specifies if the methods calling redirects in controllers and routes should
|
|
145
|
+
# be logged below their relevant log lines. Defaults to false.
|
|
146
|
+
singleton_class.attr_accessor :verbose_redirect_logs
|
|
147
|
+
self.verbose_redirect_logs = false
|
|
148
|
+
|
|
141
149
|
def eager_load!
|
|
142
150
|
super
|
|
143
151
|
Routing.eager_load!
|