Подтвердить что ты не робот

Подстановочные знаки в шаблоне для http.HandleFunc

При регистрации обработчиков в Go (language) есть ли способ указать подстановочные знаки в шаблоне?

Например:

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

Где * может быть любая допустимая строка URL. Или это единственное решение для соответствия /groups и вычисление остального изнутри обработчика (peopleInGroupHandler) func?

4b9b3361

Ответ 1

Шаблоны для 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
        }
    }
    // no pattern matched; send 404 response
    http.NotFound(w, r)
}

Ответ 2

С 2011 года вы можете (2014+) найти другие решения.
Например, пакет пакета мультиязыков Gorilla Web предлагает все виды маршрутизации:

  • Согласование шаблонов на путях запросов с необязательными регулярными выражениями.
  • Соответствие хосту и схеме URL, методу запроса, значениям заголовка и запроса.
  • Соответствие на основе пользовательских функций.
  • Использование под-маршрутизаторов для легкой вложенной маршрутизации.

Он может быть легко интегрирован в любую http-библиотеку BYOR (Bring your own Router), как negroni.

Вот пример из статьи "Gorilla vs Pat vs Routes: 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-адреса.

Ответ 3

Я просто хотел добавить 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

Ответ 4

Вы можете проверить, как violetear обрабатывает шаблоны dynamic + catchall (wildcard), это просто для дополнения, например:

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}$`)
router.HandleFunc("/command/ping/:ip", ipHandler, "GET")

Или просто просто поймать все, что позволяет только методы GET и HEAD:

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

Дополнительные примеры можно найти здесь: https://violetear.org/post/how-it-works/

Ответ 5

Вот пример того, как использовать пример кода из @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)
}