Skip to content

Commit 1243b34

Browse files
committed
Fix #recognize inconsistency with #serve
When routing a request, `#find_routes` will iterate through potential routes for the request and yield them to the passed block. `#serve` and `#recognize` both used `#find_routes`, but ended up handling things slightly differently. Both methods would modify the `script_name` and `path_info` of the request if the current route is not anchored. However, only `#serve` used `#chomp` and would restore the request's original `script_name` and `path_info` after each iteration. This inconsistency can cause issues as `#recognize` can end up not matching them even though `#serve` would. This commit fixes the issue by unifying more of the implementations of `#serve` and `#recognize`. Now, `#find_routes` implements the mutation/restoration of request params for both `#serve` and `#recognize` so that their behavior is consistent and request mutation isn't leaked between iterations. One test had to be changed because it depended on the request mutation leaking outside of `#recognize`, so it was updated to make assertions during `#recognize` instead.
1 parent 972a52d commit 1243b34

File tree

4 files changed

+41
-38
lines changed

4 files changed

+41
-38
lines changed

actionpack/lib/action_dispatch/journey/router.rb

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,45 +27,20 @@ def eager_load!
2727
end
2828

2929
def serve(req)
30-
find_routes(req) do |match, parameters, route|
31-
set_params = req.path_parameters
32-
path_info = req.path_info
33-
script_name = req.script_name
34-
35-
unless route.path.anchored
36-
req.script_name = (script_name.to_s + match.to_s).chomp("/")
37-
req.path_info = match.post_match
38-
req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
39-
end
40-
30+
find_routes(req) do |route, parameters|
4131
req.path_parameters = parameters
4232
req.route = route
4333

4434
_, headers, _ = response = route.app.serve(req)
4535

46-
if headers[Constants::X_CASCADE] == "pass"
47-
req.script_name = script_name
48-
req.path_info = path_info
49-
req.path_parameters = set_params
50-
next
51-
end
52-
53-
return response
36+
return response unless headers[Constants::X_CASCADE] == "pass"
5437
end
5538

5639
[404, { Constants::X_CASCADE => "pass" }, ["Not Found"]]
5740
end
5841

59-
def recognize(rails_req)
60-
find_routes(rails_req) do |match, parameters, route|
61-
unless route.path.anchored
62-
rails_req.script_name = match.to_s
63-
rails_req.path_info = match.post_match
64-
rails_req.path_info = "/" + rails_req.path_info unless rails_req.path_info.start_with? "/"
65-
end
66-
67-
yield(route, parameters)
68-
end
42+
def recognize(rails_req, &block)
43+
find_routes(rails_req, &block)
6944
end
7045

7146
def visualizer
@@ -100,7 +75,10 @@ def filter_routes(path)
10075
end
10176

10277
def find_routes(req)
103-
path_info = req.path_info
78+
req_params = req.path_parameters
79+
path_info = req.path_info
80+
script_name = req.script_name
81+
10482
routes = filter_routes(path_info)
10583

10684
custom_routes.each { |r|
@@ -122,7 +100,7 @@ def find_routes(req)
122100
routes.each do |r|
123101
match_data = r.path.match(path_info)
124102

125-
path_parameters = req.path_parameters.merge r.defaults
103+
path_parameters = req_params.merge r.defaults
126104

127105
index = 1
128106
match_data.names.each do |name|
@@ -137,7 +115,21 @@ def find_routes(req)
137115
end
138116
index += 1
139117
end
140-
yield [match_data, path_parameters, r]
118+
119+
if r.path.anchored
120+
yield(r, path_parameters)
121+
else
122+
req.script_name = (script_name.to_s + match_data.to_s).chomp("/")
123+
req.path_info = match_data.post_match
124+
req.path_info = "/" + req.path_info unless req.path_info.start_with? "/"
125+
126+
yield(r, path_parameters)
127+
128+
req.script_name = script_name
129+
req.path_info = path_info
130+
end
131+
132+
req.path_parameters = req_params
141133
end
142134
end
143135

actionpack/lib/action_dispatch/routing/route_set.rb

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -932,7 +932,6 @@ def recognize_path_with_request(req, path, extras, raise_on_missing: true)
932932
params[key] = URI::RFC2396_PARSER.unescape(value)
933933
end
934934
end
935-
original_path_parameters = req.path_parameters
936935
req.path_parameters = params
937936
app = route.app
938937
if app.matches?(req) && app.dispatcher?
@@ -946,8 +945,6 @@ def recognize_path_with_request(req, path, extras, raise_on_missing: true)
946945
elsif app.matches?(req) && app.engine?
947946
path_parameters = app.rack_app.routes.recognize_path_with_request(req, path, extras, raise_on_missing: false)
948947
return path_parameters if path_parameters
949-
else
950-
req.path_parameters = original_path_parameters
951948
end
952949
end
953950

actionpack/test/dispatch/routing_assertions_test.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ def self.name
7171
mount root_engine => "/"
7272

7373
get "/shelf/foo", controller: "query_articles", action: "index"
74+
75+
get "*path", to: "symbols#index", constraints: ->(request) do
76+
request.fullpath == "/request_mutated"
77+
end
7478
end
7579
end
7680

@@ -181,6 +185,10 @@ def test_assert_recognizes_continue_to_recognize_after_it_tried_engines
181185
assert_recognizes({ controller: "query_articles", action: "index" }, "/shelf/foo")
182186
end
183187

188+
def test_assert_recognizes_doesnt_mutate_request
189+
assert_recognizes({ controller: "symbols", action: "index", path: "request_mutated" }, "/request_mutated")
190+
end
191+
184192
def test_assert_routing
185193
assert_routing("/articles", controller: "articles", action: "index")
186194
end

actionpack/test/journey/router_test.rb

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,16 @@ def test_recognize_with_unbound_regexp
147147

148148
env = rails_env "PATH_INFO" => "/foo/bar"
149149

150-
router.recognize(env) { |*_| }
150+
recognized = false
151+
152+
router.recognize(env) do |*_|
153+
assert_equal "/foo", env.env["SCRIPT_NAME"]
154+
assert_equal "/bar", env.env["PATH_INFO"]
155+
156+
recognized = true
157+
end
151158

152-
assert_equal "/foo", env.env["SCRIPT_NAME"]
153-
assert_equal "/bar", env.env["PATH_INFO"]
159+
assert recognized
154160
end
155161

156162
def test_bound_regexp_keeps_path_info

0 commit comments

Comments
 (0)