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 请求,客户端被重定向到 /foo,http 状态码为 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()) }