性能改进中

This commit is contained in:
John 2018-11-16 18:20:09 +08:00
parent 7f640da9a1
commit e8101cb02f
34 changed files with 736 additions and 566 deletions

View File

@ -108,10 +108,12 @@ func (a *IntArray) Slice() []int {
// 清空数据数组
func (a *IntArray) Clear() {
a.mu.Lock()
if a.cap > 0 {
a.array = make([]int, a.size, a.cap)
} else {
a.array = make([]int, a.size)
if len(a.array) > 0 {
if a.cap > 0 {
a.array = make([]int, a.size, a.cap)
} else {
a.array = make([]int, a.size)
}
}
a.mu.Unlock()
}

View File

@ -107,10 +107,12 @@ func (a *Array) Slice() []interface{} {
// 清空数据数组
func (a *Array) Clear() {
a.mu.Lock()
if a.cap > 0 {
a.array = make([]interface{}, a.size, a.cap)
} else {
a.array = make([]interface{}, a.size)
if len(a.array) > 0 {
if a.cap > 0 {
a.array = make([]interface{}, a.size, a.cap)
} else {
a.array = make([]interface{}, a.size)
}
}
a.mu.Unlock()
}

View File

@ -76,6 +76,17 @@ func (a *SortedIntArray) Get(index int) int {
func (a *SortedIntArray) Remove(index int) int {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
@ -94,9 +105,9 @@ func (a *SortedIntArray) PopLeft() int {
func (a *SortedIntArray) PopRight() int {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
value := a.array[length - 1]
a.array = a.array[: length - 1]
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[: index]
return value
}
@ -186,7 +197,9 @@ func (a *SortedIntArray) doUnique() {
// 清空数据数组
func (a *SortedIntArray) Clear() {
a.mu.Lock()
a.array = make([]int, 0, a.cap)
if len(a.array) > 0 {
a.array = make([]int, 0, a.cap)
}
a.mu.Unlock()
}

View File

@ -67,6 +67,17 @@ func (a *SortedArray) Get(index int) interface{} {
func (a *SortedArray) Remove(index int) interface{} {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
@ -85,9 +96,9 @@ func (a *SortedArray) PopLeft() interface{} {
func (a *SortedArray) PopRight() interface{} {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
value := a.array[length - 1]
a.array = a.array[: length - 1]
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[: index]
return value
}
@ -179,7 +190,9 @@ func (a *SortedArray) doUnique() {
// 清空数据数组
func (a *SortedArray) Clear() {
a.mu.Lock()
a.array = make([]interface{}, 0, a.cap)
if len(a.array) > 0 {
a.array = make([]interface{}, 0, a.cap)
}
a.mu.Unlock()
}

View File

@ -70,6 +70,17 @@ func (a *SortedStringArray) Get(index int) string {
func (a *SortedStringArray) Remove(index int) string {
a.mu.Lock()
defer a.mu.Unlock()
// 边界删除判断,以提高删除效率
if index == 0 {
value := a.array[0]
a.array = a.array[1 : ]
return value
} else if index == len(a.array) - 1 {
value := a.array[index]
a.array = a.array[: index]
return value
}
// 如果非边界删除,会涉及到数组创建,那么删除的效率差一些
value := a.array[index]
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
return value
@ -88,9 +99,9 @@ func (a *SortedStringArray) PopLeft() string {
func (a *SortedStringArray) PopRight() string {
a.mu.Lock()
defer a.mu.Unlock()
length := len(a.array)
value := a.array[length - 1]
a.array = a.array[: length - 1]
index := len(a.array) - 1
value := a.array[index]
a.array = a.array[: index]
return value
}
@ -180,7 +191,9 @@ func (a *SortedStringArray) doUnique() {
// 清空数据数组
func (a *SortedStringArray) Clear() {
a.mu.Lock()
a.array = make([]string, 0, a.cap)
if len(a.array) > 0 {
a.array = make([]string, 0, a.cap)
}
a.mu.Unlock()
}

View File

@ -108,10 +108,12 @@ func (a *StringArray) Slice() []string {
// 清空数据数组
func (a *StringArray) Clear() {
a.mu.Lock()
if a.cap > 0 {
a.array = make([]string, a.size, a.cap)
} else {
a.array = make([]string, a.size)
if len(a.array) > 0 {
if a.cap > 0 {
a.array = make([]string, a.size, a.cap)
} else {
a.array = make([]string, a.size)
}
}
a.mu.Unlock()
}

View File

@ -7,25 +7,24 @@
package ghttp
import (
"gitee.com/johng/gf/g/container/gvar"
"gitee.com/johng/gf/g/encoding/gjson"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/third/github.com/fatih/structs"
"io/ioutil"
"net/http"
"gitee.com/johng/gf/g/encoding/gjson"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/third/github.com/fatih/structs"
"strings"
"gitee.com/johng/gf/g/container/gvar"
)
// 请求对象
type Request struct {
http.Request
parsedGet *gtype.Bool // GET参数是否已经解析
parsedPost *gtype.Bool // POST参数是否已经解析
parsedGet bool // GET参数是否已经解析
parsedPost bool // POST参数是否已经解析
queryVars map[string][]string // GET参数
routerVars map[string][]string // 路由解析参数
exit *gtype.Bool // 是否退出当前请求流程执行
exit bool // 是否退出当前请求流程执行
Id int // 请求id(唯一)
Server *Server // 请求关联的服务器对象
Cookie *Cookie // 与当前请求绑定的Cookie对象(并发安全)
@ -35,27 +34,21 @@ type Request struct {
EnterTime int64 // 请求进入时间(微秒)
LeaveTime int64 // 请求完成时间(微秒)
Param interface{} // 开发者自定义参数
parsedHost *gtype.String // 解析过后不带端口号的服务器域名名称
clientIp *gtype.String // 解析过后的客户端IP地址
parsedHost string // 解析过后不带端口号的服务器域名名称
clientIp string // 解析过后的客户端IP地址
isFileRequest bool // 是否为静态文件请求(非服务请求,当静态文件存在时,优先级会被服务请求高,被识别为文件请求)
isFileServe bool // 是否为文件处理(调用Server.serveFile时设置为true), isFileRequest为true时isFileServe也为true
}
// 创建一个Request对象
func newRequest(s *Server, r *http.Request, w http.ResponseWriter) *Request {
request := &Request{
parsedGet : gtype.NewBool(),
parsedPost : gtype.NewBool(),
queryVars : make(map[string][]string),
request := &Request {
routerVars : make(map[string][]string),
exit : gtype.NewBool(),
Id : s.servedCount.Add(1),
Server : s,
Request : *r,
Response : newResponse(s, w),
EnterTime : gtime.Microsecond(),
parsedHost : gtype.NewString(),
clientIp : gtype.NewString(),
}
// 会话处理
request.Cookie = GetCookie(request)
@ -153,28 +146,26 @@ func (r *Request) GetToStruct(object interface{}, mapping...map[string]string) {
// 退出当前请求执行原理是在Request.exit做标记由服务逻辑流程做判断自行停止
func (r *Request) Exit() {
r.exit.Set(true)
r.exit = true
panic(gEXCEPTION_EXIT)
}
// 判断当前请求是否停止执行
func (r *Request) IsExited() bool {
return r.exit.Val()
return r.exit
}
// 获取请求的服务端IP/域名
func (r *Request) GetHost() string {
host := r.parsedHost.Val()
if len(host) == 0 {
if len(r.parsedHost) == 0 {
array, _ := gregex.MatchString(`(.+):(\d+)`, r.Host)
if len(array) > 1 {
host = array[1]
r.parsedHost = array[1]
} else {
host = r.Host
r.parsedHost = r.Host
}
r.parsedHost.Set(host)
}
return host
return r.parsedHost
}
// 判断是否为静态文件请求
@ -194,19 +185,17 @@ func (r *Request) IsAjaxRequest() bool {
// 获取请求的客户端IP地址
func (r *Request) GetClientIp() string {
ip := r.clientIp.Val()
if len(ip) == 0 {
if ip = r.Header.Get("X-Real-IP"); ip == "" {
if len(r.clientIp) == 0 {
if r.clientIp = r.Header.Get("X-Real-IP"); r.clientIp == "" {
array, _ := gregex.MatchString(`(.+):(\d+)`, r.RemoteAddr)
if len(array) > 1 {
ip = array[1]
r.clientIp = array[1]
} else {
ip = r.RemoteAddr
r.clientIp = r.RemoteAddr
}
}
r.clientIp.Set(ip)
}
return ip
return r.clientIp
}
// 获得来源URL地址

View File

@ -12,11 +12,11 @@ import (
// 初始化POST请求参数
func (r *Request) initPost() {
if !r.parsedPost.Val() {
// 快速保存,尽量避免并发问题
r.parsedPost.Set(true)
if !r.parsedPost {
// MultiMedia表单请求解析允许最大使用内存1GB
r.ParseMultipartForm(1024*1024*1024)
if r.ParseMultipartForm(1024*1024*1024) == nil {
r.parsedPost = true
}
}
}

View File

@ -12,9 +12,9 @@ import (
// 初始化GET请求参数
func (r *Request) initGet() {
if !r.parsedGet.Val() {
r.parsedGet.Set(true)
if !r.parsedGet {
r.queryVars = r.URL.Query()
r.parsedGet = true
}
}

View File

@ -8,6 +8,7 @@
package ghttp
import (
"gitee.com/johng/gf/g/os/gfile"
"net/http"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/encoding/gparser"
@ -19,7 +20,6 @@ import (
// 注意该对象并没有实现http.ResponseWriter接口而是依靠ghttp.ResponseWriter实现。
type Response struct {
ResponseWriter
length int // 请求返回的内容长度(byte)
Server *Server // 所属Web Server
Writer *ResponseWriter // ResponseWriter的别名
request *Request // 关联的Request请求对象
@ -29,7 +29,7 @@ type Response struct {
func newResponse(s *Server, w http.ResponseWriter) *Response {
r := &Response {
Server : s,
ResponseWriter : ResponseWriter{
ResponseWriter : ResponseWriter {
ResponseWriter : w,
Status : http.StatusOK,
buffer : make([]byte, 0),
@ -48,17 +48,12 @@ func (r *Response) Write(content ... interface{}) {
switch v.(type) {
case []byte:
// 如果是二进制数据,那么返回二进制数据
r.mu.Lock()
r.buffer = append(r.buffer, gconv.Bytes(v)...)
r.mu.Unlock()
default:
// 否则一律按照可显示的字符串进行转换
r.mu.Lock()
r.buffer = append(r.buffer, gconv.String(v)...)
r.mu.Unlock()
}
}
r.length = len(r.buffer)
}
// 返回信息支持自定义format格式
@ -161,6 +156,15 @@ func (r *Response) WriteStatus(status int, content...string) {
// 静态文件处理
func (r *Response) ServeFile(path string) {
r.request.isFileServe = true
// 首先判断是否给定的path已经是一个绝对路径
if !gfile.Exists(path) {
path = r.Server.paths.Search(path)
}
if path == "" {
r.WriteStatus(http.StatusNotFound)
return
}
r.Server.serveFile(r.request, path)
}
@ -177,31 +181,22 @@ func (r *Response) RedirectBack() {
// 获取当前缓冲区中的数据
func (r *Response) Buffer() []byte {
r.mu.RLock()
defer r.mu.RUnlock()
return r.buffer
}
// 获取当前缓冲区中的数据大小
func (r *Response) BufferLength() int {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.buffer)
}
// 手动设置缓冲区内容
func (r *Response) SetBuffer(buffer []byte) {
r.length = 0
r.mu.Lock()
r.buffer = buffer
r.mu.Unlock()
}
// 清空缓冲区内容
func (r *Response) ClearBuffer() {
r.mu.Lock()
r.buffer = make([]byte, 0)
r.mu.Unlock()
r.buffer = r.buffer[:0]
}
// 输出缓冲区数据到客户端
@ -211,13 +206,3 @@ func (r *Response) OutputBuffer() {
r.Writer.OutputBuffer()
}
// 获取输出到客户端的数据大小
func (r *Response) ContentSize() int {
if r.Status == http.StatusOK && r.length > 0 {
return r.length
}
if length := r.Header().Get("Content-Length"); length != "" {
return gconv.Int(length)
}
return r.BufferLength()
}

View File

@ -9,13 +9,11 @@ package ghttp
import (
"net/http"
"sync"
)
// 自定义的ResponseWriter用于写入流的控制
type ResponseWriter struct {
http.ResponseWriter
mu sync.RWMutex // 缓冲区互斥锁
Status int // http status
buffer []byte // 缓冲区内容
}
@ -35,9 +33,7 @@ func (w *ResponseWriter) WriteHeader(code int) {
// 输出buffer数据到客户端
func (w *ResponseWriter) OutputBuffer() {
if len(w.buffer) > 0 {
w.mu.Lock()
w.ResponseWriter.Write(w.buffer)
w.buffer = make([]byte, 0)
w.mu.Unlock()
w.buffer = w.buffer[:0]
}
}

View File

@ -7,31 +7,30 @@
package ghttp
import (
"os"
"sync"
"bytes"
"errors"
"strings"
"reflect"
"runtime"
"net/http"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/os/gproc"
"gitee.com/johng/gf/g/os/gcache"
"gitee.com/johng/gf/g/util/gconv"
"fmt"
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/container/gmap"
"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/gcache"
"gitee.com/johng/gf/g/os/genv"
"gitee.com/johng/gf/third/github.com/gorilla/websocket"
"gitee.com/johng/gf/g/os/gtime"
"time"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/os/gproc"
"gitee.com/johng/gf/g/os/gspath"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/third/github.com/gorilla/websocket"
"gitee.com/johng/gf/third/github.com/olekukonko/tablewriter"
"bytes"
"fmt"
"net/http"
"os"
"reflect"
"runtime"
"strings"
"sync"
"time"
)
const (
@ -64,12 +63,11 @@ type Server struct {
servers []*gracefulServer // 底层http.Server列表
methodsMap map[string]struct{} // 所有支持的HTTP Method(初始化时自动填充)
servedCount *gtype.Int // 已经服务的请求数(4-8字节不考虑溢出情况)同时作为请求ID
closeQueue *gqueue.Queue // 请求结束的关闭队列(存放的是需要异步关闭处理的*Request对象)
// 服务注册相关
serveTree map[string]interface{} // 所有注册的服务回调函数(路由表,树型结构,哈希表+链表优先级匹配)
hooksTree map[string]interface{} // 所有注册的事件回调函数(路由表,树型结构,哈希表+链表优先级匹配)
serveCache *gcache.Cache // 服务注册路由内存缓存
hooksCache *gcache.Cache // 事件回调路由内存缓存
serveCache *gmap.StringInterfaceMap // 服务注册路由内存缓存
hooksCache *gmap.StringInterfaceMap // 事件回调路由内存缓存
routesMap map[string]registeredRouteItem // 已经注册的路由及对应的注册方法文件地址(用以路由重复注册判断)
// 自定义状态码回调
hsmu sync.RWMutex // status handler互斥锁
@ -180,12 +178,11 @@ func GetServer(name...interface{}) (*Server) {
statusHandlerMap : make(map[string]HandlerFunc),
serveTree : make(map[string]interface{}),
hooksTree : make(map[string]interface{}),
serveCache : gcache.New(),
hooksCache : gcache.New(),
serveCache : gmap.NewStringInterfaceMap(),
hooksCache : gmap.NewStringInterfaceMap(),
routesMap : make(map[string]registeredRouteItem),
sessions : gcache.New(),
servedCount : gtype.NewInt(),
closeQueue : gqueue.New(),
logger : glog.New(),
}
// 日志的标准输出默认关闭,但是错误信息会特殊处理
@ -239,6 +236,7 @@ func (s *Server) Start() error {
})
}
}
// gzip压缩文件类型
//if s.config.GzipContentTypes != nil {
// for _, v := range s.config.GzipContentTypes {
@ -267,9 +265,6 @@ func (s *Server) Start() error {
})
}
// 开启异步关闭队列处理循环
s.startCloseQueueLoop()
// 打印展示路由表
s.DumpRoutesMap()
return nil

View File

@ -254,7 +254,7 @@ func (s *Server)SetServerRoot(root string) {
if path == "" {
glog.Error("invalid root path \"" + root + "\"")
}
s.config.ServerRoot = strings.TrimRight(path, string(gfile.Separator))
s.config.ServerRoot = strings.TrimRight(path, gfile.Separator)
}
func (s *Server) SetDenyIps(ips []string) {

View File

@ -10,15 +10,13 @@
package ghttp
import (
"sync"
"time"
"net/http"
"gitee.com/johng/gf/g/os/gtime"
"net/http"
"time"
)
// cookie对象
// COOKIE对象
type Cookie struct {
mu sync.RWMutex // 并发安全互斥锁
data map[string]CookieItem // 数据项
path string // 默认的cookie path
domain string // 默认的cookie domain
@ -71,22 +69,30 @@ func (c *Cookie) init() {
// 获取所有的Cookie并构造成map返回
func (c *Cookie) Map() map[string]string {
m := make(map[string]string)
c.mu.RLock()
defer c.mu.RUnlock()
for k, v := range c.data {
m[k] = v.value
}
return m
}
// 获取SessionId
// 获取SessionId,不存在时则创建
func (c *Cookie) SessionId() string {
v := c.Get(c.server.GetSessionIdName())
if v == "" {
v = makeSessionId()
c.SetSessionId(v)
id := c.Get(c.server.GetSessionIdName())
if id == "" {
id = makeSessionId()
c.SetSessionId(id)
}
return v
return id
}
// 判断Cookie中是否存在制定键名(并且没有过期)
func (c *Cookie) Contains(key string) bool {
if r, ok := c.data[key]; ok {
if r.expire >= 0 {
return true
}
}
return false
}
// 设置SessionId
@ -101,7 +107,6 @@ func (c *Cookie) Set(key, value string) {
// 设置cookie带详细cookie参数
func (c *Cookie) SetCookie(key, value, domain, path string, maxAge int, httpOnly ... bool) {
c.mu.Lock()
isHttpOnly := false
if len(httpOnly) > 0 {
isHttpOnly = httpOnly[0]
@ -109,13 +114,10 @@ func (c *Cookie) SetCookie(key, value, domain, path string, maxAge int, httpOnly
c.data[key] = CookieItem {
value, domain, path, int(gtime.Second()) + maxAge, isHttpOnly,
}
c.mu.Unlock()
}
// 查询cookie
func (c *Cookie) Get(key string) string {
c.mu.RLock()
defer c.mu.RUnlock()
if r, ok := c.data[key]; ok {
if r.expire >= 0 {
return r.value
@ -134,10 +136,8 @@ func (c *Cookie) Remove(key, domain, path string) {
// 输出到客户端
func (c *Cookie) Output() {
c.mu.RLock()
defer c.mu.RUnlock()
for k, v := range c.data {
// 只有expire != 0的才是服务端在本地请求中设置的cookie
// 只有 expire != 0 的才是服务端在本次请求中设置的cookie
if v.expire == 0 {
continue
}

View File

@ -11,6 +11,7 @@ import (
"fmt"
"gitee.com/johng/gf/g/encoding/ghtml"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/os/gtime"
"net/http"
"net/url"
@ -42,56 +43,43 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
if request.LeaveTime == 0 {
request.LeaveTime = gtime.Microsecond()
}
s.callHookHandler(HOOK_BEFORE_CLOSE, request)
// access log
s.handleAccessLog(request)
// error log使用recover进行判断
if e := recover(); e != nil {
s.handleErrorLog(e, request)
}
// 将Request对象指针丢到队列中异步关闭
s.closeQueue.Push(request)
// 更新Session会话超时时间
request.Session.UpdateExpire()
s.callHookHandler(HOOK_AFTER_CLOSE, request)
}()
// 优先执行静态文件检索
filePath := s.paths.Search(r.URL.Path)
if filePath != "" {
if gfile.IsDir(filePath) {
// 如果是目录需要处理index files
if len(s.config.IndexFiles) > 0 {
for _, file := range s.config.IndexFiles {
fpath := s.paths.Search(file)
if fpath != "" {
filePath = fpath
request.isFileRequest = true
break
}
}
}
} else {
request.isFileRequest = true
}
}
// 其次进行服务路由信息检索
handler := (*handlerItem)(nil)
if !request.IsFileRequest() {
if parsedItem := s.getServeHandlerWithCache(request); parsedItem != nil {
handler = parsedItem.handler
for k, v := range parsedItem.values {
request.routerVars[k] = v
}
request.Router = parsedItem.handler.router
}
// 优先执行静态文件检索(检测是否存在对应的静态文件包括index files处理)
staticFile := s.paths.Search(r.URL.Path, s.config.IndexFiles...)
if staticFile != "" {
request.isFileRequest = true
}
glog.Info(staticFile)
// 事件 - BeforeServe
s.callHookHandler(HOOK_BEFORE_SERVE, request)
// 执行静态文件服务/回调控制器/执行对象/方法
if !request.exit.Val() {
if filePath != "" && (request.IsFileRequest() || handler == nil) {
s.serveFile(request, filePath)
if !request.IsExited() {
// 需要再次判断文件是否真实存在,因为文件检索可能使用了缓存,从健壮性考虑这里需要二次判断
if request.IsFileRequest() && gfile.Exists(staticFile) && gfile.SelfPath() != staticFile {
s.serveFile(request, staticFile)
} else {
// 动态服务检索
handler := (*handlerItem)(nil)
if parsedItem := s.getServeHandlerWithCache(request); parsedItem != nil {
handler = parsedItem.handler
for k, v := range parsedItem.values {
request.routerVars[k] = v
}
request.Router = parsedItem.handler.router
}
if handler != nil {
s.callServeHandler(handler, request)
} else {
@ -117,7 +105,7 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
}
// 初始化控制器
func (s *Server)callServeHandler(h *handlerItem, r *Request) {
func (s *Server) callServeHandler(h *handlerItem, r *Request) {
defer func() {
if e := recover(); e != nil && e != gEXCEPTION_EXIT {
panic(e)
@ -147,16 +135,6 @@ func (s *Server)callServeHandler(h *handlerItem, r *Request) {
// http server静态文件处理path可以为相对路径也可以为绝对路径
func (s *Server)serveFile(r *Request, path string) {
r.isFileServe = true
// 首先判断是否给定的path已经是一个绝对路径
if !gfile.Exists(path) {
path = s.paths.Search(path)
}
if path == "" {
r.Response.WriteStatus(http.StatusNotFound)
return
}
f, err := os.Open(path)
if err != nil {
r.Response.WriteStatus(http.StatusForbidden)
@ -172,7 +150,6 @@ func (s *Server)serveFile(r *Request, path string) {
}
} else {
// 读取文件内容返回, no buffer
r.Response.length = int(info.Size())
http.ServeContent(r.Response.Writer, &r.Request, info.Name(), info.ModTime(), f)
}
}
@ -198,18 +175,3 @@ func (s *Server)listDir(r *Request, f http.File) {
}
r.Response.Write("</pre>\n")
}
// 开启异步队列处理循环该异步线程与Server同生命周期
func (s *Server) startCloseQueueLoop() {
go func() {
for {
if v := s.closeQueue.Pop(); v != nil {
r := v.(*Request)
s.callHookHandler(HOOK_BEFORE_CLOSE, r)
// 更新Session会话超时时间
r.Session.UpdateExpire()
s.callHookHandler(HOOK_AFTER_CLOSE, r)
}
}
}()
}

View File

@ -22,10 +22,9 @@ func (s *Server) handleAccessLog(r *Request) {
v(r)
return
}
content := fmt.Sprintf(`"%s %s %s %s" %d %d`,
content := fmt.Sprintf(`"%s %s %s %s" %d`,
r.Method, r.Host, r.URL.String(), r.Proto,
r.Response.Status,
r.Response.ContentSize(),
)
content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime - r.EnterTime)/1000)
content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent())

View File

@ -8,13 +8,13 @@
package ghttp
import (
"strings"
"container/list"
"gitee.com/johng/gf/g/util/gregex"
"gitee.com/johng/gf/g/container/gset"
"fmt"
"runtime"
"gitee.com/johng/gf/g/container/gset"
"gitee.com/johng/gf/g/util/gregex"
"reflect"
"runtime"
"strings"
)
// 绑定指定的hook回调函数, pattern参数同BindHandler支持命名路由hook参数的值由ghttp server设定参数不区分大小写
@ -41,6 +41,10 @@ func (s *Server)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerF
// 事件回调处理,内部使用了缓存处理.
// 并按照指定hook回调函数的优先级及注册顺序进行调用
func (s *Server) callHookHandler(hook string, r *Request) {
// 如果没有hook注册那么不用执行后续逻辑
if len(s.hooksTree) == 0 {
return
}
hookItems := s.getHookHandlerWithCache(hook, r)
if len(hookItems) > 0 {
defer func() {
@ -74,15 +78,14 @@ func (s *Server) callHookHandler(hook string, r *Request) {
}
}
// 查询请求处理方法.
// 内部带锁机制可以并发读但是不能并发写并且有缓存机制按照Host、Method、Path进行缓存.
// 查询请求处理方法, 带缓存机制按照Host、Method、Path进行缓存.
func (s *Server) getHookHandlerWithCache(hook string, r *Request) []*handlerParsedItem {
cacheItems := ([]*handlerParsedItem)(nil)
cacheKey := s.hookHandlerKey(hook, r.Method, r.URL.Path, r.GetHost())
if v := s.hooksCache.Get(cacheKey); v == nil {
cacheItems = s.searchHookHandler(r.Method, r.URL.Path, r.GetHost(), hook)
if cacheItems != nil {
s.hooksCache.Set(cacheKey, cacheItems, 0)
s.hooksCache.Set(cacheKey, cacheItems)
}
} else {
cacheItems = v.([]*handlerParsedItem)
@ -148,7 +151,7 @@ func (s *Server) searchHookHandler(method, path, domain, hook string) []*handler
}
// 多层链表遍历检索,从数组末尾的链表开始遍历,末尾的深度高优先级也高
pushedSet := gset.NewStringSet()
pushedSet := gset.NewStringSet(false)
for i := len(lists) - 1; i >= 0; i-- {
for e := lists[i].Front(); e != nil; e = e.Next() {
handler := e.Value.(*handlerItem)

View File

@ -21,7 +21,7 @@ func (s *Server) getServeHandlerWithCache(r *Request) *handlerParsedItem {
if v := s.serveCache.Get(cacheKey); v == nil {
cacheItem = s.searchServeHandler(r.Method, r.URL.Path, r.GetHost())
if cacheItem != nil {
s.serveCache.Set(cacheKey, cacheItem, 0)
s.serveCache.Set(cacheKey, cacheItem)
}
} else {
cacheItem = v.(*handlerParsedItem)

View File

@ -8,77 +8,88 @@
package ghttp
import (
"sync"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/util/grand"
"strconv"
"strings"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/util/grand"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/container/gmap"
"time"
)
// 单个session对象
// SESSION对象
type Session struct {
mu sync.RWMutex // 并发安全互斥锁
id string // SessionId
data *gmap.StringInterfaceMap // Session数据
server *Server // 所属Server
id string // SessionId
data *gmap.StringInterfaceMap // Session数据
server *Server // 所属Server
request *Request // 关联的请求
}
// 生成一个唯一的sessionid字符串长度16
// 生成一个唯一的SessionId字符串长度16位
func makeSessionId() string {
return strings.ToUpper(strconv.FormatInt(gtime.Nanosecond(), 32) + grand.RandStr(3))
}
// 获取或者生成一个session对象
func GetSession(r *Request) *Session {
s := r.Server
sid := r.Cookie.SessionId()
if r := s.sessions.Get(sid); r != nil {
return r.(*Session)
return &Session {
data : gmap.NewStringInterfaceMap(),
server : r.Server,
request : r,
}
ses := &Session {
id : sid,
data : gmap.NewStringInterfaceMap(),
server : s,
}
s.sessions.Set(sid, ses, s.GetSessionMaxAge())
return ses
}
// 获取sessionid
// 执行初始化(用于延迟初始化)
func (s *Session) init() {
if len(s.id) == 0 {
s.id = s.request.Cookie.SessionId()
s.data = s.server.sessions.GetOrSetFuncLock(s.id, func() interface{} {
return gmap.NewStringInterfaceMap()
}, s.server.GetSessionMaxAge()).(*gmap.StringInterfaceMap)
}
}
// 获取SessionId
func (s *Session) Id() string {
s.init()
return s.id
}
// 获取当前session所有数据
func (s *Session) Data () map[string]interface{} {
func (s *Session) Data() map[string]interface{} {
s.init()
return s.data.Clone()
}
// 设置session
func (s *Session) Set (key string, value interface{}) {
func (s *Session) Set(key string, value interface{}) {
s.init()
s.data.Set(key, value)
}
// 批量设置(BatchSet别名)
func (s *Session) Sets (m map[string]interface{}) {
s.init()
s.BatchSet(m)
}
// 批量设置
func (s *Session) BatchSet (m map[string]interface{}) {
s.init()
s.data.BatchSet(m)
}
// 判断键名是否存在
func (s *Session) Contains (key string) bool {
s.init()
return s.data.Contains(key)
}
// 获取session
func (s *Session) Get (key string) interface{} { return s.data.Get(key) }
func (s *Session) Get (key string) interface{} {
s.init()
return s.data.Get(key)
}
func (s *Session) GetString (key string) string { return gconv.String(s.Get(key)) }
func (s *Session) GetBool(key string) bool { return gconv.Bool(s.Get(key)) }
@ -113,15 +124,19 @@ func (s *Session) GetStruct(key string, objPointer interface{}, attrMapping...ma
// 删除session
func (s *Session) Remove (key string) {
s.init()
s.data.Remove(key)
}
// 清空session
func (s *Session) Clear () {
s.init()
s.data.Clear()
}
// 更新过期时间(如果用在守护进程中长期使用,需要手动调用进行更新,防止超时被清除)
func (s *Session) UpdateExpire() {
s.server.sessions.Set(s.id, s, s.server.GetSessionMaxAge()*1000)
if len(s.id) > 0 {
s.server.sessions.Set(s.id, s.data, s.server.GetSessionMaxAge()*1000)
}
}

View File

@ -178,9 +178,7 @@ func (c *memCache) GetOrSet(key interface{}, value interface{}, expire int) inte
func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, expire int) interface{} {
if v := c.Get(key); v == nil {
// 可能存在多个goroutine被阻塞在这里f可能是并发运行
v = f()
c.doSetWithLockCheck(key, v, expire)
return v
return c.doSetWithLockCheck(key, f(), expire)
} else {
return v
}
@ -189,8 +187,7 @@ func (c *memCache) GetOrSetFunc(key interface{}, f func() interface{}, expire in
// 与GetOrSetFunc不同的是f是在写锁机制内执行
func (c *memCache) GetOrSetFuncLock(key interface{}, f func() interface{}, expire int) interface{} {
if v := c.Get(key); v == nil {
c.doSetWithLockCheck(key, f, expire)
return v
return c.doSetWithLockCheck(key, f, expire)
} else {
return v
}

View File

@ -15,7 +15,6 @@ import (
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gqueue"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/encoding/ghash"
"gitee.com/johng/gf/g/os/gcache"
"gitee.com/johng/gf/g/os/gcmd"
"gitee.com/johng/gf/g/os/genv"
@ -25,11 +24,12 @@ import (
// 监听管理对象
type Watcher struct {
watcher *fsnotify.Watcher // 底层fsnotify对象
events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件
closeChan chan struct{} // 关闭事件
callbacks *gmap.StringInterfaceMap // 监听的回调函数
cache *gcache.Cache // 缓存对象,用于事件重复过滤
watchers []*fsnotify.Watcher // 底层fsnotify对象支持多个以避免单个inotify对象监听队列上限问题
events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件
cache *gcache.Cache // 缓存对象,主要用于事件重复过滤
callbacks *gmap.StringInterfaceMap // 注册的所有绝对路径机器对应的回调函数列表map
recursivePaths *gmap.StringInterfaceMap // 支持递归监听的目录绝对路径及其对应的回调函数列表map
closeChan chan struct{} // 关闭事件
}
// 注册的监听回调方法
@ -37,6 +37,7 @@ type Callback struct {
Id int // 唯一ID
Func func(event *Event) // 回调方法
Path string // 监听的文件/目录
addr string // Func对应的内存地址用以判断回调的重复
elem *list.Element // 指向监听链表中的元素项位置
parent *Callback // 父级callback有这个属性表示该callback为被自动管理的callback
subs *glist.List // 子级回调对象指针列表
@ -64,68 +65,77 @@ const (
const (
REPEAT_EVENT_FILTER_INTERVAL = 1 // (毫秒)重复事件过滤间隔
DEFAULT_WATCHER_COUNT = 4 // 默认创建的监控对象数量(使用哈希取模)
DEFAULT_WATCHER_COUNT = 1 // 默认创建的监控对象数量(使用哈希取模)
gDEFAULT_PKG_WATCHER_COUNT = 4 // 默认创建的包监控对象数量(使用哈希取模)
)
var (
// 全局监听对象,方便应用端调用
watchers []*Watcher
// 全局默认的监听watcher数量
watcherCount int
// 默认的Watcher对象
defaultWatcher *Watcher
// 默认的watchers是否初始化使用时才创建
watcherInited = gtype.NewBool()
// 回调方法ID与对象指针的映射哈希表用于根据ID快速查找回调对象
callbackIdMap = gmap.NewIntInterfaceMap()
// 回调函数的ID生成器(原子操作)
callbackIdGenerator = gtype.NewInt()
)
// 初始化创建watcher对象用于包默认管理监听
func initWatcher() {
if !watcherInited.Set(true) {
// 默认的创建的inotify数量
watcherCount = gconv.Int(genv.Get("GF_INOTIFY_COUNT"))
if watcherCount == 0 {
watcherCount = gconv.Int(gcmd.Option.Get("gf.inotify-count"))
pkgWatcherCount := gconv.Int(genv.Get("GF_INOTIFY_COUNT"))
if pkgWatcherCount == 0 {
pkgWatcherCount = gconv.Int(gcmd.Option.Get("gf.inotify-count"))
}
if watcherCount == 0 {
watcherCount = DEFAULT_WATCHER_COUNT
if pkgWatcherCount == 0 {
pkgWatcherCount = gDEFAULT_PKG_WATCHER_COUNT
}
watchers = make([]*Watcher, watcherCount)
for i := 0; i < watcherCount; i++ {
if w, err := New(); err == nil {
watchers[i] = w
} else {
panic(err)
}
if w, err := New(pkgWatcherCount); err == nil {
defaultWatcher = w
} else {
panic(err)
}
}
}
// 创建监听管理对象主要注意的是创建监听对象会占用系统的inotify句柄数量受到 fs.inotify.max_user_instances 的限制
func New() (*Watcher, error) {
if watch, err := fsnotify.NewWatcher(); err == nil {
w := &Watcher {
cache : gcache.New(),
watcher : watch,
events : gqueue.New(),
closeChan : make(chan struct{}),
callbacks : gmap.NewStringInterfaceMap(),
}
w.startWatchLoop()
w.startEventLoop()
return w, nil
} else {
return nil, err
func New(inotifyCount...int) (*Watcher, error) {
count := DEFAULT_WATCHER_COUNT
if len(inotifyCount) > 0 {
count = inotifyCount[0]
}
w := &Watcher {
cache : gcache.New(),
watchers : make([]*fsnotify.Watcher, count),
events : gqueue.New(),
closeChan : make(chan struct{}),
callbacks : gmap.NewStringInterfaceMap(),
recursivePaths : gmap.NewStringInterfaceMap(),
}
for i := 0; i < count; i++ {
if watcher, err := fsnotify.NewWatcher(); err == nil {
w.watchers[i] = watcher
} else {
// 出错关闭已创建的底层watcher对象
for j := 0; j < i; j++ {
w.watchers[j].Close()
}
return nil, err
}
}
w.startWatchLoop()
w.startEventLoop()
return w, nil
}
// 添加对指定文件/目录的监听,并给定回调函数;如果给定的是一个目录,默认递归监控。
func Add(path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) {
return getWatcherByPath(path).Add(path, callbackFunc, recursive...)
return watcher().Add(path, callbackFunc, recursive...)
}
// 递归移除对指定文件/目录的所有监听回调
func Remove(path string) error {
return getWatcherByPath(path).Remove(path)
return watcher().Remove(path)
}
// 根据指定的回调函数ID移出指定的inotify回调函数
@ -137,11 +147,11 @@ func RemoveCallback(callbackId int) error {
if callback == nil {
return errors.New(fmt.Sprintf(`callback for id %d not found`, callbackId))
}
return getWatcherByPath(callback.Path).RemoveCallback(callbackId)
return watcher().RemoveCallback(callbackId)
}
// 根据path计算对应的watcher对象
func getWatcherByPath(path string) *Watcher {
// 获得默认的包watcher
func watcher() *Watcher {
initWatcher()
return watchers[ghash.BKDRHash([]byte(path)) % uint32(watcherCount)]
return defaultWatcher
}

View File

@ -10,24 +10,54 @@ import (
"errors"
"fmt"
"gitee.com/johng/gf/g/container/glist"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/encoding/ghash"
"gitee.com/johng/gf/third/github.com/fsnotify/fsnotify"
)
// 关闭监听管理对象
func (w *Watcher) Close() {
w.watcher.Close()
w.events.Close()
close(w.closeChan)
// 添加监控path参数支持文件或者目录路径recursive为非必需参数默认为递归添加监控(当path为目录时)。
// 如果添加目录这里只会返回目录的callback按照callback删除时会递归删除。
func (w *Watcher) Add(path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) {
return w.addWithCallbackFunc(nil, path, callbackFunc, recursive...)
}
// 添加监控path参数支持文件或者目录路径recursive为非必需参数默认为递归添加监控(当path为目录时)。
// 如果添加目录这里只会返回目录的callback按照callback删除时会递归删除。
func (w *Watcher) addWithCallbackFunc(parentCallback *Callback, path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) {
// 首先添加这个文件/目录
callback, err = w.doAddWithCallbackFunc(path, callbackFunc, parentCallback)
if err != nil {
return nil, err
}
// 其次递归添加其下的文件/目录
if fileIsDir(path) && (len(recursive) == 0 || recursive[0]) {
// 追加递归监控的回调到recursivePaths中
w.recursivePaths.LockFunc(func(m map[string]interface{}) {
list := (*glist.List)(nil)
if v, ok := m[path]; !ok {
list = glist.New()
m[path] = list
} else {
list = v.(*glist.List)
}
list.PushBack(callback)
})
// 递归添加监控
paths, _ := fileScanDir(path, "*", true)
for _, v := range paths {
w.doAddWithCallbackFunc(v, callbackFunc, callback)
}
}
return
}
// 添加对指定文件/目录的监听,并给定回调函数
func (w *Watcher) addWatch(path string, calbackFunc func(event *Event), parentCallback *Callback) (callback *Callback, err error) {
func (w *Watcher) doAddWithCallbackFunc(path string, callbackFunc func(event *Event), parentCallback *Callback) (callback *Callback, err error) {
// 这里统一转换为当前系统的绝对路径,便于统一监控文件名称
t := fileRealPath(path)
if t == "" {
if t := fileRealPath(path); t == "" {
return nil, errors.New(fmt.Sprintf(`"%s" does not exist`, path))
} else {
path = t
}
path = t
// 添加成功后会注册该callback id到全局的哈希表并绑定到父级的注册回调中
defer func() {
if err == nil {
@ -42,69 +72,61 @@ func (w *Watcher) addWatch(path string, calbackFunc func(event *Event), parentCa
}
}()
callback = &Callback {
Id : int(gtime.Nanosecond()),
Func : calbackFunc,
Id : callbackIdGenerator.Add(1),
Func : callbackFunc,
Path : path,
addr : fmt.Sprintf("%p", callbackFunc)[2:],
subs : glist.New(),
parent : parentCallback,
}
// 注册回调函数
w.callbacks.LockFunc(func(m map[string]interface{}) {
var result interface{}
list := (*glist.List)(nil)
if v, ok := m[path]; !ok {
result = glist.New()
m[path] = result
list = glist.New()
m[path] = list
} else {
result = v
list = v.(*glist.List)
}
callback.elem = result.(*glist.List).PushBack(callback)
callback.elem = list.PushBack(callback)
})
// 添加底层监听
w.watcher.Add(path)
w.watcher(path).Add(path)
return
}
// 添加监控path参数支持文件或者目录路径recursive为非必需参数默认为递归添加监控(当path为目录时)。
// 如果添加目录这里只会返回目录的callback按照callback删除时会递归删除。
func (w *Watcher) addWithCallback(parentCallback *Callback, path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) {
// 首先添加这个目录
if callback, err = w.addWatch(path, callbackFunc, parentCallback); err != nil {
return nil, err
}
// 其次递归添加其下的文件/目录
if fileIsDir(path) && (len(recursive) == 0 || recursive[0]) {
paths, _ := fileScanDir(path, "*", true)
for _, v := range paths {
w.addWatch(v, callbackFunc, callback)
}
}
return
// 根据path查询对应的底层watcher对象
func (w *Watcher) watcher(path string) *fsnotify.Watcher {
return w.watchers[ghash.BKDRHash([]byte(path)) % uint32(len(w.watchers))]
}
// 添加监控path参数支持文件或者目录路径recursive为非必需参数默认为递归添加监控(当path为目录时)。
// 如果添加目录这里只会返回目录的callback按照callback删除时会递归删除。
func (w *Watcher) Add(path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) {
return w.addWithCallback(nil, path, callbackFunc, recursive...)
// 关闭监听管理对象
func (w *Watcher) Close() {
w.events.Close()
close(w.closeChan)
for _, watcher := range w.watchers {
watcher.Close()
}
}
// 递归移除对指定文件/目录的所有监听回调
func (w *Watcher) Remove(path string) error {
if fileIsDir(path) {
if fileIsDir(path) && fileExists(path) {
paths, _ := fileScanDir(path, "*", true)
paths = append(paths, path)
for _, v := range paths {
if err := w.removeAll(v); err != nil {
if err := w.removeWatch(v); err != nil {
return err
}
}
return nil
} else {
return w.removeAll(path)
return w.removeWatch(path)
}
}
// 移除对指定文件/目录的所有监听
func (w *Watcher) removeAll(path string) error {
func (w *Watcher) removeWatch(path string) error {
// 首先移除所有该path的回调注册
if r := w.callbacks.Get(path); r != nil {
list := r.(*glist.List)
@ -119,7 +141,7 @@ func (w *Watcher) removeAll(path string) error {
// 其次移除该path的监听注册
w.callbacks.Remove(path)
// 最后移除底层的监听
return w.watcher.Remove(path)
return w.watcher(path).Remove(path)
}
// 根据指定的回调函数ID移出指定的inotify回调函数
@ -135,7 +157,7 @@ func (w *Watcher) RemoveCallback(callbackId int) error {
return nil
}
// 移除对指定文件/目录的所有监听
// (递归)移除对指定文件/目录的所有监听
func (w *Watcher) removeCallback(callback *Callback) error {
if r := w.callbacks.Get(callback.Path); r != nil {
list := r.(*glist.List)
@ -150,135 +172,12 @@ func (w *Watcher) removeCallback(callback *Callback) error {
}
}
}
// 如果该文件/目录的所有回调都被删除,那么移除监听
// 如果该文件/目录的所有回调都被删除,那么移除底层的监听
if list.Len() == 0 {
return w.watcher.Remove(callback.Path)
return w.watcher(callback.Path).Remove(callback.Path)
}
} else {
return errors.New(fmt.Sprintf(`callbacks not found for "%s"`, callback.Path))
}
return nil
}
// 监听循环
func (w *Watcher) startWatchLoop() {
go func() {
for {
select {
// 关闭事件
case <- w.closeChan:
return
// 监听事件
case ev := <- w.watcher.Events:
key := ev.String()
if !w.cache.Contains(key) {
w.cache.Set(key, struct {}{}, REPEAT_EVENT_FILTER_INTERVAL)
w.events.Push(&Event{
event : ev,
Path : ev.Name,
Op : Op(ev.Op),
Watcher : w,
})
}
case err := <- w.watcher.Errors:
panic("error : " + err.Error());
}
}
}()
}
// 检索给定path的回调方法**列表**
func (w *Watcher) getCallbacks(path string) *glist.List {
for {
if l := w.callbacks.Get(path); l != nil {
return l.(*glist.List)
} else {
if p := fileDir(path); p != path {
path = p
} else {
break
}
}
}
return nil
}
// 获得真正监听的文件路径,判断规则:
// 1、在 callbacks 中应当有回调注册函数(否则监听根本没意义)
// 2、如果该path下不存在回调注册函数则按照path长度从右往左递减直到减到目录地址为止(不包含)
// 3、如果仍旧无法匹配回调函数那么忽略否则使用查找到的新path覆盖掉event的path
func (w *Watcher) getWatchTruePath(path string) string {
if w.getCallbacks(path) != nil {
return path
}
dirPath := fileDir(path)
for {
path = path[0 : len(path) - 1]
if path == dirPath {
break
}
if w.getCallbacks(path) != nil {
return path
}
}
return ""
}
// 事件循环
func (w *Watcher) startEventLoop() {
go func() {
for {
if v := w.events.Pop(); v != nil {
event := v.(*Event)
if path := w.getWatchTruePath(event.Path); path == "" {
continue
} else {
event.Path = path
}
switch {
// 如果是删除操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假删除”
case event.IsRemove():
if fileExists(event.Path) {
// 重新添加监控(底层fsnotify会自动删除掉监控这里重新添加回去)
// 注意这里调用的是底层fsnotify添加监控只会产生回调事件并不会使回调函数重复注册
w.watcher.Add(event.Path)
// 修改事件操作为重命名(相当于重命名为自身名称,最终名称没变)
event.Op = RENAME
} else {
// 如果是真实删除,那么递归删除监控信息
w.Remove(event.Path)
}
// 如果是删除操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假命名”
// (特别是某些编辑器在编辑文件时会先对文件RENAME再CHMOD)
case event.IsRename():
if fileExists(event.Path) {
// 重新添加监控
w.watcher.Add(event.Path)
}
}
callbacks := w.getCallbacks(event.Path)
// 如果创建了新的目录,那么将这个目录递归添加到监控中
if event.IsCreate() && fileIsDir(event.Path) {
for _, v := range callbacks.FrontAll() {
callback := v.(*Callback)
w.addWithCallback(callback, event.Path, callback.Func)
}
}
// 执行回调处理,异步处理
if callbacks != nil {
go func(callbacks *glist.List) {
for _, v := range callbacks.FrontAll() {
go v.(*Callback).Func(event)
}
}(callbacks)
}
} else {
break
}
}
}()
}

View File

@ -0,0 +1,145 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gfsnotify
import (
"errors"
"fmt"
"gitee.com/johng/gf/g/container/glist"
"gitee.com/johng/gf/g/encoding/ghash"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/third/github.com/fsnotify/fsnotify"
"sync"
"time"
)
// 监听循环
func (w *Watcher) startWatchLoop() {
for i := 0; i < len(w.watchers); i++ {
go func(i int) {
for {
select {
// 关闭事件
case <- w.closeChan: return
// 监听事件
case ev := <- w.watchers[i].Events:
//fmt.Println("ev:", ev.String())
w.cache.SetIfNotExist(ev.String(), func() interface{} {
w.events.Push(&Event{
event : ev,
Path : ev.Name,
Op : Op(ev.Op),
Watcher : w,
})
return struct {}{}
}, REPEAT_EVENT_FILTER_INTERVAL)
case err := <- w.watchers[i].Errors:
fmt.Errorf("error: %s\n" + err.Error());
}
}
}(i)
}
}
// 获得真正监听的文件路径及回调函数列表,假如是临时文件或者新增文件,是无法搜索都监听回调的。
// 判断规则:
// 1、在 callbacks 中应当有回调注册函数(否则监听根本没意义)
// 2、如果该path下不存在回调注册函数则按照path长度从右往左递减直到减到目录地址为止(不包含)
// 3、如果仍旧无法匹配回调函数那么忽略否则使用查找到的新path覆盖掉event的path
// 解决问题:
// 1、部分IDE修改文件时生成的临时文件如: /index.html -> /index.html__jdold__
// 2、新增文件目录的回调搜索
func (w *Watcher) getWatchPathAndCallbacks(path string) (watchPath string, callbacks *glist.List) {
if path == "" {
return "", nil
}
for {
if v := w.callbacks.Get(path); v != nil {
return path, v.(*glist.List)
}
path = path[0 : len(path) - 1]
// 如果不能再继续递减,那么退出
if len(path) == 0 {
break
}
}
return "", nil
}
// 事件循环
func (w *Watcher) startEventLoop() {
go func() {
for {
if v := w.events.Pop(); v != nil {
event := v.(*Event)
// watchPath是注册回调的路径可能和event.Path不一样
watchPath, callbacks := w.getWatchPathAndCallbacks(event.Path)
if callbacks == nil {
continue
}
fmt.Println("event:", event.String(), watchPath, fileExists(watchPath))
switch {
// 如果是删除操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假删除”
case event.IsRemove():
if fileExists(watchPath) {
// 重新添加监控(底层fsnotify会自动删除掉监控这里重新添加回去)
// 注意这里调用的是底层fsnotify添加监控只会产生回调事件并不会使回调函数重复注册
w.watcher.Add(event.Path)
// 修改事件操作为重命名(相当于重命名为自身名称,最终名称没变)
event.Op = RENAME
} else {
// 删除之前需要执行一遍回调否则Remove之后就无法执行了
// 由于是异步回调,这里保证所有回调都开始执行后再执行删除
wg := sync.WaitGroup{}
for _, v := range callbacks.FrontAll() {
wg.Add(1)
go func(callback *Callback) {
wg.Done()
callback.Func(event)
}(v.(*Callback))
}
wg.Wait()
time.Sleep(time.Second)
// 如果是真实删除,那么递归删除监控信息
fmt.Println("remove", watchPath)
w.Remove(watchPath)
}
// 如果是重命名操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假命名”
// (特别是某些编辑器在编辑文件时会先对文件RENAME再CHMOD)
case event.IsRename():
if fileExists(watchPath) {
// 重新添加监控
w.watcher.Add(watchPath)
} else if watchPath != event.Path && fileExists(event.Path) {
for _, v := range callbacks.FrontAll() {
callback := v.(*Callback)
w.addWithCallback(callback, event.Path, callback.Func)
}
}
// 创建文件/目录
case event.IsCreate():
for _, v := range callbacks.FrontAll() {
callback := v.(*Callback)
w.addWithCallback(callback, event.Path, callback.Func)
}
}
// 执行回调处理,异步处理
for _, v := range callbacks.FrontAll() {
go v.(*Callback).Func(event)
}
} else {
break
}
}
}()
}

View File

@ -11,112 +11,192 @@ package gspath
import (
"errors"
"fmt"
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/os/gfsnotify"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/util/gstr"
"runtime"
"strings"
"sync"
)
// 文件目录搜索管理对象
type SPath struct {
mu sync.RWMutex
paths []string // 搜索路径,按照优先级进行排序
cache *gmap.StringStringMap // 搜索结果缓存map
paths *garray.StringArray // 搜索路径,按照优先级进行排序
cache *gmap.StringInterfaceMap // 搜索结果缓存map
}
// 文件搜索缓存项
type SPathCacheItem struct {
path string // 文件/目录绝对路径
isDir bool // 是否目录
}
// 创建一个搜索对象
func New () *SPath {
return &SPath{
paths : make([]string, 0),
cache : gmap.NewStringStringMap(),
return &SPath {
paths : garray.NewStringArray(0, 2),
cache : gmap.NewStringInterfaceMap(),
}
}
// 设置搜索路径,只保留当前设置项,其他搜索路径被清空
func (sp *SPath) Set(path string) (realpath string, err error) {
realpath = gfile.RealPath(path)
if realpath == "" {
realpath = sp.Search(path)
if realpath == "" {
realpath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
func (sp *SPath) Set(path string) (realPath string, err error) {
realPath = gfile.RealPath(path)
if realPath == "" {
realPath = sp.Search(path)
if realPath == "" {
realPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
}
}
if realpath == "" {
return realpath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
if realPath == "" {
return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
}
if realpath != "" && gfile.IsDir(realpath) {
realpath = strings.TrimRight(realpath, gfile.Separator)
sp.mu.Lock()
sp.paths = []string{realpath}
sp.mu.Unlock()
if realPath == "" {
return realPath, errors.New("invalid path:" + path)
}
// 设置的搜索路径必须为目录
if gfile.IsDir(realPath) {
realPath = strings.TrimRight(realPath, gfile.Separator)
if sp.paths.Search(realPath) != -1 {
for _, v := range sp.paths.Slice() {
sp.removeMonitorByPath(v)
}
}
sp.paths.Clear()
sp.cache.Clear()
//glog.Debug("gspath.SetPath:", r)
return realpath, nil
sp.paths.Append(realPath)
sp.updateCacheByPath(realPath)
sp.addMonitorByPath(realPath)
return realPath, nil
} else {
return "", errors.New(path + " should be a folder")
}
//glog.Warning("gspath.SetPath failed:", path)
return realpath, errors.New("invalid path:" + path)
}
// 添加搜索路径
func (sp *SPath) Add(path string) (realpath string, err error) {
realpath = gfile.RealPath(path)
if realpath == "" {
realpath = sp.Search(path)
if realpath == "" {
realpath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
func (sp *SPath) Add(path string) (realPath string, err error) {
realPath = gfile.RealPath(path)
if realPath == "" {
realPath = sp.Search(path)
if realPath == "" {
realPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
}
}
if realpath == "" {
return realpath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
if realPath == "" {
return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path))
}
if realpath != "" && gfile.IsDir(realpath) {
realpath = strings.TrimRight(realpath, gfile.Separator)
sp.mu.Lock()
sp.paths = append(sp.paths, realpath)
sp.mu.Unlock()
//glog.Debug("gspath.Add:", r)
return realpath, nil
if realPath == "" {
return realPath, errors.New("invalid path:" + path)
}
// 添加的搜索路径必须为目录
if gfile.IsDir(realPath) {
// 如果已经添加则不再添加
if sp.paths.Search(realPath) < 0 {
realPath = strings.TrimRight(realPath, gfile.Separator)
sp.paths.Append(realPath)
sp.updateCacheByPath(realPath)
sp.addMonitorByPath(realPath)
}
return realPath, nil
} else {
return "", errors.New(path + " should be a folder")
}
//glog.Warning("gspath.Add failed:", path)
return realpath, errors.New("invalid path:" + path)
}
// 按照优先级搜索文件,返回搜索到的文件绝对路径,找不到该文件时,返回空字符串
// 给定的name只是相对文件路径或者只是一个文件名
func (sp *SPath) Search(name string) string {
path := sp.cache.Get(name)
if path == "" {
sp.mu.RLock()
for _, v := range sp.paths {
path = gfile.RealPath(v + gfile.Separator + name)
if path != "" && gfile.Exists(path) {
break
// 给定的name只是相对文件路径找不到该文件时返回空字符串;
// 当给定indexFiles时如果name时一个目录那么会进一步检索其下对应的indexFiles文件是否存在存在则返回indexFile 绝对路径;
// 否则返回name目录绝对路径。
func (sp *SPath) Search(name string, indexFiles...string) string {
name = sp.formatCacheName(name)
if v := sp.cache.Get(name); v != nil {
item := v.(*SPathCacheItem)
if len(indexFiles) > 0 && item.isDir {
for _, file := range indexFiles {
if v := sp.cache.Get(name + "/" + file); v != nil {
return v.(*SPathCacheItem).path
}
}
}
sp.mu.RUnlock()
if path != "" {
sp.cache.Set(name, path)
sp.addMonitor(name, path)
}
return item.path
}
return path
return ""
}
// 当前的搜索路径数量
func (sp *SPath) Size() int {
sp.mu.RLock()
length := len(sp.paths)
sp.mu.RUnlock()
return length
return sp.paths.Len()
}
// 添加文件监控,当文件删除时,同时也删除搜索结果缓存
func (sp *SPath) addMonitor(name, path string) {
//glog.Debug("gspath.addMonitor:", name, path)
gfsnotify.Add(path, func(event *gfsnotify.Event) {
//glog.Debug("gspath.monitor:", event)
if event.IsRemove() {
sp.cache.Remove(name)
// 递归添加目录下的文件
func (sp *SPath) updateCacheByPath(path string) {
sp.addToCache(path, path)
}
// 格式化name返回符合规范的缓存名称分隔符号统一为'/',且前缀必须以'/'开头(类似HTTP URI).
func (sp *SPath) formatCacheName(name string) string {
if name == "" {
return "/"
}
if runtime.GOOS != "linux" {
name = gstr.Replace(name, "\\", "/")
}
if name[0] == '/' {
return name
}
return "/" + name
}
// 根据path计算出对应的缓存name
func (sp *SPath) nameFromPath(filePath, dirPath string) string {
name := gstr.Replace(filePath, dirPath, "")
name = sp.formatCacheName(name)
return name
}
// 添加path到缓存中(递归)
func (sp *SPath) addToCache(filePath, dirPath string) {
// 首先添加自身
idDir := gfile.IsDir(filePath)
sp.cache.SetIfNotExist(sp.nameFromPath(filePath, dirPath), func() interface{} {
return &SPathCacheItem {
path : filePath,
isDir : idDir,
}
}, false)
})
// 如果添加的是目录,那么需要递归
if idDir {
if files, err := gfile.ScanDir(filePath, "*", true); err == nil {
for _, path := range files {
sp.addToCache(path, dirPath)
}
}
}
}
// 添加文件目录监控(递归),当目录下的文件有更新时,会同时更新缓存。
// 这里需要注意的点是,由于添加监听是递归添加的,那么假如删除一个目录,那么该目录下的文件(包括目录)也会产生一条删除事件总共会产生N条事件。
func (sp *SPath) addMonitorByPath(path string) {
gfsnotify.Add(path, func(event *gfsnotify.Event) {
glog.Debug(event.String())
switch {
case event.IsRemove():
sp.cache.Remove(sp.nameFromPath(event.Path, path))
case event.IsRename():
if !gfile.Exists(event.Path) {
sp.cache.Remove(sp.nameFromPath(event.Path, path))
}
case event.IsCreate():
sp.addToCache(event.Path, path)
}
})
}
// 删除监听(递归)
func (sp *SPath) removeMonitorByPath(path string) {
gfsnotify.Remove(path)
}

View File

@ -0,0 +1,37 @@
// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gspath
import (
"testing"
)
var (
sp = New()
)
func init() {
sp.Add("/Users/john/Temp")
}
func Benchmark_Search(b *testing.B) {
for i := 0; i < b.N; i++ {
sp.Search("1")
}
}
func Benchmark_Search_None(b *testing.B) {
for i := 0; i < b.N; i++ {
sp.Search("1000")
}
}
func Benchmark_Search_IndexFiles(b *testing.B) {
for i := 0; i < b.N; i++ {
sp.Search("1", "index.html")
}
}

View File

@ -31,15 +31,13 @@ const (
// "2018-02-09",
// "2018.02.09",
// 日期连接符号支持'-'、'/'、'.'
TIME_REAGEX_PATTERN1 = `(\d{2,4}[-/\.]\d{2}[-/\.]\d{2})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)`
TIME_REAGEX_PATTERN1 = `(\d{4}[-/\.]\d{2}[-/\.]\d{2})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)`
// 01-Nov-2018 11:50:28
// 01/Nov/2018 11:50:28
// 01/Nov/2018:11:50:28
// 01/Nov/18 11:50:28
// 01/Nov/18 11:50:28
// 01.Nov.18 11:50:28
// 01.Nov.2018 11:50:28
// 01.Nov.2018:11:50:28
// 日期连接符号支持'-'、'/'、'.'
TIME_REAGEX_PATTERN2 = `(\d{1,2}[-/\.][A-Za-z]{3,}[-/\.]\d{2,4})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)`
TIME_REAGEX_PATTERN2 = `(\d{1,2}[-/\.][A-Za-z]{3,}[-/\.]\d{4})[:\sT-]*(\d{0,2}:{0,1}\d{0,2}:{0,1}\d{0,2}){0,1}\.{0,1}(\d{0,9})([\sZ]{0,1})([\+-]{0,1})([:\d]*)`
)
var (

View File

@ -126,10 +126,16 @@ func Check(value interface{}, rules string, msgs interface{}, params...map[strin
// 日期格式,
case "date":
// 使用标准日期格式检查,但是日期之间必须带连接符号
if _, err := gtime.StrToTime(val); err == nil {
match = true
break
}
// 检查是否不带日期连接符号的格式
if _, err := gtime.StrToTime(val, "Ymd"); err == nil {
match = true
break
}
// 日期格式,需要给定日期格式
case "date-format":

View File

@ -1,13 +0,0 @@
package main
import (
"gitee.com/johng/gf/g/net/ghttp"
)
func main() {
s := ghttp.GetServer()
s.SetIndexFolder(true)
s.SetServerRoot("/home/john/Workspace/Go/gf-home/static/plugin/editor.md/css")
s.SetPort(8199)
s.Run()
}

View File

@ -3,6 +3,7 @@ package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
"gitee.com/johng/gf/g/util/gconv"
)
func main() {
@ -10,7 +11,7 @@ func main() {
s.BindHandler("/session", func(r *ghttp.Request) {
id := r.Session.GetInt("id")
r.Session.Set("id", id + 1)
r.Response.Write("id:", id)
r.Response.Write("id:" + gconv.String(id))
})
s.SetPort(8199)
s.Run()

View File

@ -0,0 +1,12 @@
package main
import "gitee.com/johng/gf/g"
// 静态文件服务器
func main() {
s := g.Server()
s.SetIndexFolder(true)
s.SetServerRoot("/Users/john/Documents")
s.SetPort(8199)
s.Run()
}

View File

@ -7,14 +7,15 @@ import (
)
func main() {
//创建一个监控对象
// 创建一个监控对象
watch, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watch.Close()
//添加要监控的对象,文件或文件夹
err = watch.Add("D:\\Workspace\\Go\\GOPATH\\src\\gitee.com\\johng\\gf\\geg\\other\\test.go")
//err = watch.Add("D:\\Workspace\\Go\\GOPATH\\src\\gitee.com\\johng\\gf\\geg\\other\\test.go")
err = watch.Add("/Users/john/Temp/1/2")
if err != nil {
log.Fatal(err)
}

View File

@ -6,7 +6,8 @@ import (
)
func main() {
path := "D:\\Workspace\\Go\\GOPATH\\src\\gitee.com\\johng\\gf\\geg\\other\\test.go"
//path := "D:\\Workspace\\Go\\GOPATH\\src\\gitee.com\\johng\\gf\\geg\\other\\test.go"
path := "/Users/john/Temp/1/2/3"
_, err := gfsnotify.Add(path, func(event *gfsnotify.Event) {
glog.Println(event)
})

19
geg/os/gspath/gspath.go Normal file
View File

@ -0,0 +1,19 @@
package main
import (
"fmt"
"gitee.com/johng/gf/g/os/gspath"
"gitee.com/johng/gf/g/os/gtime"
)
func main() {
sp := gspath.New()
path := "/Users/john/Temp"
rp, err := sp.Add(path)
fmt.Println(err)
fmt.Println(rp)
fmt.Println(gtime.FuncCost(func() {
sp.Search("1")
}))
fmt.Println(sp.Search("1", "index.html"))
}

View File

@ -1,21 +1,9 @@
package main
import (
"gitee.com/johng/gf/g"
"gitee.com/johng/gf/g/net/ghttp"
"fmt"
)
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
})
s.BindHandler("/user", func(r *ghttp.Request) {
})
s.BindHandler("/user/:id", func(r *ghttp.Request) {
r.Response.Write(r.Get("id"))
})
s.SetPort(3000)
s.Run()
fmt.Println("123"[2:])
}