363 lines
11 KiB
Go
363 lines
11 KiB
Go
/*
|
||
* TencentBlueKing is pleased to support the open source community by making
|
||
* 蓝鲸智云 - 配置平台 (BlueKing - CMDB) available.
|
||
* Copyright (C) 2025 Tencent. All rights reserved.
|
||
* Licensed under the MIT License (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at http://opensource.org/licenses/MIT
|
||
* Unless required by applicable law or agreed to in writing,
|
||
* software distributed under the License is distributed on
|
||
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||
* either express or implied. See the License for the
|
||
* specific language governing permissions and limitations under the License.
|
||
* We undertake not to change the open source license (MIT license) applicable
|
||
* to the current version of the project delivered to anyone in the future.
|
||
*/
|
||
|
||
package rest
|
||
|
||
import (
|
||
"bytes"
|
||
"context"
|
||
"fmt"
|
||
"net/http"
|
||
"path"
|
||
"strings"
|
||
)
|
||
|
||
type ctxKey string
|
||
|
||
var (
|
||
patternCtxKey = ctxKey("mux.pattern")
|
||
)
|
||
|
||
// Router consisting of the core routing methods using only the standard net/http handler
|
||
type Router interface {
|
||
http.Handler
|
||
|
||
// Use appends one or more middlewares onto the Router stack.
|
||
// Note: This should be used only before any routes are added to the mux.
|
||
Use(middlewares ...func(http.Handler) http.Handler)
|
||
|
||
// With adds inline middlewares for an endpoint handler.
|
||
With(middlewares ...func(http.Handler) http.Handler) Router
|
||
|
||
// Group adds a new inline-Router along the current routing
|
||
// path, with a fresh middleware stack for the inline-Router.
|
||
Group(fn func(r Router)) Router
|
||
|
||
// Route mounts a sub-Router along a `pattern` string.
|
||
Route(pattern string, fn func(r Router)) Router
|
||
|
||
// Mount attaches another http.Handler along `pattern/` string
|
||
Mount(pattern string, h http.Handler)
|
||
|
||
// Handle and HandleFunc adds routes for `pattern{$}` that matches all HTTP methods.
|
||
Handle(pattern string, h http.Handler)
|
||
HandleFunc(pattern string, h http.HandlerFunc)
|
||
|
||
// HTTP-method routing along `pattern{$}`
|
||
Get(pattern string, h http.HandlerFunc)
|
||
Post(pattern string, h http.HandlerFunc)
|
||
Put(pattern string, h http.HandlerFunc)
|
||
Patch(pattern string, h http.HandlerFunc)
|
||
Delete(pattern string, h http.HandlerFunc)
|
||
|
||
// NotFound defines a handler to respond whenever a route could not be found.
|
||
NotFound(h http.HandlerFunc)
|
||
|
||
// MethodNotAllowed defines a handler to respond whenever a method is not allowed.
|
||
MethodNotAllowed(h http.HandlerFunc)
|
||
}
|
||
|
||
// router is a HTTP route multiplexer that parses a request path, base the standard net/http mux
|
||
type router struct {
|
||
// The underlying mux to register the routes to
|
||
mux *http.ServeMux
|
||
// The Subrouter base path
|
||
basePath string
|
||
// The middleware stack
|
||
middlewares []func(http.Handler) http.Handler
|
||
// Custom route not found handler
|
||
notFoundHandler http.HandlerFunc
|
||
// Custom method not allowed handler
|
||
methodNotAllowedHandler http.HandlerFunc
|
||
}
|
||
|
||
// NewRouter returns a new mux object that implements the Router interface.
|
||
func NewRouter() Router {
|
||
r := &router{
|
||
mux: http.NewServeMux(),
|
||
middlewares: make([]func(http.Handler) http.Handler, 0),
|
||
}
|
||
return r
|
||
}
|
||
|
||
// Use appends a middleware handler to the mux middleware stack.
|
||
func (r *router) Use(middlewares ...func(http.Handler) http.Handler) {
|
||
r.middlewares = append(r.middlewares, middlewares...)
|
||
}
|
||
|
||
// With adds inline middlewares for an endpoint handler.
|
||
func (r *router) With(middlewares ...func(http.Handler) http.Handler) Router {
|
||
newRouter := r.clone()
|
||
newRouter.middlewares = append(newRouter.middlewares, middlewares...)
|
||
return newRouter
|
||
}
|
||
|
||
// Group creates a new inline-mux with a copy of middleware stack. It's useful
|
||
// for a group of handlers along the same routing path that use an additional
|
||
// set of middlewares.
|
||
func (r *router) Group(fn func(r Router)) Router {
|
||
newRouter := r.clone()
|
||
if fn != nil {
|
||
fn(newRouter)
|
||
}
|
||
|
||
return newRouter
|
||
}
|
||
|
||
// Route creates a new mux and mounts it along the `pattern` as a subrouter.
|
||
func (r *router) Route(pattern string, fn func(r Router)) Router {
|
||
newRouter := r.clone()
|
||
newRouter.basePath = path.Join(r.basePath, pattern)
|
||
if fn != nil {
|
||
fn(newRouter)
|
||
}
|
||
|
||
return newRouter
|
||
}
|
||
|
||
// Mount attaches another http.Handler or mux Router as a subrouter along a routing
|
||
// path. It's very useful to split up a large API as many independent routers and
|
||
// compose them as a single service using Mount
|
||
func (r *router) Mount(pattern string, handler http.Handler) {
|
||
if pattern == "" || pattern[0] != '/' {
|
||
panic(fmt.Errorf("pattern must begin with /"))
|
||
}
|
||
|
||
basePattern := strings.TrimRight(pattern, "/")
|
||
r.mux.Handle(basePattern+"/", r.stripMountPrefix(basePattern, r.chain(handler)))
|
||
}
|
||
|
||
// stripMountPrefix trims the mount prefix or {var} from the url if present.
|
||
func (r *router) stripMountPrefix(pattern string, handler http.Handler) http.Handler {
|
||
if pattern == "" {
|
||
return handler
|
||
}
|
||
|
||
// add a next / prefix
|
||
count := strings.Count(pattern, "/") + 1
|
||
|
||
f := func(w http.ResponseWriter, req *http.Request) {
|
||
// 忽略路径中的前缀
|
||
skipCount := count
|
||
offset := strings.IndexFunc(req.URL.Path, func(r rune) bool {
|
||
if r == '/' {
|
||
skipCount--
|
||
}
|
||
if skipCount == 0 {
|
||
return true
|
||
}
|
||
return false
|
||
})
|
||
req.URL.Path = req.URL.Path[offset:]
|
||
|
||
// the mount route pattern
|
||
rp := strings.TrimSuffix(RoutePattern(req), "/")
|
||
ctx := context.WithValue(req.Context(), patternCtxKey, rp)
|
||
req = req.WithContext(ctx)
|
||
|
||
handler.ServeHTTP(w, req)
|
||
}
|
||
|
||
return http.HandlerFunc(f)
|
||
}
|
||
|
||
// Handle adds the route `pattern` that matches any http method to
|
||
// execute the `handler` http.Handler.
|
||
func (r *router) Handle(pattern string, handler http.Handler) {
|
||
r.register("", pattern, handler)
|
||
}
|
||
|
||
// HandleFunc adds the route `pattern` that matches any http method to
|
||
// execute the `handlerFn` http.HandlerFunc.
|
||
func (r *router) HandleFunc(pattern string, handler http.HandlerFunc) {
|
||
r.register("", pattern, handler)
|
||
}
|
||
|
||
// Get adds the route `pattern` that matches a GET http method to
|
||
// execute the `handlerFn` http.HandlerFunc.
|
||
func (r *router) Get(pattern string, handler http.HandlerFunc) {
|
||
r.register(http.MethodGet, pattern, handler)
|
||
}
|
||
|
||
// Post adds the route `pattern` that matches a POST http method to
|
||
// execute the `handlerFn` http.HandlerFunc.
|
||
func (r *router) Post(pattern string, handler http.HandlerFunc) {
|
||
r.register(http.MethodPost, pattern, handler)
|
||
}
|
||
|
||
// Put adds the route `pattern` that matches a Put http method to
|
||
// execute the `handlerFn` http.HandlerFunc.
|
||
func (r *router) Put(pattern string, handler http.HandlerFunc) {
|
||
r.register(http.MethodPut, pattern, handler)
|
||
}
|
||
|
||
// Patch adds the route `pattern` that matches a Patch http method to
|
||
// execute the `handlerFn` http.HandlerFunc.
|
||
func (r *router) Patch(pattern string, handler http.HandlerFunc) {
|
||
r.register(http.MethodPatch, pattern, handler)
|
||
}
|
||
|
||
// Delete adds the route `pattern` that matches a Delete http method to
|
||
// execute the `handlerFn` http.HandlerFunc.
|
||
func (r *router) Delete(pattern string, handler http.HandlerFunc) {
|
||
r.register(http.MethodDelete, pattern, handler)
|
||
}
|
||
|
||
// NotFound sets a custom http.HandlerFunc for routing paths that could
|
||
// not be found. The default 404 handler is `http.NotFound`.
|
||
func (r *router) NotFound(h http.HandlerFunc) {
|
||
r.notFoundHandler = h
|
||
}
|
||
|
||
// MethodNotAllowed sets a custom http.HandlerFunc for routing paths where the
|
||
// method is unresolved. The default handler returns a 405 with an empty body.
|
||
func (r *router) MethodNotAllowed(h http.HandlerFunc) {
|
||
r.methodNotAllowedHandler = h
|
||
}
|
||
|
||
// ServeHTTP is the single method of the http.Handler interface that makes
|
||
// Mux interoperable with the standard library
|
||
func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||
// 没有自定义404和405处理函数,直接调用mux的ServeHTTP, 减少一次路由匹配
|
||
if r.notFoundHandler == nil && r.methodNotAllowedHandler == nil {
|
||
r.mux.ServeHTTP(w, req)
|
||
return
|
||
}
|
||
|
||
h, pattern := r.mux.Handler(req)
|
||
|
||
// handle custom 404 and 405 route
|
||
if pattern == "" {
|
||
h = r.serveNotFound(h)
|
||
} else {
|
||
h = r.mux
|
||
}
|
||
|
||
h.ServeHTTP(w, req)
|
||
}
|
||
|
||
func (r *router) serveNotFound(handler http.Handler) http.Handler {
|
||
f := func(w http.ResponseWriter, req *http.Request) {
|
||
wrapper := ¬FoundWrapper{
|
||
header: http.Header{},
|
||
buf: bytes.NewBuffer(nil),
|
||
}
|
||
handler.ServeHTTP(wrapper, req)
|
||
|
||
// 404
|
||
if r.notFoundHandler != nil && wrapper.code == http.StatusNotFound {
|
||
r.notFoundHandler(w, req)
|
||
return
|
||
}
|
||
|
||
// 405
|
||
if r.methodNotAllowedHandler != nil && wrapper.code == http.StatusMethodNotAllowed {
|
||
w.Header().Set("Allow", wrapper.header.Get("Allow"))
|
||
r.methodNotAllowedHandler(w, req)
|
||
return
|
||
}
|
||
|
||
wrapper.ServeHTTP(w, req)
|
||
}
|
||
|
||
return http.HandlerFunc(f)
|
||
}
|
||
|
||
func (r *router) register(method string, pattern string, handler http.Handler) {
|
||
if r.basePath != "" {
|
||
pattern = path.Join(r.basePath, pattern)
|
||
}
|
||
|
||
if strings.HasSuffix(pattern, "/") {
|
||
pattern = pattern + "{$}" // 除开mount, 其他路径都精准匹配
|
||
}
|
||
|
||
if method != "" {
|
||
r.mux.Handle(method+" "+pattern, r.chain(handler))
|
||
} else {
|
||
r.mux.Handle(pattern, r.chain(handler))
|
||
}
|
||
}
|
||
|
||
// chain builds a http.Handler composed of an inline middleware stack and endpoint
|
||
// handler in the order they are passed.
|
||
func (r *router) chain(endpoint http.Handler) http.Handler {
|
||
for i := len(r.middlewares) - 1; i >= 0; i-- {
|
||
endpoint = r.middlewares[i](endpoint)
|
||
}
|
||
|
||
return endpoint
|
||
}
|
||
|
||
func (r *router) clone() *router {
|
||
newMiddlewares := make([]func(http.Handler) http.Handler, len(r.middlewares))
|
||
copy(newMiddlewares, r.middlewares)
|
||
|
||
newRouter := &router{
|
||
mux: r.mux,
|
||
basePath: r.basePath,
|
||
middlewares: newMiddlewares,
|
||
}
|
||
return newRouter
|
||
}
|
||
|
||
// notFoundWrapper that implements the minimal http.ResponseWriter interface.
|
||
type notFoundWrapper struct {
|
||
code int
|
||
header http.Header
|
||
buf *bytes.Buffer
|
||
}
|
||
|
||
// Header implement the http.ResponseWriter interface Header method
|
||
func (nf *notFoundWrapper) Header() http.Header {
|
||
return nf.header
|
||
}
|
||
|
||
// WriteHeader implement the http.ResponseWriter interface WriteHeader method
|
||
func (nf *notFoundWrapper) WriteHeader(code int) {
|
||
nf.code = code
|
||
}
|
||
|
||
// Write implement the http.ResponseWriter interface Write method
|
||
func (nf *notFoundWrapper) Write(buf []byte) (int, error) {
|
||
return nf.buf.Write(buf)
|
||
}
|
||
|
||
// ServeHTTP implement the http.Handler interface ServeHTTP method
|
||
func (nf *notFoundWrapper) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||
for k, v := range nf.header {
|
||
for i := range v {
|
||
w.Header().Add(k, v[i])
|
||
}
|
||
}
|
||
|
||
w.WriteHeader(nf.code)
|
||
w.Write(nf.buf.Bytes())
|
||
}
|
||
|
||
// RoutePattern returns the matched route pattern include mount prefix, but ignore method and host
|
||
func RoutePattern(req *http.Request) string {
|
||
pattern, _ := req.Context().Value(patternCtxKey).(string)
|
||
|
||
// ignore method and host
|
||
i := strings.IndexByte(req.Pattern, '/')
|
||
if i < 0 {
|
||
return pattern
|
||
}
|
||
|
||
return pattern + req.Pattern[i:]
|
||
}
|