Wildcard route patterns
It’s also possible to define route patterns that contain wildcard segments. You can use these to create more flexible routing rules, and also to pass variables to your Go application via a request URL. If you’ve built web applications using frameworks in other languages before, the concepts in this chapter will probably feel familiar to you.
Let’s step away from our application build for a moment to explain how it works.
Wildcard segments in a route pattern are denoted by an wildcard identifier inside {}
brackets. Like this:
In this example, the route pattern contains two wildcard segments. The first segment has the identifier category
and the second has the identifier itemID
.
The matching rules for route patterns containing wildcard segments are the same as we saw in the previous chapter, with the additional rule that the request path can contain any non-empty value for the wildcard segments. So, for example, the following requests would all match the route we defined above:
Inside your handler, you can retrieve the corresponding value for a wildcard segment using its identifier and the r.PathValue()
method. For example:
The r.PathValue()
method always returns a string
value, and it’s important to remember that this can be any value that the user includes in the URL — so you should validate or sanity check the value before doing anything important with it.
Using wildcard segments in practice
OK, let’s get back to our application and update it to include a new {id}
wildcard segment in the /snippet/view
route, so that our routes look like this:
Route pattern | Handler | Action |
---|---|---|
/{$} | home | Display the home page |
/snippet/view/{id} | snippetView | Display a specific snippet |
/snippet/create | snippetCreate | Display a form for creating a new snippet |
Open up your main.go
file, and make this change like so:
Now let’s head to our snippetView
handler and update it to retrieve the id
value from the request URL path. Later in the book, we’ll use this id
value to select a specific snippet from a database, but for now we’ll just echo the id
back to the user as part of the HTTP response.
Because the id
value is untrusted user input, we should validate it to make sure it’s sane and sensible before we use it. For the purpose of our application we want to check that the id
value contains a positive integer, which we can do by trying to convert the string value to an integer with the strconv.Atoi()
function and then checking that the value is greater than zero.
Here’s how:
Save your changes, restart the application, then open your browser and try visiting a URL like http://localhost:4000/snippet/view/123
. You should see a response containing the echoed id
wildcard value from the request URL, similar to this:
You might also like to try visiting some URLs which have invalid values for the id
wildcard, or no wildcard value at all. For instance:
http://localhost:4000/snippet/view/
http://localhost:4000/snippet/view/-1
http://localhost:4000/snippet/view/foo
For all these requests you should get a 404 page not found
response.
Additional information
Precedence and conflicts
When defining route patterns with wildcard segments, it’s possible that some of your patterns will ‘overlap’. For example, if you define routes with the patterns "/post/edit"
and "/post/{id}"
they overlap because an incoming HTTP request with the path /post/edit
is a valid match for both patterns.
When route patterns overlap, Go’s servemux needs to decide which pattern takes precedent so it can dispatch the request to the appropriate handler.
The rule for this is very neat and succinct: the most specific route pattern wins. Formally, Go defines a pattern as more specific than another if it matches only a subset of requests that the other pattern matches.
Continuing with the example above, the route pattern "/post/edit"
only matches requests with the exact path /post/edit
, whereas the pattern "/post/{id}"
matches requests with the path /post/edit
, /post/123
, /post/abc
and many more. Therefore "/post/edit"
is the more specific route pattern and will take precedent.
While we’re on this topic, there are a few other things worth mentioning:
A nice side-effect of the most specific pattern wins rule is that you can register patterns in any order and it won’t change how the servemux behaves.
There is a potential edge case where you have two overlapping route patterns but neither one is obviously more specific than the other. For example, the patterns
"/post/new/{id}"
and"/post/{author}/latest"
overlap because they both match the request path/post/new/latest
, but it’s not clear which one should take precedence. In this scenario, Go’s servemux considers the patterns to conflict, and will panic at runtime when initializing the routes.Just because Go’s servemux supports overlapping routes, it doesn’t mean that you should use them! Having overlapping route patterns increases the risk of bugs and unintended behavior in your application, and if you have the freedom to design the URL structure for your application it’s generally good practice to keep overlaps to a minimum or avoid them completely.
Subtree path patterns with wildcards
It’s important to understand that the routing rules we described in the previous chapter still apply, even when you’re using wildcard segments. In particular, if your route pattern ends in a trailing slash and has no {$}
at the end, then it is treated as a subtree path pattern and it only requires the start of a request URL path to match.
So, if you have a subtree path pattern like "/user/{id}/"
in your routes (note the trailing slash), this pattern will match requests like /user/1/
, /user/2/a
, /user/2/a/b/c
and so on.
Again, if you don’t want that behavior, stick a {$}
at the end — like "/user/{id}/{$}"
.
Remainder wildcards
Wildcards in route patterns normally match a single, non-empty, segment of the request path only. But there is one special case.
If a route pattern ends with a wildcard, and this final wildcard identifier ends in ...
, then the wildcard will match any and all remaining segments of a request path.
For example, if you declare a route pattern like "/post/{path...}"
it will match requests like /post/a
, /post/a/b
, /post/a/b/c
and so on — very much like a subtree path pattern does. But the difference is that you can access the entire wildcard part via the r.PathValue()
method in your handlers. In this example, you could get the wildcard value for {path...}
by calling r.PathValue("path")
.