mirror of
https://gitee.com/johng/gf.git
synced 2024-12-01 11:48:09 +08:00
性能改进中
This commit is contained in:
parent
7f640da9a1
commit
e8101cb02f
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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地址
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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]
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
145
g/os/gfsnotify/gfsnotify_watcher_loop.go
Normal file
145
g/os/gfsnotify/gfsnotify_watcher_loop.go
Normal 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
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
@ -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)
|
||||
}
|
37
g/os/gspath/gspath_test.go
Normal file
37
g/os/gspath/gspath_test.go
Normal 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")
|
||||
}
|
||||
}
|
@ -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 (
|
||||
|
@ -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":
|
||||
|
@ -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()
|
||||
}
|
@ -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()
|
||||
|
12
geg/net/ghttp/server/static.go
Normal file
12
geg/net/ghttp/server/static.go
Normal 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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
19
geg/os/gspath/gspath.go
Normal 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"))
|
||||
}
|
@ -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:])
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user