http.HandleFunc 模式中的通配符

发布于 2024-11-18 10:09:42 字数 255 浏览 4 评论 0原文

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

例如:

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

羁拥 2024-11-25 10:09:42

http.Handler 和 http.HandleFunc 的模式不是正则表达式或 glob。没有办法指定通配符。它们记录在此处

也就是说,创建您自己的可以使用正则表达式或您想要的任何其他类型的模式的处理程序并不太难。这是使用正则表达式的一个(已编译,但未经测试):

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

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)
}
狠疯拽 2024-11-25 10:09:42

自 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.

冰雪之触 2024-11-25 10:09:42

以下是如何使用 @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)
}

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)
}
浮萍、无处依 2024-11-25 10:09:42

我只是想添加 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

您的好友蓝忘机已上羡 2024-11-25 10:09:42

您可以检查 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
                        \______/\___/\________/
                            |     |      |
                             static      |
                                      dynamic

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

router.AddRegex(":ip", `^(?:[0-9]{1,3}\.){3}[0-9]{1,3}

您可以检查 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
                        \______/\___/\________/
                            |     |      |
                             static      |
                                      dynamic

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

) router.HandleFunc("/command/ping/:ip", ipHandler, "GET")

或者只是一个捕获所有允许 GET 和仅限 HEAD 方法:

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}

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.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/

倾城花音 2024-11-25 10:09:42

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

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

我偶然发现 Ben Hoyt 关于 Go 中 HTTP 路由的博客,其中他描述了自定义技术并将其与第三方的。我强烈建议任何阅读本文的人都仔细阅读它。另外,Gorilla'x 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 + "
quot;), 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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文