http.HandleFunc中的通配符模式

huangapple go评论102阅读模式
英文:

Wildcards in the pattern for http.HandleFunc

问题

当注册处理程序时,是否有办法在模式中指定通配符?

例如:

http.HandleFunc("/groups/*/people", peopleInGroupHandler)

其中的 * 可以是任何有效的 URL 字符串。或者,唯一的解决方案是匹配 /groups,然后在处理程序 (peopleInGroupHandler) 函数中解析其余部分?

英文:

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 solution to match /groups and figure the rest out from within the handler (peopleInGroupHandler) func?

答案1

得分: 108

http.Handler和http.HandleFunc的模式不是正则表达式或通配符。没有办法指定通配符。它们在这里有文档。

话虽如此,创建一个可以使用正则表达式或任何其他类型模式的自定义处理程序并不太难。这里有一个使用正则表达式的示例(已编译但未经测试):

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
        }
    }
    // 没有匹配的模式;发送404响应
    http.NotFound(w, r)
}
英文:

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)
}

答案2

得分: 63

自2011年以来,您现在可以(2014+)找到其他解决方案。
例如,Gorilla Web工具包的mux包提供了各种路由选项:

  • 可选的正则表达式的请求路径模式匹配。
  • 基于URL主机和方案、请求方法、标头和查询值的匹配。
  • 基于自定义函数的匹配。
  • 使用子路由进行简单的嵌套路由。

它可以轻松集成到任何BYOR(自带路由器)http库中,例如negroni。

以下是来自文章“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))
}

有时最好不要只是使用另一个“神奇”的包,而是了解底层发生了什么

在这种情况下,“魔术”定义在“gorilla/mux/regexp.go”中,并在此处进行了测试。
其思想是提取命名变量,组装要匹配的正则表达式,创建一个“反向”模板来构建URL,并编译正则表达式以验证在URL构建中使用的变量值。

英文:

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.

答案3

得分: 9

这是一个使用@evanshaw的代码示例的示例:

func handleDigits(res http.ResponseWriter, req *http.Request) {
    res.Write([]byte("URL中的数字\n"))
}

func handleStrings(res http.ResponseWriter, req *http.Request) {
    res.Write([]byte("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)
}
英文:

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)
}

答案4

得分: 7

我只想添加julienschmidt/httprouter,它的行为与net/http类似,但有一个额外的参数用于URL值和对请求方法的支持:

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))
}

它似乎比gorilla/mux稍微受欢迎一些(根据GitHub上的数据),并且还声称需要更少的内存。

https://github.com/julienschmidt/go-http-routing-benchmark

英文:

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

答案5

得分: 1

你可以查看violetear如何处理动态+通配符模式,这只是一个补充示例:

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")

在这种情况下,请求可能有2个不同的UUIDS

对于动态/通配符,可以应用以下内容:

http://api.violetear.org/command/ping/127.0.0.1
                        \______/\___/\________/
                            |     |      |
                             静态      |
                                      动态

可以使用正则表达式来匹配IP:

router.AddRegex("ip", `^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$`)
router.HandleFunc("/command/ping/:ip", ipHandler, "GET")

或者只允许GETHEAD方法的通配符:

router.HandleFunc("/command/ping/*", anyHandler, "GET, HEAD")

更多示例可以在这里找到:https://violetear.org/post/how-it-works/

英文:

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/

答案6

得分: 0

通常在注册处理程序时不指定通配符。

包含通配符的路由在路由表中维护,根据用户输入进行编译,然后传递给适当的处理程序函数。

我在Ben Hoyt关于Go中HTTP路由的博客中发现了他描述和比较自定义技术与第三方技术的内容。我强烈建议任何阅读这篇文章的人都要仔细阅读。此外,Gorilla的mux包现在已经存档

下面的方法基于正则表达式表,我们通过预编译的正则表达式循环遍历并使用请求上下文传递匹配项。

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))
}

示例请求和响应

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

英文:

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

huangapple
  • 本文由 发表于 2011年7月4日 02:00:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/6564558.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定