diff --git a/README.MD b/README.MD index 98a14edc5..c23daa901 100644 --- a/README.MD +++ b/README.MD @@ -13,7 +13,7 @@ import "gitee.com/johng/gf/g/xxx/xxx" ## 说明 . ├── g 【框架目录】 - │   ├── container 【常用数据结构容器】 + │   ├── container 【常用数据结构】 │   │   ├── gbtree B+树 │   │   ├── glist 并发安全的双向链表 │   │   ├── gmap 并发安全的哈希表 @@ -42,6 +42,7 @@ import "gitee.com/johng/gf/g/xxx/xxx" │   ├── net 【网络通信】 │   │   ├── ghttp HTTP客户端及服务端 │   │   ├── gipv4 IP操作 + │   │   ├── grouter 路由管理器 │   │   ├── gscanner 端口扫描 │   │   ├── gsession SESSION会话管理 │   │   ├── gsmtp SMTP邮件管理 diff --git a/g/frame/ginstance/ginstance.go b/g/frame/ginstance/ginstance.go index aa7ceb953..fc3286688 100644 --- a/g/frame/ginstance/ginstance.go +++ b/g/frame/ginstance/ginstance.go @@ -4,15 +4,19 @@ package ginstance import ( "strconv" + "gitee.com/johng/gf/g/os/glog" + "gitee.com/johng/gf/g/os/genv" + "gitee.com/johng/gf/g/os/gview" "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/gconsole" "gitee.com/johng/gf/g/database/gdb" "gitee.com/johng/gf/g/frame/gconfig" "gitee.com/johng/gf/g/container/gmap" - "gitee.com/johng/gf/g/os/glog" + "gitee.com/johng/gf/g/net/ghttp" ) const ( + FRAME_CORE_COMPONENT_NAME_VIEW = "gf.component.view" FRAME_CORE_COMPONENT_NAME_CONFIG = "gf.component.config" FRAME_CORE_COMPONENT_NAME_DATABASE = "gf.component.database" ) @@ -30,6 +34,36 @@ func Set(k string, v interface{}) { instances.Set(k, v) } +// 核心对象:Server +// 框架支持多服务器对象,通过传入不同的name进行区分 +func Server(names...string) *ghttp.Server { + name := "default" + if len(names) > 0 { + name = names[0] + } + return ghttp.GetServer(name) +} + +// 核心对象:View +func View() *gview.View { + result := Get(FRAME_CORE_COMPONENT_NAME_VIEW) + if result != nil { + return result.(*gview.View) + } else { + path := gconsole.Option.Get("viewpath") + if path == "" { + path = genv.Get("viewpath") + if path == "" { + path = gfile.SelfDir() + } + } + view := gview.GetView(path) + Set(FRAME_CORE_COMPONENT_NAME_VIEW, view) + return view + } + return nil +} + // 核心对象:Config // 配置文件目录查找依次为:启动参数cfgpath、当前程序运行目录 func Config() *gconfig.Config { @@ -39,7 +73,10 @@ func Config() *gconfig.Config { } else { path := gconsole.Option.Get("cfgpath") if path == "" { - path = gfile.SelfDir() + path = genv.Get("cfgpath") + if path == "" { + path = gfile.SelfDir() + } } config := gconfig.New(path) Set(FRAME_CORE_COMPONENT_NAME_CONFIG, config) @@ -58,7 +95,6 @@ func Database(names...string) gdb.Link { if config == nil { return nil } - if m := config.GetMap("database"); m != nil { for group, v := range m { if list, ok := v.([]interface{}); ok { diff --git a/g/frame/gmvc/controller.go b/g/frame/gmvc/controller.go index fcbc66371..6ac97b33b 100644 --- a/g/frame/gmvc/controller.go +++ b/g/frame/gmvc/controller.go @@ -40,7 +40,6 @@ func (c *Controller) Shut() { c.Cookie.Set(gDEFAULT_SESSION_ID_NAME, c.Session.Id()) } c.Cookie.Output() - c.Response.Output() } diff --git a/g/frame/gmvc/view.go b/g/frame/gmvc/view.go index 669e9d382..a97d99411 100644 --- a/g/frame/gmvc/view.go +++ b/g/frame/gmvc/view.go @@ -4,8 +4,6 @@ import ( "sync" "html/template" "gitee.com/johng/gf/g/os/gview" - "gitee.com/johng/gf/g/os/gfile" - "gitee.com/johng/gf/g/os/gconsole" "gitee.com/johng/gf/g/frame/ginstance" ) @@ -19,19 +17,9 @@ type View struct { // 创建一个MVC请求中使用的视图对象 func NewView(c *Controller) *View { - // 视图目录路径查找优先级:配置文件参数viewpath、启动参数viewpath、当前程序运行目录 - path := gconsole.Option.Get("viewpath") - if path == "" { - path = gfile.SelfDir() - } - if config := ginstance.Config(); config != nil { - if r := config.Get("viewpath"); r != nil { - path = r.(string) - } - } return &View{ ctl : c, - view : gview.GetView(path), + view : ginstance.View(), data : make(map[string]interface{}), } } @@ -52,29 +40,33 @@ func (view *View) Assign(key string, value interface{}) { view.data[key] = value } +// 解析模板,并返回解析后的内容 +func (view *View) Parse(file string) ([]byte, error) { + // 查询模板 + tpl, err := view.view.Template(file) + if err != nil { + return nil, err + } + // 绑定函数 + tpl.BindFunc("include", view.funcInclude) + // 执行解析 + view.mu.RLock() + content, err := tpl.Parse(view.data) + view.mu.RUnlock() + return content, err +} + // 解析指定模板 func (view *View) Display(files...string) error { file := "default" if len(files) > 0 { file = files[0] } - // 查询模板 - tpl, err := view.view.Template(file) - if err != nil { - view.ctl.Response.WriteString("Tpl Parsing Error: " + err.Error()) - return err - } - // 绑定函数 - tpl.BindFunc("include", view.funcInclude) - // 执行解析 - view.mu.RLock() - defer view.mu.RUnlock() - content, err := tpl.Parse(view.data) - if err != nil { + if content, err := view.Parse(file); err != nil { view.ctl.Response.WriteString("Tpl Parsing Error: " + err.Error()) return err } else { - view.ctl.Response.WriteString(content) + view.ctl.Response.Write(content) } return nil } diff --git a/g/net/ghttp/http_server.go b/g/net/ghttp/http_server.go index af260c0a2..687f002ee 100644 --- a/g/net/ghttp/http_server.go +++ b/g/net/ghttp/http_server.go @@ -13,6 +13,7 @@ import ( "path/filepath" "gitee.com/johng/gf/g/util/gutil" "gitee.com/johng/gf/g/container/gmap" + "gitee.com/johng/gf/g/net/grouter" ) const ( @@ -30,6 +31,7 @@ type Server struct { status int8 // 当前服务器状态(0:未启动,1:运行中) handlerMap HandlerMap // 所有注册的回调函数 methodsMap map[string]bool // 所有支持的HTTP Method + Router *grouter.Router // 路由管理对象 } // 域名、URI与回调函数的绑定记录表 diff --git a/g/net/ghttp/http_server_handler.go b/g/net/ghttp/http_server_handler.go index f3329b0e5..9b9d47ad8 100644 --- a/g/net/ghttp/http_server_handler.go +++ b/g/net/ghttp/http_server_handler.go @@ -23,6 +23,13 @@ func (s *Server)defaultHttpHandle(w http.ResponseWriter, r *http.Request) { // 其次,如果没有对应的自定义处理接口配置,那么走默认的域名处理接口配置; // 最后,如果以上都没有找到处理接口,那么进行文件处理; func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) { + // 路由解析 + uri := r.URL.String() + result, err := s.Router.Dispatch(uri) + if err == nil && strings.Compare(uri, result) != 0 { + r.URL, _ = r.URL.Parse(result) + } + // 构造请求/返回参数对象 request := &ClientRequest{} response := &ServerResponse{} request.Request = *r @@ -47,8 +54,14 @@ func (s *Server)callHandler(h *HandlerItem, r *ClientRequest, w *ServerResponse) c.MethodByName("Shut").Call(nil) } else { h.faddr(s, r, w) - w.Output() } + // 路由规则打包 + if buffer, err := s.Router.Patch(w.Buffer()); err == nil { + w.ClearBuffer() + w.Write(buffer) + } + // 输出缓冲区 + w.OutputBuffer() } // 处理静态文件请求 @@ -59,7 +72,7 @@ func (s *Server)serveFile(w http.ResponseWriter, r *http.Request) { path := strings.TrimRight(s.config.ServerRoot, string(filepath.Separator)) path = path + uri path = gfile.RealPath(path) - if (path != "") { + if path != "" { s.doServeFile(w, r, path) } else { s.NotFound(w, r) diff --git a/g/net/ghttp/http_server_response.go b/g/net/ghttp/http_server_response.go index 11bdffb4f..651bf13ab 100644 --- a/g/net/ghttp/http_server_response.go +++ b/g/net/ghttp/http_server_response.go @@ -59,8 +59,15 @@ func (r *ServerResponse) Buffer() []byte { return r.buffer } +// 清空缓冲区内容 +func (r *ServerResponse) ClearBuffer() { + r.bufmu.Lock() + defer r.bufmu.Unlock() + r.buffer = make([]byte, 0) +} + // 输出缓冲区数据到客户端 -func (r *ServerResponse) Output() { +func (r *ServerResponse) OutputBuffer() { r.bufmu.Lock() defer r.bufmu.Unlock() if len(r.buffer) > 0 { diff --git a/g/net/grouter/grouter.go b/g/net/grouter/grouter.go new file mode 100644 index 000000000..eb3300c70 --- /dev/null +++ b/g/net/grouter/grouter.go @@ -0,0 +1,122 @@ +package grouter + +import ( + "sync" + "sort" + "bytes" + "errors" + "strings" + "gitee.com/johng/gf/g/util/gregx" + "gitee.com/johng/gf/g/container/gmap" +) + +// 路由管理对象 +type Router struct { + dmu sync.RWMutex // 解析规则互斥锁 + pmu sync.RWMutex // 打包规则互斥锁 + dkeys []string // 解析规则排序键名 + pkeys []string // 打包规则排序键名 + drules *gmap.StringStringMap // 解析规则 + prules *gmap.StringStringMap // 打包规则 +} + + +func New() *Router { + return &Router{ + drules : gmap.NewStringStringMap(), + prules : gmap.NewStringStringMap(), + } +} + +// 设置解析规则,例如:静态分页 +// `\/([\w\.\-]+)\/([\w\.\-]+)\/page\/([\d\.\-]+)[\/\?]*`, "/user/list/page/2" +func (r *Router) SetRule(rule, replace string) { + r.drules.Set(rule, replace) + r.updateDispatchKeys() +} + +// 批量设置解析规则 +func (r *Router) SetRules(rules map[string]string) { + r.drules.BatchSet(rules) + r.updateDispatchKeys() +} + +// 删除解析规则 +func (r *Router) RemoveRule(rule string) { + r.drules.Remove(rule) + r.updateDispatchKeys() +} + +// 设置打包规则 +func (r *Router) SetPatchRule(rule, replace string) { + r.prules.Set(rule, replace) + r.updatePatchKeys() +} + +// 批量设置打包规则 +func (r *Router) SetPatchRules(rules map[string]string) { + r.prules.BatchSet(rules) + r.updatePatchKeys() +} + +// 删除打包规则 +func (r *Router) RemovePatchRule(rule string) { + r.prules.Remove(rule) + r.updatePatchKeys() +} + +func (r *Router) updateDispatchKeys() { + r.dmu.Lock() + defer r.dmu.Unlock() + r.dkeys = r.drules.Keys() + sort.Slice(r.dkeys, func(i, j int) bool { return len(r.dkeys[i]) > len(r.dkeys[j]) }) +} + +func (r *Router) updatePatchKeys() { + r.pmu.Lock() + defer r.pmu.Unlock() + r.pkeys = r.prules.Keys() + sort.Slice(r.pkeys, func(i, j int) bool { return len(r.pkeys[i]) > len(r.pkeys[j]) }) +} + +// 解析URI +func (r *Router) Dispatch(uri string) (string, error) { + r.dmu.RLock() + defer r.dmu.RUnlock() + if len(r.dkeys) == 0 { + return uri, errors.New("no dispatch rules found") + } + for _, rule := range r.dkeys { + if replace := r.drules.Get(rule); replace != "" { + result, err := gregx.ReplaceString(rule, uri, replace) + if err != nil { + return result, err + } + if len(uri) != len(result) || strings.Compare(result, uri) != 0 { + return result, nil + } + } + } + return uri, nil +} + +// 打包内容 +func (r *Router) Patch(content []byte) ([]byte, error) { + r.pmu.RLock() + defer r.pmu.RUnlock() + if len(r.pkeys) == 0 { + return content, errors.New("no patch rules found") + } + for _, rule := range r.pkeys { + if replace := r.prules.Get(rule); replace != "" { + result, err := gregx.Replace(rule, content, []byte(replace)) + if err != nil { + return result, err + } + if len(content) != len(result) || bytes.Compare(result, content) != 0 { + return result, nil + } + } + } + return content, nil +} \ No newline at end of file diff --git a/g/os/gview/gview.go b/g/os/gview/gview.go index 221efa883..55b047b3a 100644 --- a/g/os/gview/gview.go +++ b/g/os/gview/gview.go @@ -110,21 +110,21 @@ func (t *Template) Assign(k string, v interface{}) { // 返回解析后的模板内容,可以额外指定模板变量,如果没有可以传入nil // 函数内部的底层template必须每次调用都新生成一个,防止错误:html/template: cannot Parse after Execute -func (t *Template) Parse(data map[string]interface{}) (string, error) { +func (t *Template) Parse(data map[string]interface{}) ([]byte, error) { t.mu.RLock() defer t.mu.RUnlock() buffer := bytes.NewBuffer(nil) if tpl, err := template.New(t.path).Funcs(t.funcmap).Parse(t.content); err != nil { - return "", err + return nil, err } else { m := t.data for k, v := range data { m[k] = v } if err := tpl.Execute(buffer, m); err != nil { - return "", err + return nil, err } } - return buffer.String(), nil + return buffer.Bytes(), nil } diff --git a/g/util/gregx/regx.go b/g/util/gregx/regx.go index 1189931d6..f5eff1266 100644 --- a/g/util/gregx/regx.go +++ b/g/util/gregx/regx.go @@ -5,10 +5,29 @@ import ( ) // 正则表达式是否匹配 -func IsMatch(val, pattern string) bool { - match, err := regexp.Match(pattern, []byte(val)) +func IsMatch(pattern string, src []byte) bool { + match, err := regexp.Match(pattern, src) if err != nil { return false } return match +} + +func IsMatchString(pattern string, src string) bool { + return IsMatch(pattern, []byte(src)) +} + +// 正则替换(全部替换) +func Replace(pattern string, src, replace []byte) ([]byte, error) { + reg, err := regexp.Compile(pattern) + if err != nil { + return src, err + } + return reg.ReplaceAll(src, replace), nil +} + +// 正则替换(全部替换),字符串 +func ReplaceString(pattern, src, replace string) (string, error) { + r, e := Replace(pattern, []byte(src), []byte(replace)) + return string(r), e } \ No newline at end of file diff --git a/geg/frame/mvc/controller/user/user.go b/geg/frame/mvc/controller/user/user.go index 147552a82..9fdf0c930 100644 --- a/geg/frame/mvc/controller/user/user.go +++ b/geg/frame/mvc/controller/user/user.go @@ -3,6 +3,7 @@ package user import ( "gitee.com/johng/gf/g/net/ghttp" "gitee.com/johng/gf/g/frame/gmvc" + "gitee.com/johng/gf/g/frame/ginstance" ) // 定义业务相关的控制器对象 @@ -10,6 +11,7 @@ type ControllerUser struct { gmvc.Controller } +// 测试绑定对象 type T struct { } @@ -28,8 +30,8 @@ func (t *T) Get(s *ghttp.Server, r *ghttp.ClientRequest, w *ghttp.ServerResponse func init() { //ghttp.GetServer("johng").Domain("localhost").BindHandler("/user", u.Info) //ghttp.GetServer("johng").BindHandler("/test", Test) - ghttp.GetServer("johng").BindObjectRest("/test", &T{}) - ghttp.GetServer("johng").BindController("/user", &ControllerUser{}) + ginstance.Server().BindObjectRest("/test", &T{}) + ginstance.Server().BindController("/user", &ControllerUser{}) } // 定义操作逻辑 diff --git a/geg/frame/mvc/main.go b/geg/frame/mvc/main.go index 12f937e1e..a2696e4db 100644 --- a/geg/frame/mvc/main.go +++ b/geg/frame/mvc/main.go @@ -1,11 +1,11 @@ package main import ( - "gitee.com/johng/gf/g/net/ghttp" + "gitee.com/johng/gf/g/frame/ginstance" _ "gitee.com/johng/gf/geg/frame/mvc/controller/user" ) func main() { - ghttp.GetServer("johng").SetPort(8199) - ghttp.GetServer("johng").Run() + ginstance.Server().SetPort(8199) + ginstance.Server().Run() } diff --git a/geg/other/test.go b/geg/other/test.go index cee21fbac..fd44789a5 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,11 +1,12 @@ package main import ( + "gitee.com/johng/gf/g/util/gregx" "fmt" - "math" ) func main() { - fmt.Printf("given id not match current maxid [%d]", int(math.MaxInt64)) + s, _ := gregx.Replace(`\w`, []byte("/user/list/page/2"), []byte("-")) + fmt.Println(string(s)) } \ No newline at end of file