512 lines
14 KiB
Go
512 lines
14 KiB
Go
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())
|
||
|
||
}
|