go_dreamfactory/lego/sys/gin/engine/engine.go
2023-05-11 17:42:57 +08:00

512 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package engine
import (
"html/template"
"net"
"net/http"
"path"
"strings"
"sync"
"go_dreamfactory/lego/sys/gin/render"
"go_dreamfactory/lego/sys/log"
"go_dreamfactory/lego/utils/codec"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
/*
默认文件上传的最大尺寸
*/
const defaultMultipartMemory = 32 << 20 // 32 MB
/*
默认可信代理
*/
var defaultTrustedCIDRs = []*net.IPNet{
{ // 0.0.0.0/0 (IPv4)
IP: net.IP{0x0, 0x0, 0x0, 0x0},
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0},
},
{ // ::/0 (IPv6)
IP: net.IP{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
Mask: net.IPMask{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
},
}
func NewEngine(log log.ILogger) (engine *Engine) {
engine = &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
log: log,
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);",
trustedProxies: []string{"0.0.0.0/0"},
trustedCIDRs: defaultTrustedCIDRs,
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return
}
var (
default404Body = []byte("404 page not found")
default405Body = []byte("405 method not allowed")
)
var mimePlain = []string{MIMEPlain}
type Engine struct {
RouterGroup
log log.ILogger
UseRawPath bool
/*
如果启用,路由器尝试修复当前请求路径,如果没有
如果没有
已为其注册句柄。
第一个多余的路径元素,如 ../ 或 // 被删除。
之后路由器对清理后的路径进行不区分大小写的查找。
如果可以找到该路由的句柄,则路由器进行重定向
到正确的路径GET 请求的状态码为 301而 GET 请求的状态码为 307
所有其他请求方法。
例如 /FOO 和 /..//Foo 可以重定向到 /foo。
RedirectTrailingSlash 与此选项无关。
*/
RedirectFixedPath bool
/*
如果为真,路径值将不转义
*/
UnescapePathValues bool
/*
如果当前路由无法匹配,但启用自动重定向
带有(不带)尾部斜杠的路径的处理程序存在。
例如,如果 /foo/ 被请求,但路由只存在于 /foo
对于 GET 请求,客户端被重定向到 /foohttp 状态码为 301
对于所有其他请求方法,则为 307。
*/
RedirectTrailingSlash bool //
/*
如果启用,路由器检查是否允许其他方法
当前路由,如果当前请求无法路由。
如果是这种情况,则使用“不允许的方法”回答请求
和 HTTP 状态码 405。
如果不允许其他方法,则将请求委托给 NotFound
处理程序。
*/
HandleMethodNotAllowed bool
/*
可以从 URL 中解析出一个参数,即使带有额外的斜杠。
*/
RemoveExtraSlash bool
/*
TrustedPlatform 如果设置为值 gin.Platform* 的常量,则信任由设置的标头
那个平台比如判断客户端IP
*/
TrustedPlatform string
/*
ForwardedByClientIP 如果启用,客户端 IP 将从请求的标头中解析
匹配存储在 `(*gin.Engine).RemoteIPHeaders` 中的那些。 如果没有 IP
fetched, 它回退到从获取的 IP
`(*gin.Context).Request.RemoteAddr`。
ForwardedByClientIP 布尔值
*/
ForwardedByClientIP bool
/*
RemoteIPHeaders 用于获取客户端 IP 时的 headers 列表
`(*gin.Engine).ForwardedByClientIP` 为 `true` 并且
`(*gin.Context).Request.RemoteAddr` 被至少一个匹配
由 `(*gin.Engine).SetTrustedProxies()` 定义的列表的网络来源。
*/
RemoteIPHeaders []string
/*
文件上传的最大尺寸
*/
MaxMultipartMemory int64
/*
是否使用H2C
*/
UseH2C bool
delims render.Delims
secureJSONPrefix string
HTMLRender render.HTMLRender
FuncMap template.FuncMap
noRoute HandlersChain
noMethod HandlersChain
allNoRoute HandlersChain
allNoMethod HandlersChain
pool sync.Pool
trees methodTrees
maxParams uint16
maxSections uint16
trustedProxies []string
trustedCIDRs []*net.IPNet
}
func (this *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := this.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
this.handleHTTPRequest(c)
this.pool.Put(c)
}
func (this *Engine) Handler() http.Handler {
if !this.UseH2C {
return this
}
h2s := &http2.Server{}
return h2c.NewHandler(this, h2s)
}
/*
使用中间件
*/
func (this *Engine) Use(middleware ...HandlerFunc) IRoutes {
this.RouterGroup.Use(middleware...)
this.rebuild404Handlers()
this.rebuild405Handlers()
return this
}
/*
LoadHTMLGlob 加载由 glob 模式标识的 HTML 文件
并将结果与 HTML 渲染器相关联。
*/
func (this *Engine) LoadHTMLGlob(pattern string) {
left := this.delims.Left
right := this.delims.Right
templ := template.Must(template.New("").Delims(left, right).Funcs(this.FuncMap).ParseGlob(pattern))
if this.log.Enabled(log.DebugLevel) {
this.debugPrintLoadTemplate(templ)
this.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: this.FuncMap, Delims: this.delims}
return
}
this.SetHTMLTemplate(templ)
}
/*
LoadHTMLFiles 加载一段 HTML 文件
并将结果与 HTML 渲染器相关联。
*/
func (this *Engine) LoadHTMLFiles(files ...string) {
if this.log.Enabled(log.DebugLevel) {
this.HTMLRender = render.HTMLDebug{Files: files, FuncMap: this.FuncMap, Delims: this.delims}
return
}
templ := template.Must(template.New("").Delims(this.delims.Left, this.delims.Right).Funcs(this.FuncMap).ParseFiles(files...))
this.SetHTMLTemplate(templ)
}
func (this *Engine) SetHTMLTemplate(templ *template.Template) {
if len(this.trees) > 0 {
this.log.Warnf(`Since SetHTMLTemplate() is NOT thread-safe. It should only be called
at initialization. ie. before any route is registered or the router is listening in a socket:
router := gin.Default()
router.SetHTMLTemplate(template) // << good place
`)
}
this.HTMLRender = render.HTMLProduction{Template: templ.Funcs(this.FuncMap)}
}
/*
设置template FuncMap
*/
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {
engine.FuncMap = funcMap
}
/*
404 处理路由
*/
func (this *Engine) NoRoute(handlers ...HandlerFunc) {
this.noRoute = handlers
this.rebuild404Handlers()
}
/*
没有找到对应的方法
*/
func (this *Engine) NoMethod(handlers ...HandlerFunc) {
this.noMethod = handlers
this.rebuild405Handlers()
}
func (engine *Engine) Routes() (routes RoutesInfo) {
for _, tree := range engine.trees {
routes = iterate("", tree.method, routes, tree.root)
}
return routes
}
/*
设置信任代理
*/
func (this *Engine) SetTrustedProxies(trustedProxies []string) error {
this.trustedProxies = trustedProxies
return this.parseTrustedProxies()
}
func (this *Engine) addRoute(method, path string, handlers HandlersChain) {
assert1(path[0] == '/', "path must begin with '/'")
assert1(method != "", "HTTP method can not be empty")
assert1(len(handlers) > 0, "there must be at least one handler")
if this.log.Enabled(log.DebugLevel) {
nuHandlers := len(handlers)
handlerName := nameOfFunction(handlers.Last())
this.log.Debugf("%s:%s --> %s handlers:%d", method, path, handlerName, nuHandlers)
}
root := this.trees.get(method)
if root == nil {
root = new(node)
root.fullPath = "/"
this.trees = append(this.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
// Update maxParams
if paramsCount := countParams(path); paramsCount > this.maxParams {
this.maxParams = paramsCount
}
if sectionsCount := countSections(path); sectionsCount > this.maxSections {
this.maxSections = sectionsCount
}
}
func (this *Engine) handleHTTPRequest(c *Context) {
httpMethod := c.Request.Method
rPath := c.Request.URL.Path
unescape := false
if this.UseRawPath && len(c.Request.URL.RawPath) > 0 {
rPath = c.Request.URL.RawPath
unescape = this.UnescapePathValues
}
if this.RemoveExtraSlash {
rPath = cleanPath(rPath)
}
t := this.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// Find route in tree
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next()
c.writermem.WriteHeaderNow()
return
}
if httpMethod != http.MethodConnect && rPath != "/" {
if value.tsr && this.RedirectTrailingSlash {
this.redirectTrailingSlash(c)
return
}
if this.RedirectFixedPath && this.redirectFixedPath(c, root, this.RedirectFixedPath) {
return
}
}
break
}
if this.HandleMethodNotAllowed {
for _, tree := range this.trees {
if tree.method == httpMethod {
continue
}
if value := tree.root.getValue(rPath, nil, c.skippedNodes, unescape); value.handlers != nil {
c.handlers = this.allNoMethod
this.serveError(c, http.StatusMethodNotAllowed, default405Body)
return
}
}
}
c.handlers = this.allNoRoute
this.serveError(c, http.StatusNotFound, default404Body)
}
func (this *Engine) rebuild404Handlers() {
this.allNoRoute = this.combineHandlers(this.noRoute)
}
func (this *Engine) rebuild405Handlers() {
this.allNoMethod = this.combineHandlers(this.noMethod)
}
func (this *Engine) IsUnsafeTrustedProxies() bool {
return this.isTrustedProxy(net.ParseIP("0.0.0.0")) || this.isTrustedProxy(net.ParseIP("::"))
}
//validateHeader 将解析 X-Forwarded-For 标头并返回受信任的客户端 IP 地址
func (this *Engine) validateHeader(header string) (clientIP string, valid bool) {
if header == "" {
return "", false
}
items := strings.Split(header, ",")
for i := len(items) - 1; i >= 0; i-- {
ipStr := strings.TrimSpace(items[i])
ip := net.ParseIP(ipStr)
if ip == nil {
break
}
// X-Forwarded-For is appended by proxy
// Check IPs in reverse order and stop when find untrusted proxy
if (i == 0) || (!this.isTrustedProxy(ip)) {
return ipStr, true
}
}
return "", false
}
///目标Ip是否是可信
func (this *Engine) isTrustedProxy(ip net.IP) bool {
if this.trustedCIDRs == nil {
return false
}
for _, cidr := range this.trustedCIDRs {
if cidr.Contains(ip) {
return true
}
}
return false
}
func (this *Engine) serveError(c *Context, code int, defaultMessage []byte) {
c.writermem.status = code
c.Next()
if c.writermem.Written() {
return
}
if c.writermem.Status() == code {
c.writermem.Header()["Content-Type"] = mimePlain
_, err := c.Writer.Write(defaultMessage)
if err != nil {
this.log.Errorf("[SYS-Gin] cannot write message to writer during serve error: %v", err)
}
return
}
c.writermem.WriteHeaderNow()
}
func (this *Engine) redirectFixedPath(c *Context, root *node, trailingSlash bool) bool {
req := c.Request
rPath := req.URL.Path
if fixedPath, ok := root.findCaseInsensitivePath(cleanPath(rPath), trailingSlash); ok {
req.URL.Path = codec.BytesToString(fixedPath)
this.redirectRequest(c)
return true
}
return false
}
func (this *Engine) redirectTrailingSlash(c *Context) {
req := c.Request
p := req.URL.Path
if prefix := path.Clean(c.Request.Header.Get("X-Forwarded-Prefix")); prefix != "." {
p = prefix + "/" + req.URL.Path
}
req.URL.Path = p + "/"
if length := len(p); length > 1 && p[length-1] == '/' {
req.URL.Path = p[:length-1]
}
this.redirectRequest(c)
}
func (this *Engine) redirectRequest(c *Context) {
req := c.Request
rPath := req.URL.Path
rURL := req.URL.String()
code := http.StatusMovedPermanently // Permanent redirect, request with GET method
if req.Method != http.MethodGet {
code = http.StatusTemporaryRedirect
}
this.log.Debugf("redirecting request %d: %s --> %s", code, rPath, rURL)
http.Redirect(c.Writer, req, rURL, code)
c.writermem.WriteHeaderNow()
}
func (this *Engine) parseTrustedProxies() error {
trustedCIDRs, err := this.prepareTrustedCIDRs()
this.trustedCIDRs = trustedCIDRs
return err
}
func (this *Engine) prepareTrustedCIDRs() ([]*net.IPNet, error) {
if this.trustedProxies == nil {
return nil, nil
}
cidr := make([]*net.IPNet, 0, len(this.trustedProxies))
for _, trustedProxy := range this.trustedProxies {
if !strings.Contains(trustedProxy, "/") {
ip := parseIP(trustedProxy)
if ip == nil {
return cidr, &net.ParseError{Type: "IP address", Text: trustedProxy}
}
switch len(ip) {
case net.IPv4len:
trustedProxy += "/32"
case net.IPv6len:
trustedProxy += "/128"
}
}
_, cidrNet, err := net.ParseCIDR(trustedProxy)
if err != nil {
return cidr, err
}
cidr = append(cidr, cidrNet)
}
return cidr, nil
}
func (this *Engine) allocateContext() *Context {
v := make(Params, 0, this.maxParams)
skippedNodes := make([]skippedNode, 0, this.maxSections)
return newContext(this.log, this, &v, &skippedNodes) //& Context{Log: this.log, engine: this, params: &v, skippedNodes: &skippedNodes}
}
//日志接口-------------------------------------------------------------
func (this *Engine) debugPrintLoadTemplate(tmpl *template.Template) {
var buf strings.Builder
for _, tmpl := range tmpl.Templates() {
buf.WriteString("\t- ")
buf.WriteString(tmpl.Name())
buf.WriteString("\n")
}
format := "Loaded HTML Templates (%d): \n%s\n"
if !strings.HasSuffix(format, "\n") {
format += "\n"
}
this.log.Debugf(format, len(tmpl.Templates()), buf.String())
}