When registering handlers, is there any way to specify wildcards in the pattern?
For example:
http.HandleFunc("/groups/*/people", peopleInGroupHandler)
Where the *
could be any valid URL string. Or is the only开发者_如何学C solution to match /groups
and figure the rest out from within the handler (peopleInGroupHandler
) func?
The patterns for http.Handler and http.HandleFunc aren't regular expressions or globs. There isn't a way to specify wildcards. They're documented here.
That said, it's not too hard to create your own handler that can use regular expressions or any other kind of pattern you want. Here's one that uses regular expressions (compiled, but not tested):
type route struct {
pattern *regexp.Regexp
handler http.Handler
}
type RegexpHandler struct {
routes []*route
}
func (h *RegexpHandler) Handler(pattern *regexp.Regexp, handler http.Handler) {
h.routes = append(h.routes, &route{pattern, handler})
}
func (h *RegexpHandler) HandleFunc(pattern *regexp.Regexp, handler func(http.ResponseWriter, *http.Request)) {
h.routes = append(h.routes, &route{pattern, http.HandlerFunc(handler)})
}
func (h *RegexpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
for _, route := range h.routes {
if route.pattern.MatchString(r.URL.Path) {
route.handler.ServeHTTP(w, r)
return
}
}
// no pattern matched; send 404 response
http.NotFound(w, r)
}
Since 2011, you can now (2014+) find other solutions.
For instance, the mux package of the Gorilla Web toolkit provides all kind of routing options:
- Pattern matching on request paths, with optional regular expressions.
- Matching on URL host and scheme, request method, header and query values.
- Matching based on custom functions.
- Use of sub-routers for easy nested routing.
It can be easily integrated to any BYOR (Bring your own Router) http library, like negroni.
Here is an example from the article "Gorilla vs Pat vs Routes: A Mux Showdown":
package main
import (
"github.com/gorilla/mux"
"log"
"net/http"
)
func main() {
rtr := mux.NewRouter()
rtr.HandleFunc("/user/{name:[a-z]+}/profile", profile).Methods("GET")
http.Handle("/", rtr)
log.Println("Listening...")
http.ListenAndServe(":3000", nil)
}
func profile(w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
name := params["name"]
w.Write([]byte("Hello " + name))
}
Sometimes better not to just use yet another "magic" package, but understand what's going on under the hood
In this instance, the "magic" is defined in "gorilla/mux/regexp.go
", and tested here.
The idea is to extract named variables, assemble a regexp to be matched, create a "reverse" template to build URLs and compile regexps to validate variable values used in URL building.
Here's an example of how to use the code example from @evanshaw
func handleDigits(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("Digits in the URL\n"))
}
func handleStrings(res http.ResponseWriter, req *http.Request) {
res.Write([]byte("Strings in the URL\n"))
}
func main() {
handler := &RegexpHandler{}
reg1, _ := regexp.Compile("/foo-\\d+")
handler.HandleFunc(reg1, handleDigits)
reg2, _ := regexp.Compile("/foo-\\w+")
handler.HandleFunc(reg2, handleStrings)
http.ListenAndServe(":3000", handler)
}
I just wanted to add julienschmidt/httprouter
, which just behaves like net/http
but with an additional parameter for url-values and support for request methods:
https://github.com/julienschmidt/httprouter
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"net/http"
"log"
)
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\n")
}
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}
func main() {
router := httprouter.New()
router.GET("/", Index)
router.GET("/hello/:name", Hello)
log.Fatal(http.ListenAndServe(":8080", router))
}
It also seems to be slightly more popular than gorilla/mux
(according to GitHub) and it also claims to need less memory.
https://github.com/julienschmidt/go-http-routing-benchmark
You could check how violetear handles dynamic + catchall (wildcard) patterns, this is just for complement for example:
uuid := `[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}`
router.AddRegex(":uuid")
router.HandleFunc("/test/:uuid/:uuid", handleUUID, "GET,HEAD")
In this case, the request may have 2 different UUIDS
For a dynamic/wildcard this could apply:
http://api.violetear.org/command/ping/127.0.0.1
\______/\___/\________/
| | |
static |
dynamic
A regex may be used to match the IP:
router.AddRegex(":ip", `^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`)
router.HandleFunc("/command/ping/:ip", ipHandler, "GET")
Or simply just a catch all allowing GET
and HEAD
methods only:
router.HandleFunc("/command/ping/*", anyHandler, "GET, HEAD")
More examples can be found here: https://violetear.org/post/how-it-works/
Wildcards are usually not specified when registering handlers.
Routes containing wildcards are maintained in a route table, compiled based on user input, and then passed to the appropriate handler function.
I came across Ben Hoyt's blog on HTTP routing in Go in which he describes and compares custom techniques with third-party ones. I would highly recommend anyone reading this to go through it. Also, Gorilla'x mux package is now archived
The below approach is based on regex table, in which we loop through pre-compiled regexes and pass matches using the request context.
package main
import (
"context"
"fmt"
"net/http"
"regexp"
"strings"
)
var routes = []route{
newRoute("GET", "/", home),
newRoute("GET", "/([^/]+)", sink),
newRoute("GET", "/groups/([^/]+)/people", peopleInGroupHandler),
}
func newRoute(method, pattern string, handler http.HandlerFunc) route {
return route{method, regexp.MustCompile("^" + pattern + "$"), handler}
}
type route struct {
method string
regex *regexp.Regexp
handler http.HandlerFunc
}
func Serve(w http.ResponseWriter, r *http.Request) {
var allow []string
for _, route := range routes {
matches := route.regex.FindStringSubmatch(r.URL.Path)
if len(matches) > 0 {
if r.Method != route.method {
allow = append(allow, route.method)
continue
}
ctx := context.WithValue(r.Context(), ctxKey{}, matches[1:])
route.handler(w, r.WithContext(ctx))
return
}
}
if len(allow) > 0 {
w.Header().Set("Allow", strings.Join(allow, ", "))
http.Error(w, "405 method not allowed", http.StatusMethodNotAllowed)
return
}
http.NotFound(w, r)
}
type ctxKey struct{}
func getField(r *http.Request, index int) string {
fields := r.Context().Value(ctxKey{}).([]string)
return fields[index]
}
func home(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "HOME\n")
}
func peopleInGroupHandler(w http.ResponseWriter, r *http.Request) {
slug := getField(r, 0)
fmt.Fprintf(w, "Group handler: %s\n", slug)
}
func sink(w http.ResponseWriter, r *http.Request) {
slug := getField(r, 0)
fmt.Fprintf(w, "Sink %s\n", slug)
}
func main() {
http.ListenAndServe("127.0.0.1:8080", http.HandlerFunc(Serve))
}
Example requests and responses
curl -X GET http://127.0.0.1:8080
HOME
curl -X GET http://127.0.0.1:8080/
HOME
curl -X GET http://127.0.0.1:8080/temp
Sink temp
curl -X GET http://127.0.0.1:8080/groups/south-park/people
People in group south-park
curl -X GET http://127.0.0.1:8080/groups/6/people
People in group 6
精彩评论