diff --git a/TODO b/TODO index a1413a387..7cb9a29eb 100644 --- a/TODO +++ b/TODO @@ -15,6 +15,8 @@ map转struct增加对tag的支持; gcache检查在i386下的int64->int转换问题; gfsnotify增加对于目录的监控; orm增加sqlite对Save方法的支持(去掉触发器语句); +ghttp.Server的Cookie及Session锁机制优化(去掉map锁机制); +ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps); DONE: 1. gconv完善针对不同类型的判断,例如:尽量减少sprintf("%v", xxx)来执行string类型的转换; diff --git a/g/container/gpool/gpool.go b/g/container/gpool/gpool.go index 4c96dce7c..c3b6374a3 100644 --- a/g/container/gpool/gpool.go +++ b/g/container/gpool/gpool.go @@ -70,7 +70,7 @@ func (p *Pool) Get() (interface{}, error) { for !p.closed.Val() { if r := p.list.PopFront(); r != nil { f := r.(*poolItem) - if f.expire > gtime.Millisecond() { + if f.expire == 0 || f.expire > gtime.Millisecond() { return f.value, nil } } else { @@ -99,7 +99,7 @@ func (p *Pool) expireCheckingLoop() { for { if r := p.list.PopFront(); r != nil { item := r.(*poolItem) - if item.expire > gtime.Millisecond() { + if item.expire == 0 || item.expire > gtime.Millisecond() { p.list.PushFront(item) break } diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 86e6c6b7d..dd219f19c 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -22,16 +22,22 @@ import ( "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/container/gqueue" "gitee.com/johng/gf/g/os/gspath" - "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/genv" "github.com/gorilla/websocket" "gitee.com/johng/gf/g/os/gtime" "time" + "gitee.com/johng/gf/g/os/gfile" ) const ( SERVER_STATUS_STOPPED = 0 // Server状态:停止 SERVER_STATUS_RUNNING = 1 // Server状态:运行 + HOOK_BEFORE_SERVE = "BeforeServe" + HOOK_AFTER_SERVE = "AfterServe" + HOOK_BEFORE_OUTPUT = "BeforeOutput" + HOOK_AFTER_OUTPUT = "AfterOutput" + HOOK_BEFORE_CLOSE = "BeforeClose" + HOOK_AFTER_CLOSE = "AfterClose" ) const ( gHTTP_METHODS = "GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE" @@ -208,8 +214,14 @@ func GetServer(name...interface{}) (*Server) { // 作为守护协程异步执行(当同一进程中存在多个Web Server时,需要采用这种方式执行) // 需要结合Wait方式一起使用 func (s *Server) Start() error { + // 服务进程初始化,只会初始化一次 serverProcInit() + // 当前Web Server状态判断 + if s.Status() == SERVER_STATUS_RUNNING { + return errors.New("server is already running") + } + // 如果设置了静态文件目录,那么严格按照静态文件目录进行检索 // 否则,默认使用当前可执行文件目录,并且如果是开发环境,默认也会添加main包的源码目录路径做为二级检索 if s.config.ServerRoot != "" { @@ -221,13 +233,19 @@ func (s *Server) Start() error { } } - if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server is already running") - } // 底层http server配置 if s.config.Handler == nil { s.config.Handler = http.HandlerFunc(s.defaultHttpHandle) } + // 不允许访问的路由注册 + if s.config.DenyRoutes != nil { + for _, v := range s.config.DenyRoutes { + s.BindHookHandler(v, HOOK_BEFORE_SERVE, func(r *Request) { + r.Response.WriteStatus(403) + r.Exit() + }) + } + } // 启动http server reloaded := false diff --git a/g/net/ghttp/ghttp_server_config.go b/g/net/ghttp/ghttp_server_config.go index d75519b73..167749e9a 100644 --- a/g/net/ghttp/ghttp_server_config.go +++ b/g/net/ghttp/ghttp_server_config.go @@ -9,11 +9,11 @@ package ghttp import ( "time" - "errors" "net/http" "strconv" - "gitee.com/johng/gf/g/os/gfile" "strings" + "gitee.com/johng/gf/g/os/glog" + "gitee.com/johng/gf/g/os/gfile" ) const ( @@ -28,21 +28,20 @@ const ( // HTTP Server 设置结构体,静态配置 type ServerConfig struct { // 底层http对象配置 - Addr string // 监听IP和端口,监听本地所有IP使用":端口"(支持多个地址,使用","号分隔) - HTTPSAddr string // HTTPS服务监听地址(支持多个地址,使用","号分隔) - HTTPSCertPath string // HTTPS证书文件路径 - HTTPSKeyPath string // HTTPS签名文件路径 - Handler http.Handler // 默认的处理函数 - ReadTimeout time.Duration - WriteTimeout time.Duration - IdleTimeout time.Duration - MaxHeaderBytes int // 最大的header长度 + Addr string // 监听IP和端口,监听本地所有IP使用":端口"(支持多个地址,使用","号分隔) + HTTPSAddr string // HTTPS服务监听地址(支持多个地址,使用","号分隔) + HTTPSCertPath string // HTTPS证书文件路径 + HTTPSKeyPath string // HTTPS签名文件路径 + Handler http.Handler // 默认的处理函数 + ReadTimeout time.Duration + WriteTimeout time.Duration + IdleTimeout time.Duration + MaxHeaderBytes int // 最大的header长度 // 静态文件配置 - IndexFiles []string // 默认访问的文件列表 - IndexFolder bool // 如果访问目录是否显示目录列表 - ServerAgent string // server agent - ServerRoot string // 服务器服务的本地目录根路径 - + IndexFiles []string // 默认访问的文件列表 + IndexFolder bool // 如果访问目录是否显示目录列表 + ServerAgent string // server agent + ServerRoot string // 服务器服务的本地目录根路径 // 日志配置 LogPath string // 存放日志的目录路径 LogHandler func(r *Request, error ... interface{}) // 自定义日志处理回调方法 @@ -55,6 +54,11 @@ type ServerConfig struct { SessionIdName string // SessionId名称 // 其他设置 NameToUriType int // 服务注册时对象和方法名称转换为URI时的规则 + // ip访问控制 + DenyIps []string // 不允许访问的ip列表,支持ip前缀过滤,如: 10 将不允许10开头的ip访问 + AllowIps []string // 仅允许访问的ip列表,支持ip前缀过滤,如: 10 将仅允许10开头的ip访问 + // 路由访问控制 + DenyRoutes []string // 不允许访问的路由规则列表 } // 默认HTTP Server @@ -85,9 +89,9 @@ func DefaultSetting() ServerConfig { // http server setting设置 // 注意使用该方法进行http server配置时,需要配置所有的配置项,否则没有配置的属性将会默认变量为空 -func (s *Server)SetConfig(c ServerConfig) error { +func (s *Server)SetConfig(c ServerConfig) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } if c.Handler == nil { c.Handler = http.HandlerFunc(s.defaultHttpHandle) @@ -120,22 +124,21 @@ func (s *Server)SetConfig(c ServerConfig) error { s.SetSessionIdName(c.SessionIdName) } s.SetNameToUriType(c.NameToUriType) - return nil + } // 设置http server参数 - Addr -func (s *Server)SetAddr(addr string) error { +func (s *Server)SetAddr(addr string) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } s.config.Addr = addr - return nil } // 设置http server参数 - Port -func (s *Server)SetPort(port...int) error { +func (s *Server)SetPort(port...int) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("config cannot be changed while running") } if len(port) > 0 { s.config.Addr = "" @@ -146,22 +149,21 @@ func (s *Server)SetPort(port...int) error { s.config.Addr += ":" + strconv.Itoa(v) } } - return nil } // 设置http server参数 - HTTPS Addr -func (s *Server)SetHTTPSAddr(addr string) error { +func (s *Server)SetHTTPSAddr(addr string) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } s.config.HTTPSAddr = addr - return nil + } // 设置http server参数 - HTTPS Port -func (s *Server)SetHTTPSPort(port...int) error { +func (s *Server)SetHTTPSPort(port...int) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } if len(port) > 0 { s.config.HTTPSAddr = "" @@ -172,94 +174,113 @@ func (s *Server)SetHTTPSPort(port...int) error { s.config.HTTPSAddr += ":" + strconv.Itoa(v) } } - return nil } // 开启HTTPS支持,但是必须提供Cert和Key文件 -func (s *Server)EnableHTTPS(certFile, keyFile string) error { +func (s *Server)EnableHTTPS(certFile, keyFile string) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } s.config.HTTPSCertPath = certFile s.config.HTTPSKeyPath = keyFile - return nil + } // 设置http server参数 - ReadTimeout -func (s *Server)SetReadTimeout(t time.Duration) error { +func (s *Server)SetReadTimeout(t time.Duration) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } s.config.ReadTimeout = t - return nil + } // 设置http server参数 - WriteTimeout -func (s *Server)SetWriteTimeout(t time.Duration) error { +func (s *Server)SetWriteTimeout(t time.Duration) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } s.config.WriteTimeout = t - return nil + } // 设置http server参数 - IdleTimeout -func (s *Server)SetIdleTimeout(t time.Duration) error { +func (s *Server)SetIdleTimeout(t time.Duration) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } s.config.IdleTimeout = t - return nil + } // 设置http server参数 - MaxHeaderBytes -func (s *Server)SetMaxHeaderBytes(b int) error { +func (s *Server)SetMaxHeaderBytes(b int) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } s.config.MaxHeaderBytes = b - return nil + } // 设置http server参数 - IndexFiles,默认展示文件,如:index.html, index.htm -func (s *Server)SetIndexFiles(index []string) error { +func (s *Server)SetIndexFiles(index []string) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } s.config.IndexFiles = index - return nil + } // 允许展示访问目录的文件列表 -func (s *Server)SetIndexFolder(index bool) error { +func (s *Server)SetIndexFolder(index bool) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } s.config.IndexFolder = index - return nil + } // 设置http server参数 - ServerAgent -func (s *Server)SetServerAgent(agent string) error { +func (s *Server)SetServerAgent(agent string) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } s.config.ServerAgent = agent - return nil + } // 设置http server参数 - ServerRoot -func (s *Server)SetServerRoot(root string) error { +func (s *Server)SetServerRoot(root string) { if s.Status() == SERVER_STATUS_RUNNING { - return errors.New("server config cannot be changed while running") + glog.Error("cannot be changed while running") } // RealPath的作用除了校验地址正确性以外,还转换分隔符号为当前系统正确的文件分隔符号 path := gfile.RealPath(root) if path == "" { - return errors.New("invalid root path \"" + root + "\"") + glog.Error("invalid root path \"" + root + "\"") } s.config.ServerRoot = strings.TrimRight(path, string(gfile.Separator)) - return nil +} + +func (s *Server) SetDenyIps(ips []string) { + if s.Status() == SERVER_STATUS_RUNNING { + glog.Error("cannot be changed while running") + } + s.config.DenyIps = ips +} + +func (s *Server) SetAllowIps(ips []string) { + if s.Status() == SERVER_STATUS_RUNNING { + glog.Error("cannot be changed while running") + } + s.config.AllowIps = ips +} + +func (s *Server) SetDenyRoutes(routes []string) { + if s.Status() == SERVER_STATUS_RUNNING { + glog.Error("cannot be changed while running") + } + s.config.DenyRoutes = routes } // 设置http server参数 - CookieMaxAge @@ -278,20 +299,19 @@ func (s *Server)SetSessionIdName(name string) { } // 设置日志目录 -func (s *Server)SetLogPath(path string) error { +func (s *Server)SetLogPath(path string) { if len(path) == 0 { - return nil + return } errorLogPath := strings.TrimRight(path, gfile.Separator) + gfile.Separator + "error" accessLogPath := strings.TrimRight(path, gfile.Separator) + gfile.Separator + "access" if err := s.accessLogger.SetPath(accessLogPath); err != nil { - return err + glog.Error(err) } if err := s.errorLogger.SetPath(errorLogPath); err != nil { - return err + glog.Error(err) } s.logPath.Set(path) - return nil } // 设置是否开启access log日志功能 diff --git a/g/net/ghttp/ghttp_server_cookie.go b/g/net/ghttp/ghttp_server_cookie.go index df0a78a18..cf445fa6a 100644 --- a/g/net/ghttp/ghttp_server_cookie.go +++ b/g/net/ghttp/ghttp_server_cookie.go @@ -48,7 +48,7 @@ func GetCookie(r *Request) *Cookie { response : r.Response, } c.init() - c.server.cookies.Set(r.Id, c) + r.Server.cookies.Set(r.Id, c) return c } diff --git a/g/net/ghttp/ghttp_server_handler.go b/g/net/ghttp/ghttp_server_handler.go index 89ac2a637..04664a939 100644 --- a/g/net/ghttp/ghttp_server_handler.go +++ b/g/net/ghttp/ghttp_server_handler.go @@ -52,55 +52,51 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) { s.closeQueue.PushBack(request) }() + // 事件 - BeforeServe + s.callHookHandler(HOOK_BEFORE_SERVE, request) + // 路由注册检索 - handler := (*handlerItem)(nil) - parsedItem := s.getServeHandlerWithCache(request) - if parsedItem == nil { - // 如果路由不匹配,那么执行静态文件检索 - path := s.paths.Search(r.URL.Path) - if path != "" { - s.serveFile(request, path) - } else { - request.Response.WriteStatus(http.StatusNotFound) - request.Response.OutputBuffer() - } - return - } else { - handler = parsedItem.handler - if request.Router == nil { - for k, v := range parsedItem.values { - request.routerVars[k] = v + handler := (*handlerItem)(nil) + if !request.exit.Val() { + parsedItem := s.getServeHandlerWithCache(request) + if parsedItem == nil { + // 如果路由不匹配,那么执行静态文件检索 + path := s.paths.Search(r.URL.Path) + if path != "" { + s.serveFile(request, path) + } else { + request.Response.WriteStatus(http.StatusNotFound) + } + } else { + handler = parsedItem.handler + if request.Router == nil { + for k, v := range parsedItem.values { + request.routerVars[k] = v + } + request.Router = parsedItem.handler.router } - request.Router = parsedItem.handler.router } } - // ********************************************** - // 以下操作仅对路由控制有效,包括事件处理,不对静态文件有效 - // ********************************************** - - // 事件 - BeforeServe - s.callHookHandler("BeforeServe", request) - // 执行回调控制器/执行对象/方法 if handler != nil { s.callServeHandler(handler, request) } // 事件 - AfterServe - s.callHookHandler("AfterServe", request) + s.callHookHandler(HOOK_AFTER_SERVE, request) // 设置请求完成时间 request.LeaveTime = gtime.Microsecond() // 事件 - BeforeOutput - s.callHookHandler("BeforeOutput", request) + s.callHookHandler(HOOK_BEFORE_OUTPUT, request) // 输出Cookie request.Cookie.Output() // 输出缓冲区 request.Response.OutputBuffer() // 事件 - AfterOutput - s.callHookHandler("AfterOutput", request) + s.callHookHandler(HOOK_AFTER_OUTPUT, request) } // 初始化控制器 @@ -182,12 +178,12 @@ func (s *Server) startCloseQueueLoop() { for { if v := s.closeQueue.PopFront(); v != nil { r := v.(*Request) - s.callHookHandler("BeforeClose", r) + s.callHookHandler(HOOK_BEFORE_CLOSE, r) // 关闭当前会话的Cookie r.Cookie.Close() // 更新Session会话超时时间 r.Session.UpdateExpire() - s.callHookHandler("AfterClose", r) + s.callHookHandler(HOOK_AFTER_CLOSE, r) } } }() diff --git a/g/net/ghttp/ghttp_server_router.go b/g/net/ghttp/ghttp_server_router.go index df52dec36..e7420e195 100644 --- a/g/net/ghttp/ghttp_server_router.go +++ b/g/net/ghttp/ghttp_server_router.go @@ -12,6 +12,7 @@ import ( "strings" "container/list" "gitee.com/johng/gf/g/util/gregex" + "gitee.com/johng/gf/g/util/gstr" ) @@ -98,7 +99,7 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin continue } // 判断是否模糊匹配规则 - if gregex.IsMatchString(`^[:\*]|{[\w\.\-]+}`, v) { + if gregex.IsMatchString(`^[:\*]|\{[\w\.\-]+\}|\*`, v) { v = "*fuzz" // 由于是模糊规则,因此这里会有一个*list,用以将后续的路由规则加进来, // 检索会从叶子节点的链表往根节点按照优先级进行检索 @@ -201,7 +202,13 @@ func (s *Server) patternToRegRule(rule string) (regrule string, names []string) regrule += `/{0,1}(.*)` names = append(names, v[1:]) default: - s, _ := gregex.ReplaceStringFunc(`{[\w\.\-]+}`, v, func(s string) string { + // 特殊字符替换 + v = gstr.ReplaceByMap(v, map[string]string{ + `.` : `\.`, + `+` : `\+`, + `*` : `.*`, + }) + s, _ := gregex.ReplaceStringFunc(`\{[\w\.\-]+\}`, v, func(s string) string { names = append(names, s[1 : len(s) - 1]) return `([\w\.\-]+)` }) diff --git a/g/net/ghttp/ghttp_server_session.go b/g/net/ghttp/ghttp_server_session.go index c8db783d1..61a63a206 100644 --- a/g/net/ghttp/ghttp_server_session.go +++ b/g/net/ghttp/ghttp_server_session.go @@ -42,6 +42,7 @@ func GetSession(r *Request) *Session { data : gmap.NewStringInterfaceMap(), server : s, } + s.sessions.Set(sid, ses, s.GetSessionMaxAge()) return ses } diff --git a/g/util/gvalid/gvalid.go b/g/util/gvalid/gvalid.go index 6c0aff14b..174d93eb4 100644 --- a/g/util/gvalid/gvalid.go +++ b/g/util/gvalid/gvalid.go @@ -472,9 +472,10 @@ func CheckStruct(st interface{}, rules map[string]string, msgs...map[string]inte return CheckMap(params, rules, errMsgs) } -// 检测单条数据的规则,其中params参数为非必须参数,可以传递所有的校验参数进来,进行多参数对比(部分校验规则需要) -// msgs为自定义错误信息,由于同一条数据的校验规则可能存在多条,为方便调用,参数类型支持string/map[string]string,允许传递多个自定义的错误信息,如果类型为string,那么中间使用"|"符号分隔多个自定义错误 -// values参数为表单联合校验参数,对于需要联合校验的规则有效,如:required-*、same、different +// 检测单条数据的规则. +// val为校验数据,可以为任意格式; +// msgs为自定义错误信息,由于同一条数据的校验规则可能存在多条,为方便调用,参数类型支持string/map[string]string,允许传递多个自定义的错误信息,如果类型为string,那么中间使用"|"符号分隔多个自定义错误; +// params参数为表单联合校验参数,对于需要联合校验的规则有效,如:required-*、same、different; func Check(val interface{}, rules string, msgs interface{}, params...map[string]interface{}) map[string]string { // 内部会将参数全部转换为字符串类型进行校验 value := strings.TrimSpace(gconv.String(val)) diff --git a/geg/net/ghttp/server/denyroutes/config.toml b/geg/net/ghttp/server/denyroutes/config.toml new file mode 100644 index 000000000..0e7810036 --- /dev/null +++ b/geg/net/ghttp/server/denyroutes/config.toml @@ -0,0 +1 @@ +You're not supposed to view this \ No newline at end of file diff --git a/geg/net/ghttp/server/denyroutes/denyroutes.go b/geg/net/ghttp/server/denyroutes/denyroutes.go new file mode 100644 index 000000000..c25006f13 --- /dev/null +++ b/geg/net/ghttp/server/denyroutes/denyroutes.go @@ -0,0 +1,12 @@ +package main + +import "gitee.com/johng/gf/g" + +func main() { + s := g.Server() + s.SetDenyRoutes([]string{ + "/config*", + }) + s.SetPort(8299) + s.Run() +}