Routes not executed in expected order
What version of Hono are you using?
4.8-4.9
What runtime/platform is your app running on? (with version if possible)
Deno
What steps can reproduce the bug?
(Update: See functioning reproduction in comments.)
I'm trying to create a minimalist example but I'm not able to reproduce it. But what I observe is that with routes like this:
#!/usr/bin/env -S deno run --check -N
import { Hono } from "npm:[email protected]"
const app = new Hono()
app.get("/:remaining{.*}", (c) => {
const msg = `regex route: ${c.req.url}`
console.debug(msg)
return c.text(msg)
})
app.get("/", (c) => {
const msg = `root route: ${c.req.url}`
console.debug(msg)
return c.text(msg)
})
Deno.serve({port: 8080}, app.fetch)
Sometimes the first route matches /, and sometimes the second route.
The actual code is here: https://github.com/NfNitLoop/gemi/blob/ff2f11cd293464dc8189b70063518e5169de459c/src/commands/serve.ts#L54-L62
What is the expected behavior?
Hono docs say:
Handlers or middleware will be executed in registration order.
So, I expect deterministic handling of the handlers -- i.e., the root path / will always go to one or the other.
What do you see instead?
On some servers (macOS, Linux) the regex path is always skipped and the / path wins.
But on Windows, the regex path wins. (Though, it gives back an invalid response: undefined for the path param. See: #4384)
Additional information
No response
Hi @NfNitLoop, thanks for the report.
Since /:remaining{.*} has been supported since v4.8.12, it should become a regex route in supported versions and a root route in unsupported versions.
Additionally, when writing the following in a Deno environment, if the package.json and node_modules directories exist in the execution directory, it appears the version installed in node_modules is used instead of 4.9.5 (this was the case on my machine). Such environments may have made reproduction difficult.
import { Hono } from "npm:[email protected]"
should become a regex route in supported versions
I know that the regex route "works generally" in both environments, it's just the edge case of / that behaves differently. So I assume they're both supported versions, but I'll double-check.
Such environments may have made reproduction difficult
TIL! Thanks! I'll check if that was the case. I did notice I'd accidentally mixed jsr/npm versions during some of my testing, so I wonder if I ran into the same issue. I'll try again when I get some time.
supported since v4.8.12
I just realized that my lockfile locks the version to 4.8.2: https://github.com/NfNitLoop/gemi/blob/ff2f11cd293464dc8189b70063518e5169de459c/deno.jsonc#L34
I wonder if Deno isn't using the lockfile when you deno run jsr:@nfnitloop/gemi or deno install ..., so this works on environments that pull in a supported version, and fails in others. But IIRC in my non-minimal reproduction I know that the wildcard matching does work for non-root paths. I'll need to re-test that assumption too.
Update: Confirmed, deno install does not respect the lockfile, so there are likely different versions of libraries installed in different locations. 🤦
OK, I have a small reproduction:
#!/usr/bin/env -S deno run --check -N
import { Hono } from "jsr:@hono/[email protected]"
import { delay } from "jsr:@std/async"
const app = new Hono()
app.get("/:remaining{.*}", (c) => {
const msg = `regex route: ${c.req.url}`
console.debug(msg, c.req.param())
return c.text(msg)
})
app.get("/", (c) => {
const msg = `root route: ${c.req.url}`
console.debug(msg)
return c.text(msg)
})
const port = 8080
Deno.serve({port}, app.fetch)
await delay(500)
await test("/")
await test("/foo")
Deno.exit()
async function test(path: string) {
const response = await fetch(`http://localhost:${port}${path}`)
await response.text()
}
For versions v4.8.2-11, I get:
Listening on http://0.0.0.0:8080/ (http://localhost:8080/)
root route: http://localhost:8080/
regex route: http://localhost:8080/foo { remaining: "foo" }
For version 4.8.12-4.9.6, I get:
regex route: http://localhost:8080/ {}
regex route: http://localhost:8080/foo { remaining: "foo" }
So, the regex pattern was supported previously, it just didn't match empty strings. (It probably should have.) But in 4.8.12 it was updated to match empty strings, but return them as undefined. (to be fixed in #4384)
I probably expected the regex to match empty strings when using 4.8.2, but saw that it didn't match an empty string, and added my additional / pattern to catch that case. But things broke when behavior changed.
The seeming inconsistency between different platforms was actually due to the Deno issue linked above.
If that all sounds right, we can close this as "works as expected".
Hi @NfNitLoop, thanks for the additional confirmation. I apologize for the confusion caused by the behavior changing between versions. That is the expected result.