This commit is contained in:
zhangjinfu
2018-08-23 16:12:01 +08:00
23 changed files with 458 additions and 168 deletions

3
TODO
View File

@ -19,6 +19,7 @@ ghttp.Server的Cookie及Session锁机制优化(去掉map锁机制);
ghttp.Server增加Ip访问控制功能(DenyIps&AllowIps)
ghttp路由功能增加分组路由特性
解决glog串日志情况
ghttp增加返回数据压缩机制
DONE:
@ -53,4 +54,4 @@ DONE:
29. gdb增加查询缓存特性
30. gpage分页增加对自定义后缀的支持如:2.html, 2.php等等
31. gvalid包增加struct tag的校验规则、自定义错误提示信息绑定的支持特性
32. 增加文件缓存包可根据fsnotify机制进行缓存更新

View File

@ -48,14 +48,14 @@ func (a *IntArray) Get(index int) int {
// 设置指定索引的数据项, 调用方注意判断数组边界
func (a *IntArray) Set(index int, value int) {
a.mu.Lock()
defer a.mu.RUnlock()
defer a.mu.Unlock()
a.array[index] = value
}
// 在当前索引位置前插入一个数据项, 调用方注意判断数组边界
func (a *IntArray) Insert(index int, value int) {
a.mu.Lock()
defer a.mu.RUnlock()
defer a.mu.Unlock()
rear := append([]int{}, a.array[index : ]...)
a.array = append(a.array[0 : index], value)
a.array = append(a.array, rear...)
@ -65,14 +65,14 @@ func (a *IntArray) Insert(index int, value int) {
// 删除指定索引的数据项, 调用方注意判断数组边界
func (a *IntArray) Remove(index int) {
a.mu.Lock()
defer a.mu.RUnlock()
defer a.mu.Unlock()
a.array = append(a.array[ : index], a.array[index + 1 : ]...)
}
// 追加数据项
func (a *IntArray) Append(value int) {
a.mu.Lock()
defer a.mu.RUnlock()
defer a.mu.Unlock()
a.array = append(a.array, value)
}
@ -95,7 +95,7 @@ func (a *IntArray) Slice() []int {
// 清空数据数组
func (a *IntArray) Clear() {
a.mu.Lock()
defer a.mu.RUnlock()
defer a.mu.Unlock()
if a.cap > 0 {
a.array = make([]int, a.size, a.cap)
} else {
@ -148,6 +148,6 @@ func (a *IntArray) LockFunc(f func(array []int)) {
// 使用自定义方法执行加锁读取操作
func (a *IntArray) RLockFunc(f func(array []int)) {
a.mu.RLock()
defer a.mu.Unlock()
defer a.mu.RUnlock()
f(a.array)
}

View File

@ -20,22 +20,23 @@ import (
// 请求对象
type Request struct {
http.Request
parsedGet *gtype.Bool // GET参数是否已经解析
parsedPost *gtype.Bool // POST参数是否已经解析
queryVars map[string][]string // GET参数
routerVars map[string][]string // 路由解析参数
exit *gtype.Bool // 是否退出当前请求流程执行
Id int // 请求id(唯一)
Server *Server // 请求关联的服务器对象
Cookie *Cookie // 与当前请求绑定的Cookie对象(并发安全)
Session *Session // 与当前请求绑定的Session对象(并发安全)
Response *Response // 对应请求的返回数据操作对象
Router *Router // 匹配到的路由对象
EnterTime int64 // 请求进入时间(微秒)
LeaveTime int64 // 请求完成时间(微秒)
Param interface{} // 开发者自定义参数
parsedHost *gtype.String // 解析过后不带端口号的服务器域名名称
clientIp *gtype.String // 解析过后的客户端IP地址
parsedGet *gtype.Bool // GET参数是否已经解析
parsedPost *gtype.Bool // POST参数是否已经解析
queryVars map[string][]string // GET参数
routerVars map[string][]string // 路由解析参数
exit *gtype.Bool // 是否退出当前请求流程执行
Id int // 请求id(唯一)
Server *Server // 请求关联的服务器对象
Cookie *Cookie // 与当前请求绑定的Cookie对象(并发安全)
Session *Session // 与当前请求绑定的Session对象(并发安全)
Response *Response // 对应请求的返回数据操作对象
Router *Router // 匹配到的路由对象
EnterTime int64 // 请求进入时间(微秒)
LeaveTime int64 // 请求完成时间(微秒)
Param interface{} // 开发者自定义参数
parsedHost *gtype.String // 解析过后不带端口号的服务器域名名称
clientIp *gtype.String // 解析过后的客户端IP地址
isFileRequest bool // 是否为静态文件请求(非服务请求,当静态文件存在时,优先级会被服务请求高,被识别为文件请求)
}
// 创建一个Request对象
@ -153,6 +154,16 @@ func (r *Request) GetHost() string {
return host
}
// 判断是否为静态文件请求
func (r *Request) IsFileRequest() bool {
return r.isFileRequest
}
// 判断是否为AJAX请求
func (r *Request) IsAjaxRequest() bool {
return strings.EqualFold(r.Header.Get("X-Requested-With"), "XMLHttpRequest")
}
// 获取请求的客户端IP地址
func (r *Request) GetClientIp() string {
ip := r.clientIp.Val()

View File

@ -68,7 +68,7 @@ func (r *Request) GetPostBool(key string, def ... bool) bool {
func (r *Request) GetPostInt(key string, def ... int) int {
value := r.GetPostString(key)
if value != "" {
return gconv.Int(value[0])
return gconv.Int(value)
}
if len(def) > 0 {
return def[0]
@ -79,7 +79,7 @@ func (r *Request) GetPostInt(key string, def ... int) int {
func (r *Request) GetPostUint(key string, def ... uint) uint {
value := r.GetPostString(key)
if value != "" {
return gconv.Uint(value[0])
return gconv.Uint(value)
}
if len(def) > 0 {
return def[0]
@ -90,7 +90,7 @@ func (r *Request) GetPostUint(key string, def ... uint) uint {
func (r *Request) GetPostFloat32(key string, def ... float32) float32 {
value := r.GetPostString(key)
if value != "" {
return gconv.Float32(value[0])
return gconv.Float32(value)
}
if len(def) > 0 {
return def[0]
@ -101,7 +101,7 @@ func (r *Request) GetPostFloat32(key string, def ... float32) float32 {
func (r *Request) GetPostFloat64(key string, def ... float64) float64 {
value := r.GetPostString(key)
if value != "" {
return gconv.Float64(value[0])
return gconv.Float64(value)
}
if len(def) > 0 {
return def[0]

View File

@ -67,7 +67,7 @@ func (r *Request) GetQueryBool(key string, def ... bool) bool {
func (r *Request) GetQueryInt(key string, def ... int) int {
value := r.GetQueryString(key)
if value != "" {
return gconv.Int(value[0])
return gconv.Int(value)
}
if len(def) > 0 {
return def[0]
@ -78,7 +78,7 @@ func (r *Request) GetQueryInt(key string, def ... int) int {
func (r *Request) GetQueryUint(key string, def ... uint) uint {
value := r.GetQueryString(key)
if value != "" {
return gconv.Uint(value[0])
return gconv.Uint(value)
}
if len(def) > 0 {
return def[0]
@ -89,7 +89,7 @@ func (r *Request) GetQueryUint(key string, def ... uint) uint {
func (r *Request) GetQueryFloat32(key string, def ... float32) float32 {
value := r.GetQueryString(key)
if value != "" {
return gconv.Float32(value[0])
return gconv.Float32(value)
}
if len(def) > 0 {
return def[0]
@ -100,7 +100,7 @@ func (r *Request) GetQueryFloat32(key string, def ... float32) float32 {
func (r *Request) GetQueryFloat64(key string, def ... float64) float64 {
value := r.GetQueryString(key)
if value != "" {
return gconv.Float64(value[0])
return gconv.Float64(value)
}
if len(def) > 0 {
return def[0]

View File

@ -39,7 +39,7 @@ func (r *Request) GetRequestString(key string, def ... string) string {
func (r *Request) GetRequestBool(key string, def ... bool) bool {
value := r.GetRequestString(key)
if value != "" {
return gconv.Bool(value[0])
return gconv.Bool(value)
}
if len(def) > 0 {
return def[0]
@ -50,7 +50,7 @@ func (r *Request) GetRequestBool(key string, def ... bool) bool {
func (r *Request) GetRequestInt(key string, def ... int) int {
value := r.GetRequestString(key)
if value != "" {
return gconv.Int(value[0])
return gconv.Int(value)
}
if len(def) > 0 {
return def[0]
@ -61,7 +61,7 @@ func (r *Request) GetRequestInt(key string, def ... int) int {
func (r *Request) GetRequestUint(key string, def ... uint) uint {
value := r.GetRequestString(key)
if value != "" {
return gconv.Uint(value[0])
return gconv.Uint(value)
}
if len(def) > 0 {
return def[0]
@ -72,7 +72,7 @@ func (r *Request) GetRequestUint(key string, def ... uint) uint {
func (r *Request) GetRequestFloat32(key string, def ... float32) float32 {
value := r.GetRequestString(key)
if value != "" {
return gconv.Float32(value[0])
return gconv.Float32(value)
}
if len(def) > 0 {
return def[0]
@ -83,7 +83,7 @@ func (r *Request) GetRequestFloat32(key string, def ... float32) float32 {
func (r *Request) GetRequestFloat64(key string, def ... float64) float64 {
value := r.GetRequestString(key)
if value != "" {
return gconv.Float64(value[0])
return gconv.Float64(value)
}
if len(def) > 0 {
return def[0]

View File

@ -8,7 +8,6 @@
package ghttp
import (
"sync"
"net/http"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/encoding/gparser"
@ -17,46 +16,29 @@ import (
"gitee.com/johng/gf/g/frame/gins"
)
// 服务端请求返回对象
// 服务端请求返回对象
// 注意该对象并没有实现http.ResponseWriter接口而是依靠ghttp.ResponseWriter实现。
type Response struct {
ResponseWriter
Server *Server
Writer *ResponseWriter // io.Writer
mu sync.RWMutex // 缓冲区互斥锁
buffer []byte // 每个请求的返回数据缓冲区
Writer *ResponseWriter // ResponseWriter的别名
request *Request // 关联的Request请求对象
}
// 自定义的ResponseWriter用于写入流的控制
type ResponseWriter struct {
http.ResponseWriter
Status int // http status
Length int // response length
}
// 创建一个ghttp.Response对象指针
func newResponse(s *Server, w http.ResponseWriter) *Response {
r := &Response {
Server : s,
ResponseWriter : ResponseWriter{w, http.StatusOK, 0},
ResponseWriter : ResponseWriter{
ResponseWriter : w,
Status : http.StatusOK,
buffer : make([]byte, 0),
},
}
r.Writer = &r.ResponseWriter
return r
}
// 覆盖父级的WriteHeader方法
func (w *ResponseWriter) Write(buffer []byte) (int, error) {
n, e := w.ResponseWriter.Write(buffer)
w.Length += n
return n, e
}
// 覆盖父级的WriteHeader方法
func (w *ResponseWriter) WriteHeader(code int) {
w.Status = code
w.ResponseWriter.WriteHeader(code)
}
// 返回信息任何变量自动转换为bytes
func (r *Response) Write(content ... interface{}) {
if len(content) == 0 {
@ -225,11 +207,6 @@ func (r *Response) ClearBuffer() {
// 输出缓冲区数据到客户端
func (r *Response) OutputBuffer() {
r.Header().Set("Server", r.Server.config.ServerAgent)
if len(r.buffer) > 0 {
r.mu.Lock()
r.ResponseWriter.Write(r.buffer)
r.buffer = make([]byte, 0)
r.mu.Unlock()
}
//r.handleGzip()
r.Writer.OutputBuffer()
}

View File

@ -0,0 +1,78 @@
// 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 ghttp
import (
"mime"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/encoding/gcompress"
"strings"
"gitee.com/johng/gf/g/util/gconv"
)
// 默认的gzip压缩文件类型
var defaultGzipContentTypes = []string{
"application/atom+xml",
"application/font-sfnt",
"application/javascript",
"application/json",
"application/ld+json",
"application/manifest+json",
"application/rdf+xml",
"application/rss+xml",
"application/schema+json",
"application/vnd.geo+json",
"application/vnd.ms-fontobject",
"application/x-font-ttf",
"application/x-javascript",
"application/x-web-app-manifest+json",
"application/xhtml+xml",
"application/xml",
"font/eot",
"font/opentype",
"image/bmp",
"image/svg+xml",
"image/vnd.microsoft.icon",
"image/x-icon",
"text/cache-manifest",
"text/css",
"text/html",
"text/javascript",
"text/plain",
"text/vcard",
"text/vnd.rim.location.xloc",
"text/vtt",
"text/x-component",
"text/x-cross-domain-policy",
"text/xml",
}
// 返回内容gzip检查处理
func (r *Response) handleGzip() {
// 如果客户端支持gzip压缩并且服务端设置开启gzip压缩特性那么执行压缩
encoding := r.request.Header.Get("Accept-Encoding")
if encoding != "" && strings.Contains(encoding, "gzip") {
mimeType := ""
ext := gfile.Ext(r.request.URL.Path)
if ext != "" {
mimeType = strings.Split(mime.TypeByExtension(ext), ";")[0]
}
if mimeType == "" {
contentType := r.Header().Get("Content-Type")
if contentType != "" {
mimeType = strings.Split(contentType, ";")[0]
}
}
if _, ok := r.Server.gzipMimesMap[mimeType]; ok {
r.SetBuffer(gcompress.Gzip(r.buffer))
r.Header().Set("Content-Length", gconv.String(len(r.buffer)))
r.Header().Set("Content-Encoding", "gzip")
}
}
}

View File

@ -0,0 +1,43 @@
// 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 ghttp
import (
"net/http"
"sync"
)
// 自定义的ResponseWriter用于写入流的控制
type ResponseWriter struct {
http.ResponseWriter
mu sync.RWMutex // 缓冲区互斥锁
Status int // http status
buffer []byte // 缓冲区内容
}
// 覆盖父级的WriteHeader方法
func (w *ResponseWriter) Write(buffer []byte) (int, error) {
w.buffer = append(w.buffer, buffer...)
return len(buffer), nil
}
// 覆盖父级的WriteHeader方法
func (w *ResponseWriter) WriteHeader(code int) {
w.Status = code
w.ResponseWriter.WriteHeader(code)
}
// 输出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()
}
}

View File

@ -62,7 +62,7 @@ type Server struct {
paths *gspath.SPath // 静态文件检索对象
config ServerConfig // 配置对象
servers []*gracefulServer // 底层http.Server列表
methodsMap map[string]bool // 所有支持的HTTP Method(初始化时自动填充)
methodsMap map[string]struct{} // 所有支持的HTTP Method(初始化时自动填充)
servedCount *gtype.Int // 已经服务的请求数(4-8字节不考虑溢出情况)同时作为请求ID
closeQueue *gqueue.Queue // 请求结束的关闭队列(存放的是需要异步关闭处理的*Request对象)
// 服务注册相关
@ -90,6 +90,7 @@ type Server struct {
errorLogger *glog.Logger // error log日志对象
// 其他属性
nameToUriType *gtype.Int // 服务注册时对象和方法名称转换为URI时的规则
gzipMimesMap map[string]struct{} // 支持gzip压缩的类型
}
// 路由对象
@ -181,7 +182,7 @@ func GetServer(name...interface{}) (*Server) {
name : sname,
paths : gspath.New(),
servers : make([]*gracefulServer, 0),
methodsMap : make(map[string]bool),
methodsMap : make(map[string]struct{}),
statusHandlerMap : make(map[string]HandlerFunc),
serveTree : make(map[string]interface{}),
hooksTree : make(map[string]interface{}),
@ -203,6 +204,7 @@ func GetServer(name...interface{}) (*Server) {
errorLogEnabled : gtype.NewBool(),
logHandler : gtype.NewInterface(),
nameToUriType : gtype.NewInt(),
gzipMimesMap : make(map[string]struct{}),
}
s.errorLogger.SetBacktraceSkip(4)
s.accessLogger.SetBacktraceSkip(4)
@ -210,7 +212,7 @@ func GetServer(name...interface{}) (*Server) {
s.serveCache.SetCap(gSERVE_CACHE_LRU_SIZE)
s.hooksCache.SetCap(gHOOKS_CACHE_LRU_SIZE)
for _, v := range strings.Split(gHTTP_METHODS, ",") {
s.methodsMap[v] = true
s.methodsMap[v] = struct{}{}
}
// 初始化时使用默认配置
s.SetConfig(defaultServerConfig)
@ -254,6 +256,12 @@ func (s *Server) Start() error {
})
}
}
// gzip压缩文件类型
if s.config.GzipContentTypes != nil {
for _, v := range s.config.GzipContentTypes {
s.gzipMimesMap[v] = struct{}{}
}
}
// 启动http server
reloaded := false

View File

@ -59,27 +59,31 @@ type ServerConfig struct {
AllowIps []string // 仅允许访问的ip列表支持ip前缀过滤如: 10 将仅允许10开头的ip访问
// 路由访问控制
DenyRoutes []string // 不允许访问的路由规则列表
// Gzip压缩文件类型
GzipContentTypes []string // 允许进行gzip压缩的文件类型
}
// 默认HTTP Server
var defaultServerConfig = ServerConfig {
Addr : "",
HTTPSAddr : "",
Handler : nil,
ReadTimeout : 60 * time.Second,
WriteTimeout : 60 * time.Second,
IdleTimeout : 60 * time.Second,
MaxHeaderBytes : 1024,
IndexFiles : []string{"index.html", "index.htm"},
IndexFolder : false,
ServerAgent : "gf",
ServerRoot : "",
Addr : "",
HTTPSAddr : "",
Handler : nil,
ReadTimeout : 60 * time.Second,
WriteTimeout : 60 * time.Second,
IdleTimeout : 60 * time.Second,
MaxHeaderBytes : 1024,
IndexFiles : []string{"index.html", "index.htm"},
IndexFolder : false,
ServerAgent : "gf",
ServerRoot : "",
CookieMaxAge : gDEFAULT_COOKIE_MAX_AGE,
SessionMaxAge : gDEFAULT_SESSION_MAX_AGE,
SessionIdName : gDEFAULT_SESSION_ID_NAME,
CookieMaxAge : gDEFAULT_COOKIE_MAX_AGE,
SessionMaxAge : gDEFAULT_SESSION_MAX_AGE,
SessionIdName : gDEFAULT_SESSION_ID_NAME,
ErrorLogEnabled : true,
ErrorLogEnabled : true,
GzipContentTypes : defaultGzipContentTypes,
}
// 获取默认的http server设置
@ -108,7 +112,10 @@ func (s *Server)SetConfig(c ServerConfig) {
if s.config.ServerAgent == "" {
s.SetServerAgent(defaultServerConfig.ServerAgent)
}
// 其他可动态设置的配置
// **********************
// 可动态设置的配置处理
// **********************
s.SetLogPath(c.LogPath)
s.SetLogHandler(c.LogHandler)
s.SetErrorLogEnabled(c.ErrorLogEnabled)
@ -124,7 +131,6 @@ func (s *Server)SetConfig(c ServerConfig) {
s.SetSessionIdName(c.SessionIdName)
}
s.SetNameToUriType(c.NameToUriType)
}
// 设置http server参数 - Addr
@ -283,6 +289,13 @@ func (s *Server) SetDenyRoutes(routes []string) {
s.config.DenyRoutes = routes
}
func (s *Server) SetGzipContentTypes(types []string) {
if s.Status() == SERVER_STATUS_RUNNING {
glog.Error("cannot be changed while running")
}
s.config.GzipContentTypes = types
}
// 设置http server参数 - CookieMaxAge
func (s *Server)SetCookieMaxAge(maxage int) {
s.cookieMaxAge.Set(maxage)

View File

@ -52,28 +52,54 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) {
s.closeQueue.PushBack(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(filePath + gfile.Separator + file)
if fpath != "" {
filePath = fpath
request.isFileRequest = true
break
}
}
}
} else {
request.isFileRequest = true
}
}
// 事件 - BeforeServe
s.callHookHandler(HOOK_BEFORE_SERVE, request)
// 路由注册检索
// 服务路由信息检索
handler := (*handlerItem)(nil)
if !request.exit.Val() {
parsedItem := s.getServeHandlerWithCache(request)
if parsedItem == nil {
// 如果路由不匹配,那么执行静态文件检索
path := s.paths.Search(r.URL.Path)
if path != "" {
s.serveFile(request, path)
} else {
request.Response.WriteStatus(http.StatusNotFound)
}
if request.IsFileRequest() {
// 如果存在请求的具体文件,那么优先服务文件
s.serveFile(request, filePath)
} else {
handler = parsedItem.handler
if request.Router == nil {
for k, v := range parsedItem.values {
request.routerVars[k] = v
// 其次进行服务路由注册检索
parsedItem := s.getServeHandlerWithCache(request)
if parsedItem == nil {
// 目录是优先级最低的操作
if filePath != "" {
s.serveFile(request, filePath)
} else {
request.Response.WriteStatus(http.StatusNotFound)
}
} else {
// 执行服务
handler = parsedItem.handler
if request.Router == nil {
for k, v := range parsedItem.values {
request.routerVars[k] = v
}
request.Router = parsedItem.handler.router
}
request.Router = parsedItem.handler.router
}
}
}
@ -131,20 +157,6 @@ func (s *Server)serveFile(r *Request, path string) {
}
info, _ := f.Stat()
if info.IsDir() {
// 处理index files
if len(s.config.IndexFiles) > 0 {
for _, file := range s.config.IndexFiles {
// 这里使用s.paths来检索而不是直接使用path避免多目录检索情况
// 例如:/tmp目录及源码目录都存在的情况按照优先级会返回/tmp但是index files只在源码目录中存在
fpath := s.paths.Search(r.URL.Path + gfile.Separator + file)
if fpath != "" {
f.Close()
s.serveFile(r, fpath)
return
}
}
}
// 处理访问目录
if s.config.IndexFolder {
s.listDir(r, f)
} else {

View File

@ -26,7 +26,7 @@ func (s *Server) handleAccessLog(r *Request) {
content := fmt.Sprintf(`"%s %s %s %s" %s %s`,
r.Method, r.Host, r.URL.String(), r.Proto,
gconv.String(r.Response.Status),
gconv.String(r.Response.Length),
gconv.String(r.Response.BufferLength()),
)
content += fmt.Sprintf(` %.3f`, float64(r.LeaveTime - r.EnterTime)/1000)
content += fmt.Sprintf(`, %s, "%s", "%s"`, r.GetClientIp(), r.Referer(), r.UserAgent())

View File

@ -25,7 +25,6 @@ type Config struct {
name *gtype.String // 默认配置文件名称
paths *gspath.SPath // 搜索目录路径
jsons *gmap.StringInterfaceMap // 配置文件对象
closed *gtype.Bool // 是否已经被close
vc *gtype.Bool // 层级检索是否执行分隔符冲突检测(默认为false检测会比较影响检索效率)
}
@ -41,7 +40,6 @@ func New(path string, file...string) *Config {
name : gtype.NewString(name),
paths : s,
jsons : gmap.NewStringInterfaceMap(),
closed : gtype.NewBool(),
vc : gtype.NewBool(),
}
}
@ -251,17 +249,14 @@ func (c *Config) Reload() {
c.jsons.Clear()
}
// 关闭Config对象自动关闭异步协程
func (c *Config) Close() {
c.closed.Set(true)
}
// 添加文件监控
func (c *Config) addMonitor(path string) {
if c.jsons.Get(path) == nil {
gfsnotify.Add(path, func(event *gfsnotify.Event) {
// 删除文件内容缓存,下一次查询会自动更新
c.jsons.Remove(event.Path)
})
// 防止多goroutine同时调用
if c.jsons.Get(path) != nil {
return
}
gfsnotify.Add(path, func(event *gfsnotify.Event) {
// 删除文件内容缓存,下一次查询会自动更新
c.jsons.Remove(event.Path)
})
}

61
g/os/gfcache/gfcache.go Normal file
View File

@ -0,0 +1,61 @@
// 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 gfcache
import (
"gitee.com/johng/gf/g/os/gcache"
"gitee.com/johng/gf/g/container/gtype"
"gitee.com/johng/gf/g/os/gfsnotify"
)
type Cache struct {
cap *gtype.Int // 缓存容量(byte)设置为0表示不限制
size *gtype.Int // 缓存大小(Byte)
cache *gcache.Cache // 缓存对象
notify *gfsnotify.Watcher // 文件监控管理对象
}
const (
// 默认的缓存容量(不限制)
gDEFAULT_CACHE_CAP = 0
)
var (
// 默认的文件缓存对象
cache = New()
)
func New(cap ... int) *Cache {
c := gDEFAULT_CACHE_CAP
if len(cap) > 0 {
c = cap[0]
}
notify, _ := gfsnotify.New()
return &Cache {
cap : gtype.NewInt(c),
size : gtype.NewInt(),
cache : gcache.New(),
notify : notify,
}
}
// 获得已缓存的文件大小(byte)
func GetSize() int {
return cache.GetSize()
}
// 获得文件内容 string
func GetContents(path string) string {
return cache.GetContents(path)
}
// 获得文件内容 []byte
func GetBinContents(path string) []byte {
return cache.GetBinContents(path)
}

View File

@ -0,0 +1,73 @@
// 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 gfcache
import (
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/os/gfsnotify"
)
// 设置容量大小(MB)
func (c *Cache) SetCap(cap int) {
c.cap.Set(cap)
}
// 获得缓存容量大小(byte)
func (c *Cache) GetCap() int {
return c.cap.Val()
}
// 获得已缓存的文件大小(byte)
func (c *Cache) GetSize() int {
return c.size.Val()
}
// 获得文件内容 string
func (c *Cache) GetContents(path string) string {
return string(c.GetBinContents(path))
}
// 获得文件内容 []byte
func (c *Cache) GetBinContents(path string) []byte {
v := c.cache.Get(path)
if v != nil {
return v.([]byte)
}
b := gfile.GetBinContents(path)
if b != nil && (c.cap.Val() == 0 || c.size.Val() < c.cap.Val()) {
c.addMonitor(path)
c.cache.Set(path, b, 0)
c.size.Add(len(b))
}
return b
}
// 添加文件监控
func (c *Cache) addMonitor(path string) {
// 防止多goroutine同时调用
if c.cache.Get(path) != nil {
return
}
c.notify.Add(path, func(event *gfsnotify.Event) {
r := c.cache.Get(path).([]byte)
// 是否删除
if event.IsRemove() {
c.cache.Remove(path)
c.size.Add(-len(r))
}
// 更新缓存内容
if c.cap.Val() == 0 || c.size.Val() < c.cap.Val() {
b := gfile.GetBinContents(path)
if b != nil {
dif := len(b) - len(r)
c.cache.Set(path, b, 0)
c.size.Add(dif)
}
}
})
}

View File

@ -28,16 +28,19 @@ import (
// 封装了常用的文件操作方法如需更详细的文件控制请查看官方os包
// 文件分隔符
const (
Separator = string(filepath.Separator)
)
// 源码的main包所在目录仅仅会设置一次
var mainPkgPath = gtype.NewString()
var (
// 源码的main包所在目录仅仅会设置一次
mainPkgPath = gtype.NewString()
// 编译时的 GOROOT 数值
var goRootOfBuild = gtype.NewString()
// 编译时的 GOROOT 数值
goRootOfBuild = gtype.NewString()
)
// 给定文件的绝对路径创建文件
func Mkdir(path string) error {

View File

@ -70,12 +70,14 @@ func (p *Pool) File() (*File, error) {
}
}
// 关闭指针池
func (p *Pool) Close() {
// 关闭指针池(返回error是标准库io.ReadWriteCloser接口实现)
func (p *Pool) Close() error {
p.pool.Close()
return nil
}
// 获得底层文件指针
func (f *File) Close() {
// 获得底层文件指针(返回error是标准库io.ReadWriteCloser接口实现)
func (f *File) Close() error {
f.pool.pool.Put(f)
return nil
}

View File

@ -12,12 +12,11 @@ import (
"bytes"
"errors"
"html/template"
"gitee.com/johng/gf/g/os/gfile"
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/encoding/ghash"
"gitee.com/johng/gf/g/util/gconv"
"gitee.com/johng/gf/g/os/gfsnotify"
"gitee.com/johng/gf/g/os/gspath"
"gitee.com/johng/gf/g/os/gfcache"
)
// 视图对象
@ -25,7 +24,6 @@ type View struct {
mu sync.RWMutex
paths *gspath.SPath // 模板查找目录(绝对路径)
funcmap map[string]interface{} // FuncMap
contents *gmap.StringStringMap // 已解析的模板文件内容
delimiters []string // 模板变量分隔符号
}
@ -62,7 +60,6 @@ func New(path string) *View {
view := &View {
paths : s,
funcmap : make(map[string]interface{}),
contents : gmap.NewStringStringMap(),
delimiters : make([]string, 2),
}
view.SetDelimiters("{{", "}}")
@ -82,18 +79,12 @@ func (view *View) AddPath(path string) error {
// 解析模板,返回解析后的内容
func (view *View) Parse(file string, params...map[string]interface{}) ([]byte, error) {
path := view.paths.Search(file)
content := view.contents.Get(path)
if content == "" {
content = gfile.GetContents(path)
if content != "" {
view.addMonitor(path)
view.contents.Set(path, content)
}
}
if content == "" {
path := view.paths.Search(file)
if path == "" {
return nil, errors.New("tpl \"" + file + "\" not found")
}
content := gfcache.GetContents(path)
// 模板参数
data := (map[string]interface{})(nil)
if len(params) > 0 {
@ -154,13 +145,3 @@ func (view *View) funcInclude(file string, data...map[string]interface{}) templa
}
return template.HTML(content)
}
// 添加模板文件监控
func (view *View) addMonitor(path string) {
if view.contents.Get(path) == "" {
gfsnotify.Add(path, func(event *gfsnotify.Event) {
// 删除文件内容缓存,下一次查询会自动更新
view.contents.Remove(event.Path)
})
}
}

View File

@ -7,7 +7,7 @@ import (
func main() {
s := ghttp.GetServer()
s.SetIndexFolder(true)
s.SetServerRoot("/home/john/Workspace/view")
s.SetServerRoot("/home/john/Workspace/Go/gf-home/static/plugin/editor.md/css")
s.SetPort(8199)
s.Run()
}

28
geg/os/gfcache/gfcache.go Normal file
View File

@ -0,0 +1,28 @@
package main
import (
"gitee.com/johng/gf/g/os/gfcache"
"gitee.com/johng/gf/g/os/gfile"
"fmt"
"time"
)
func main() {
s := 0
r := ""
path := gfile.TempDir() + gfile.Separator + "temp"
gfile.PutContents(path, "hello")
s = gfcache.GetSize()
r = gfcache.GetContents(path)
fmt.Println(s, r)
gfile.PutContentsAppend(path, " john")
// 等待1秒以便gfsnotify回调能处理完成
time.Sleep(time.Second)
s = gfcache.GetSize()
r = gfcache.GetContents(path)
fmt.Println(s, r)
}

View File

@ -1,11 +1,15 @@
package main
import (
"gitee.com/johng/gf/g/util/gregex"
"fmt"
)
func main() {
methodMap := make(map[string]bool)
methodMap["t"] = true
fmt.Println(methodMap["t"])
s := `<a href="baidu.com">百度</a>"`
gregex.ReplaceStringFunc(`href="(.+?)"`, s, func(s string) string {
fmt.Println(s)
return s
})
}

View File

@ -1,5 +1,5 @@
package gf
const VERSION = "0.99 beta"
const VERSION = "0.99.682 beta"
const AUTHORS = "john<john@johng.cn>"