Слияние кода завершено, страница обновится автоматически
// Copyright 2021 Flamego. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package flamego
import (
"fmt"
"net/http"
"strings"
"github.com/flamego/flamego/internal/route"
)
// Router is the router for adding routes and their handlers.
type Router interface {
// AutoHead sets a boolean value which determines whether to add HEAD method
// automatically when GET method is added. Only routes that are added after call
// of this method will be affected, existing routes remain unchanged.
AutoHead(v bool)
// HandlerWrapper sets handlerWrapper for the router. It is used to wrap Handler
// and inject logic, and is especially useful for wrapping the Handler to
// inject.FastInvoker.
HandlerWrapper(f func(Handler) Handler)
// Route adds the new route path and its handlers to the router tree.
Route(method, routePath string, handlers []Handler) *Route
// Combo returns a ComboRoute for adding handlers of different HTTP methods to
// the same route.
Combo(routePath string, handlers ...Handler) *ComboRoute
// Group pushes a new group with the given route path and its handlers, it then
// pops the group when leaves the scope of `fn`.
Group(routePath string, fn func(), handlers ...Handler)
// Get is a shortcut for `r.Route(http.MethodGet, routePath, handlers)`.
Get(routePath string, handlers ...Handler) *Route
// Patch is a shortcut for `r.Route(http.MethodPatch, routePath, handlers)`.
Patch(routePath string, handlers ...Handler) *Route
// Post is a shortcut for `r.Route(http.MethodPost, routePath, handlers)`.
Post(routePath string, handlers ...Handler) *Route
// Put is a shortcut for `r.Route(http.MethodPut, routePath, handlers)`.
Put(routePath string, handlers ...Handler) *Route
// Delete is a shortcut for `r.Route(http.MethodDelete, routePath, handlers)`.
Delete(routePath string, handlers ...Handler) *Route
// Options is a shortcut for `r.Route(http.MethodOptions, routePath, handlers)`.
Options(routePath string, handlers ...Handler) *Route
// Head is a shortcut for `r.Route(http.MethodHead, routePath, handlers)`.
Head(routePath string, handlers ...Handler) *Route
// Connect is a shortcut for `r.Route(http.MethodConnect, routePath, handlers)`.
Connect(routePath string, handlers ...Handler) *Route
// Trace is a shortcut for `r.Route(http.MethodTrace, routePath, handlers)`.
Trace(routePath string, handlers ...Handler) *Route
// Any is a shortcut for `r.Route("*", routePath, handlers)`.
Any(routePath string, handlers ...Handler) *Route
// Routes is a shortcut of adding same handlers for different HTTP methods.
//
// Example:
// f.Routes("/", "GET,POST", handlers)
Routes(routePath, methods string, handlers ...Handler) *Route
// NotFound configures a http.HandlerFunc to be called when no matching route is
// found. When it is not set, http.NotFound is used. Be sure to set
// http.StatusNotFound as the response status code in your last handler.
NotFound(handlers ...Handler)
// URLPath builds the "path" portion of URL with given pairs of values. To
// include the optional segment, pass `"withOptional", "true"`.
URLPath(name string, pairs ...string) string
// ServeHTTP implements the method of http.Handler.
ServeHTTP(w http.ResponseWriter, req *http.Request)
}
type contextCreator func(http.ResponseWriter, *http.Request, route.Params, []Handler, urlPather) Context
type router struct {
parser *route.Parser // The route parser.
autoHead bool // Whether to automatically attach the same handler of a GET method as HEAD.
groups []group // The living stack of nested route groups.
routeTrees map[string]route.Tree // A set of route trees, keys are HTTP methods.
namedRoutes map[string]route.Leaf // A set of named routes.
staticRoutes map[string]map[string]route.Leaf // A set of static routes, keys are HTTP methods and full route paths.
notFound http.HandlerFunc // The handler to be called when a route has no match.
// contextCreator is used to create new Context for incoming requests.
contextCreator contextCreator
// handlerWrapper is used to wrap Handler and inject logic, and is especially
// useful for wrapping the Handler to inject.FastInvoker.
handlerWrapper func(Handler) Handler
}
// httpMethods is a list of HTTP methods defined in IETF RFC 7231 and RFC 5789.
var httpMethods = []string{
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodDelete,
http.MethodPatch,
http.MethodOptions,
http.MethodHead,
http.MethodConnect,
http.MethodTrace,
}
// newRouter creates and returns a new Router.
func newRouter(contextCreator contextCreator) Router {
parser, err := route.NewParser()
if err != nil {
panic("new parser: " + err.Error())
}
r := &router{
parser: parser,
routeTrees: make(map[string]route.Tree),
namedRoutes: make(map[string]route.Leaf),
staticRoutes: make(map[string]map[string]route.Leaf),
contextCreator: contextCreator,
}
for _, m := range httpMethods {
r.routeTrees[m] = route.NewTree()
r.staticRoutes[m] = make(map[string]route.Leaf)
}
r.NotFound(http.NotFound)
return r
}
func (r *router) AutoHead(v bool) {
r.autoHead = v
}
func (r *router) HandlerWrapper(f func(Handler) Handler) {
r.handlerWrapper = f
}
// Route is a wrapper of the route leaf and its router.
type Route struct {
router *router
leaf route.Leaf
}
// Name sets the name for the route.
func (r *Route) Name(name string) {
if name == "" {
panic("empty route name")
} else if _, ok := r.router.namedRoutes[name]; ok {
panic("duplicated route name: " + name)
}
r.router.namedRoutes[name] = r.leaf
}
func (r *router) addRoute(method, routePath string, handler route.Handler) *Route {
method = strings.ToUpper(method)
var methods []string
if method == "*" {
methods = httpMethods
} else {
for _, m := range httpMethods {
if method == m {
methods = []string{method}
break
}
}
}
if len(methods) == 0 {
panic("unknown HTTP method: " + method)
}
ast, err := r.parser.Parse(routePath)
if err != nil {
panic(fmt.Sprintf("unable to parse route %q: %v", routePath, err))
}
var leaf route.Leaf
for _, m := range methods {
leaf, err = route.AddRoute(r.routeTrees[m], ast, handler)
if err != nil {
panic(fmt.Sprintf("unable to add route %q with method %s: %v", routePath, m, err))
}
if leaf.Static() {
r.staticRoutes[m][leaf.Route()] = leaf
}
}
return &Route{
router: r,
leaf: leaf,
}
}
// group contains information of a nested routing group.
type group struct {
path string
handlers []Handler
}
func (r *router) Route(method, routePath string, handlers []Handler) *Route {
if len(r.groups) > 0 {
groupPath := ""
hs := make([]Handler, 0)
for _, g := range r.groups {
groupPath += g.path
hs = append(hs, g.handlers...)
}
routePath = groupPath + routePath
handlers = append(hs, handlers...)
}
validateAndWrapHandlers(handlers, r.handlerWrapper)
return r.addRoute(method, routePath, func(w http.ResponseWriter, req *http.Request, params route.Params) {
r.contextCreator(w, req, params, handlers, r.URLPath).run()
})
}
func (r *router) Group(routePath string, fn func(), handlers ...Handler) {
r.groups = append(r.groups,
group{
path: routePath,
handlers: handlers,
},
)
fn()
r.groups = r.groups[:len(r.groups)-1]
}
func (r *router) Get(routePath string, handlers ...Handler) *Route {
route := r.Route(http.MethodGet, routePath, handlers)
if r.autoHead {
r.Head(routePath, handlers...)
}
return route
}
func (r *router) Patch(routePath string, handlers ...Handler) *Route {
return r.Route(http.MethodPatch, routePath, handlers)
}
func (r *router) Post(routePath string, handlers ...Handler) *Route {
return r.Route(http.MethodPost, routePath, handlers)
}
func (r *router) Put(routePath string, handlers ...Handler) *Route {
return r.Route(http.MethodPut, routePath, handlers)
}
func (r *router) Delete(routePath string, handlers ...Handler) *Route {
return r.Route(http.MethodDelete, routePath, handlers)
}
func (r *router) Options(routePath string, handlers ...Handler) *Route {
return r.Route(http.MethodOptions, routePath, handlers)
}
func (r *router) Head(routePath string, handlers ...Handler) *Route {
return r.Route(http.MethodHead, routePath, handlers)
}
func (r *router) Connect(routePath string, handlers ...Handler) *Route {
return r.Route(http.MethodConnect, routePath, handlers)
}
func (r *router) Trace(routePath string, handlers ...Handler) *Route {
return r.Route(http.MethodTrace, routePath, handlers)
}
func (r *router) Any(routePath string, handlers ...Handler) *Route {
return r.Route("*", routePath, handlers)
}
func (r *router) Routes(routePath, methods string, handlers ...Handler) *Route {
if methods == "" {
panic("empty methods")
}
var route *Route
for _, m := range strings.Split(methods, ",") {
route = r.Route(strings.TrimSpace(m), routePath, handlers)
}
return route
}
func (r *router) NotFound(handlers ...Handler) {
validateAndWrapHandlers(handlers, r.handlerWrapper)
r.notFound = func(w http.ResponseWriter, req *http.Request) {
r.contextCreator(w, req, nil, handlers, r.URLPath).run()
}
}
func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Fast path for static routes
leaf, ok := r.staticRoutes[req.Method][req.URL.Path]
if ok {
leaf.Handler()(w, req, route.Params{
"route": leaf.Route(),
})
return
}
routeTree, ok := r.routeTrees[req.Method]
if !ok {
r.notFound(w, req)
return
}
leaf, params, ok := routeTree.Match(req.URL.Path)
if !ok {
r.notFound(w, req)
return
}
params["route"] = leaf.Route()
leaf.Handler()(w, req, params)
}
func (r *router) URLPath(name string, pairs ...string) string {
leaf, ok := r.namedRoutes[name]
if !ok {
panic("route with given name does not exist: " + name)
}
vals := make(map[string]string, len(pairs)/2)
for i := 1; i < len(pairs); i += 2 {
vals[pairs[i-1]] = pairs[i]
}
withOptional := false
if vals["withOptional"] == "true" {
withOptional = true
delete(vals, "withOptional")
}
return leaf.URLPath(vals, withOptional)
}
// Combo creates and returns new ComboRoute with common handlers for the route.
func (r *router) Combo(routePath string, handlers ...Handler) *ComboRoute {
return &ComboRoute{
router: r,
routePath: routePath,
handlers: handlers,
added: make(map[string]struct{}),
}
}
// ComboRoute is a wrapper of the router for adding handlers of different HTTP
// methods to the same route.
type ComboRoute struct {
router *router
routePath string
handlers []Handler
added map[string]struct{}
lastRoute *Route
}
func (r *ComboRoute) route(fn func(string, ...Handler) *Route, method string, handlers ...Handler) *ComboRoute {
_, ok := r.added[method]
if ok {
panic(fmt.Sprintf("duplicated method %q for route %q", method, r.routePath))
}
r.added[method] = struct{}{}
r.lastRoute = fn(r.routePath, append(r.handlers, handlers...)...)
return r
}
// Get adds handlers of the GET method to the route.
func (r *ComboRoute) Get(handlers ...Handler) *ComboRoute {
return r.route(r.router.Get, http.MethodGet, handlers...)
}
// Patch adds handlers of the PATCH method to the route.
func (r *ComboRoute) Patch(handlers ...Handler) *ComboRoute {
return r.route(r.router.Patch, http.MethodPatch, handlers...)
}
// Post adds handlers of the POST method to the route.
func (r *ComboRoute) Post(handlers ...Handler) *ComboRoute {
return r.route(r.router.Post, http.MethodPost, handlers...)
}
// Put adds handlers of the PUT method to the route.
func (r *ComboRoute) Put(handlers ...Handler) *ComboRoute {
return r.route(r.router.Put, http.MethodPut, handlers...)
}
// Delete adds handlers of the DELETE method to the route.
func (r *ComboRoute) Delete(handlers ...Handler) *ComboRoute {
return r.route(r.router.Delete, http.MethodDelete, handlers...)
}
// Options adds handlers of the OPTIONS method to the route.
func (r *ComboRoute) Options(handlers ...Handler) *ComboRoute {
return r.route(r.router.Options, http.MethodOptions, handlers...)
}
// Head adds handlers of the HEAD method to the route.
func (r *ComboRoute) Head(handlers ...Handler) *ComboRoute {
return r.route(r.router.Head, http.MethodHead, handlers...)
}
// Connect adds handlers of the CONNECT method to the route.
func (r *ComboRoute) Connect(handlers ...Handler) *ComboRoute {
return r.route(r.router.Connect, http.MethodConnect, handlers...)
}
// Trace adds handlers of the TRACE method to the route.
func (r *ComboRoute) Trace(handlers ...Handler) *ComboRoute {
return r.route(r.router.Trace, http.MethodTrace, handlers...)
}
// Name sets the name for the route.
func (r *ComboRoute) Name(name string) {
if r.lastRoute == nil {
panic("no route has been added")
}
r.lastRoute.Name(name)
}
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.