Skip to content

Commit 807e3d1

Browse files
committed
Preserve empty PATH_INFO for mounts
Allow PATH_INFO to remain an empty string when a request is made for the exact prefix name of a mount, e.g. a request to "/settings" for this route: ``` mount ->(env) { [200, {}, [env["PATH_INFO"]] }, at: "/settings" ``` This ensures that the Rack environment can be used to reconstruct the same path as used for the original request (e.g. "/settings" instead of "/settings/" for the example above). To ensure compatibility for routers used within mounts, consider an empty string to be equivalent to "/" for the purposes of route matching. This allows a router to be mounted and have it root route be used when a request is made to the exact mount prefix without any trailing slash: ``` mounted_router = Hanami::Router.new do root to: ->(env) { [200, {}, ["Hello from root"]] } end mount mounted_router, at: "/settings" ``` In the above example, a request to /settings will still serve the route returning "Hello from root".
1 parent 9fd499a commit 807e3d1

File tree

5 files changed

+52
-5
lines changed

5 files changed

+52
-5
lines changed

lib/hanami/router.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,12 @@ def recognize(env, params = {}, options = {})
642642
# @since 2.0.0
643643
# @api private
644644
def fixed(env)
645-
@fixed.dig(env[::Rack::REQUEST_METHOD], env[::Rack::PATH_INFO])
645+
path_info = env[::Rack::PATH_INFO]
646+
# Treat empty PATH_INFO as "/" for route matching. This allows root routes (defined as "/") to
647+
# match the empty PATH_INFO that is set for requests to a mount without a trailing slash.
648+
path_info = DEFAULT_PREFIX if path_info == EMPTY_STRING
649+
650+
@fixed.dig(env[::Rack::REQUEST_METHOD], path_info)
646651
end
647652

648653
# @since 2.0.0

lib/hanami/router/mounted_path.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ def endpoint_and_params(env)
2020
matched_path = match.to_s
2121
env[::Rack::SCRIPT_NAME] = env[::Rack::SCRIPT_NAME].to_s + matched_path
2222
env[::Rack::PATH_INFO] = env[::Rack::PATH_INFO].sub(matched_path, EMPTY_STRING)
23-
env[::Rack::PATH_INFO] = DEFAULT_PREFIX if env[::Rack::PATH_INFO] == EMPTY_STRING
2423
end
2524

2625
[@app, match.named_captures]

spec/integration/hanami/router/mount_spec.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,47 @@
9898
end
9999
end
100100
end
101+
102+
context "PATH_INFO handling" do
103+
let(:router) do
104+
root_endpoint = ->(env) {
105+
path_info = env[Rack::PATH_INFO]
106+
[200, rack_headers({"Content-Length" => path_info.bytesize.to_s}), [path_info]]
107+
}
108+
articles_endpoint = ->(_env) {
109+
[200, rack_headers({"Content-Length" => "9"}), ["/articles"]]
110+
}
111+
112+
mounted_router = Hanami::Router.new do
113+
get "/", to: root_endpoint
114+
get "/articles", to: articles_endpoint
115+
end
116+
117+
Hanami::Router.new do
118+
mount mounted_router, at: "/api"
119+
end
120+
end
121+
let(:app) { Rack::MockRequest.new(router) }
122+
123+
it "preserves empty PATH_INFO and matches the root route when request matches mount point exactly" do
124+
response = app.request("GET", "/api", lint: true)
125+
126+
expect(response.status).to eq(200)
127+
expect(response.body).to eq("")
128+
end
129+
130+
it "preserves slash PATH_INFO and matches the root route when request has trailing slash" do
131+
response = app.request("GET", "/api/", lint: true)
132+
133+
expect(response.status).to eq(200)
134+
expect(response.body).to eq("/")
135+
end
136+
137+
it "passes PATH_INFO for sub-paths" do
138+
response = app.request("GET", "/api/articles", lint: true)
139+
140+
expect(response.status).to eq(200)
141+
expect(response.body).to eq("/articles")
142+
end
143+
end
101144
end

spec/support/fixtures.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ module Api
211211
class App
212212
def call(env)
213213
case env["PATH_INFO"]
214-
when "/"
214+
when ""
215215
[200, {}, ["home"]]
216216
when "/articles"
217217
[200, {}, ["articles"]]

spec/unit/hanami/router/mounted_path_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,12 @@
5858
expect(env[Rack::PATH_INFO]).to eq("/orders")
5959
end
6060

61-
it "uses a slash for the PATH_INFO if it would otherwise be empty" do
61+
it "uses an empty string for the PATH_INFO when the request matches the mount point exactly" do
6262
env.merge!(Rack::PATH_INFO => "/api")
6363

6464
subject.endpoint_and_params(env)
6565

66-
expect(env[Rack::PATH_INFO]).to eq("/")
66+
expect(env[Rack::PATH_INFO]).to eq("")
6767
end
6868
end
6969

0 commit comments

Comments
 (0)