From 9b61727a0005b18bb6126d4d60506cbb848583aa Mon Sep 17 00:00:00 2001 From: John Date: Fri, 13 Apr 2018 15:19:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90ghttp?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E8=B7=AF=E7=94=B1=E7=89=B9=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TODO | 3 + g/net/ghttp/http_client_request.go | 2 +- g/net/ghttp/http_client_response.go | 1 + g/net/ghttp/http_server.go | 317 +--------------------------- g/net/ghttp/http_server_auto.go | 1 + g/net/ghttp/http_server_config.go | 2 +- g/net/ghttp/http_server_domain.go | 2 +- g/net/ghttp/http_server_handler.go | 83 +------- g/net/ghttp/http_server_options.go | 1 + g/net/ghttp/http_server_router.go | 241 +++++++++++++++++++++ g/net/ghttp/http_server_service.go | 237 +++++++++++++++++++++ geg/net/ghttp/hello.go | 14 +- 12 files changed, 501 insertions(+), 403 deletions(-) create mode 100644 TODO create mode 100644 g/net/ghttp/http_server_router.go create mode 100644 g/net/ghttp/http_server_service.go diff --git a/TODO b/TODO new file mode 100644 index 000000000..04f8729a7 --- /dev/null +++ b/TODO @@ -0,0 +1,3 @@ +1. gconv完善针对不同类型的判断,例如:尽量减少sprintf("%v", xxx)来执行string类型的转换; +2. gdb Where方法参数的改进,研究是否可以将string参数类型修改为interfaceP{; +3. 增加对于数据表Model的封装; \ No newline at end of file diff --git a/g/net/ghttp/http_client_request.go b/g/net/ghttp/http_client_request.go index 694cb0a4d..47fda85af 100644 --- a/g/net/ghttp/http_client_request.go +++ b/g/net/ghttp/http_client_request.go @@ -3,7 +3,7 @@ // 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. - +// HTTP客户端请求. package ghttp diff --git a/g/net/ghttp/http_client_response.go b/g/net/ghttp/http_client_response.go index 88fe87b7e..2b625ec9b 100644 --- a/g/net/ghttp/http_client_response.go +++ b/g/net/ghttp/http_client_response.go @@ -3,6 +3,7 @@ // 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. +// HTTP客户端请求返回. package ghttp diff --git a/g/net/ghttp/http_server.go b/g/net/ghttp/http_server.go index b5a261e62..bb6f59ff1 100644 --- a/g/net/ghttp/http_server.go +++ b/g/net/ghttp/http_server.go @@ -13,12 +13,9 @@ import ( "reflect" "net/http" "gitee.com/johng/gf/g/os/gcache" - "gitee.com/johng/gf/g/util/gregx" - "gitee.com/johng/gf/g/util/gutil" "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/container/gqueue" - "container/list" ) const ( @@ -134,316 +131,4 @@ func (s *Server) Run() error { } s.status = 1 return nil -} - -// 生成回调方法查询的Key -func (s *Server) handlerKey(domain, method, pattern string) string { - return strings.ToUpper(method) + ":" + pattern + "@" + strings.ToLower(domain) -} - -// 注册服务处理方法 -func (s *Server) setHandler(pattern string, item *HandlerItem) error { - domain, method, uri, err := s.parsePatternForBindHandler(pattern) - if err != nil { - return errors.New("invalid pattern") - } - item.uri = uri - item.domain = domain - item.method = method - // 静态注册 - s.hmmu.Lock() - if method == gDEFAULT_METHOD { - for v, _ := range s.methodsMap { - s.handlerMap[s.handlerKey(domain, v, pattern)] = item - } - } else { - s.handlerMap[s.handlerKey(domain, method, pattern)] = item - } - s.hmmu.Unlock() - // 动态注册,首先需要判断是否是动态注册,如果不是那么就没必要添加到动态注册记录变量中 - if s.isUriHasRule(uri) { - if _, ok := s.handlerTree[domain]; !ok { - s.handlerTree[domain] = make(map[string]interface{}) - } - p := s.handlerTree[domain] - array := strings.Split(uri[1:], "/") - item.priority = len(array) - for _, v := range array { - if len(v) == 0 { - continue - } - switch v[0] { - case ':': - fallthrough - case '*': - v = "/" - fallthrough - default: - if _, ok := p.(map[string]interface{})[v]; !ok { - p.(map[string]interface{})[v] = make(map[string]interface{}) - } else { - p = p.(map[string]interface{})[v] - } - } - } - // 到达叶子节点 - var l *list.List - if v, ok := p.(map[string]interface{})["*list"]; !ok { - l = list.New() - p.(map[string]interface{})["*list"] = l - } else { - l = v.(*list.List) - } - l.PushBack(item) - } - return nil -} - -// 判断URI中是否包含动态注册规则 -func (s *Server) isUriHasRule(uri string) bool { - if len(uri) > 1 && (strings.Index(uri, "/:") != -1 || strings.Index(uri, "/*") != -1) { - return true - } - return false -} - -// 解析pattern -func (s *Server)parsePatternForBindHandler(pattern string) (domain, method, uri string, err error) { - uri = pattern - domain = gDEFAULT_DOMAIN - method = gDEFAULT_METHOD - if array, err := gregx.MatchString(`([a-zA-Z]+):(.+)`, pattern); len(array) > 1 && err == nil { - method = array[1] - pattern = array[2] - } - if array, err := gregx.MatchString(`(.+)@([\w\.\-]+)`, pattern); len(array) > 1 && err == nil { - uri = array[1] - domain = array[2] - } - if uri == "" { - err = errors.New("invalid pattern") - } - return -} - -// 绑定URI到操作函数/方法 -// pattern的格式形如:/user/list, put:/user, delete:/user, post:/user@johng.cn -// 支持RESTful的请求格式,具体业务逻辑由绑定的处理方法来执行 -func (s *Server)bindHandlerItem(pattern string, item *HandlerItem) error { - if s.status == 1 { - return errors.New("server handlers cannot be changed while running") - } - return s.setHandler(pattern, item) -} - -// 通过映射数组绑定URI到操作函数/方法 -func (s *Server)bindHandlerByMap(m HandlerMap) error { - for p, h := range m { - if err := s.bindHandlerItem(p, h); err != nil { - return err - } - } - return nil -} - -// 将方法名称按照设定的规则转换为URI并附加到指定的URI后面 -func (s *Server)appendMethodNameToUriWithPattern(pattern string, name string) string { - // 检测域名后缀 - array := strings.Split(pattern, "@") - // 分离URI(其实可能包含HTTP Method) - uri := array[0] - uri = strings.TrimRight(uri, "/") + "/" - // 方法名中间存在大写字母,转换为小写URI地址以“-”号链接每个单词 - for i := 0; i < len(name); i++ { - if i > 0 && gutil.IsLetterUpper(name[i]) { - uri += "-" - } - uri += strings.ToLower(string(name[i])) - } - // 加上指定域名后缀 - if len(array) > 1 { - uri += "@" + array[1] - } - return uri -} - -// 注意该方法是直接绑定函数的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑 -func (s *Server)BindHandler(pattern string, handler HandlerFunc) error { - return s.bindHandlerItem(pattern, &HandlerItem{ - ctype : nil, - fname : "", - faddr : handler, - }) -} - -// 绑定对象到URI请求处理中,会自动识别方法名称,并附加到对应的URI地址后面 -// 需要注意对象方法的定义必须按照ghttp.HandlerFunc来定义 -func (s *Server)BindObject(pattern string, obj interface{}) error { - m := make(HandlerMap) - v := reflect.ValueOf(obj) - t := v.Type() - for i := 0; i < v.NumMethod(); i++ { - name := t.Method(i).Name - key := s.appendMethodNameToUriWithPattern(pattern, name) - m[key] = &HandlerItem{ - ctype : nil, - fname : "", - faddr : v.Method(i).Interface().(func(*Request)), - } - } - return s.bindHandlerByMap(m) -} - -// 绑定对象到URI请求处理中,会自动识别方法名称,并附加到对应的URI地址后面 -// 第三个参数methods支持多个方法注册,多个方法以英文“,”号分隔,区分大小写 -func (s *Server)BindObjectMethod(pattern string, obj interface{}, methods string) error { - m := make(HandlerMap) - for _, v := range strings.Split(methods, ",") { - method := strings.TrimSpace(v) - fval := reflect.ValueOf(obj).MethodByName(method) - if !fval.IsValid() { - return errors.New("invalid method name:" + method) - } - key := s.appendMethodNameToUriWithPattern(pattern, method) - m[key] = &HandlerItem{ - ctype : nil, - fname : "", - faddr : fval.Interface().(func(*Request)), - } - } - return s.bindHandlerByMap(m) -} - -// 绑定对象到URI请求处理中,会自动识别方法名称,并附加到对应的URI地址后面 -// 需要注意对象方法的定义必须按照ghttp.HandlerFunc来定义 -func (s *Server)BindObjectRest(pattern string, obj interface{}) error { - m := make(HandlerMap) - v := reflect.ValueOf(obj) - t := v.Type() - for i := 0; i < v.NumMethod(); i++ { - name := t.Method(i).Name - if _, ok := s.methodsMap[strings.ToUpper(name)]; !ok { - continue - } - key := name + ":" + pattern - m[key] = &HandlerItem{ - ctype : nil, - fname : "", - faddr : v.Method(i).Interface().(func(*Request)), - } - } - return s.bindHandlerByMap(m) -} - -// 绑定控制器,控制器需要实现gmvc.Controller接口 -// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话 -func (s *Server)BindController(pattern string, c Controller) error { - // 遍历控制器,获取方法列表,并构造成uri - m := make(HandlerMap) - v := reflect.ValueOf(c) - t := v.Type() - for i := 0; i < v.NumMethod(); i++ { - name := t.Method(i).Name - if name == "Init" || name == "Shut" { - continue - } - key := s.appendMethodNameToUriWithPattern(pattern, name) - m[key] = &HandlerItem{ - ctype : v.Elem().Type(), - fname : name, - faddr : nil, - } - } - return s.bindHandlerByMap(m) -} - -// 绑定控制器(RESTFul),控制器需要实现gmvc.Controller接口 -// 方法会识别HTTP方法,并做REST绑定处理,例如:Post方法会绑定到HTTP POST的方法请求处理,Delete方法会绑定到HTTP DELETE的方法请求处理 -// 因此只会绑定HTTP Method对应的方法,其他方法不会自动注册绑定 -// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话 -func (s *Server)BindControllerRest(pattern string, c Controller) error { - // 遍历控制器,获取方法列表,并构造成uri - m := make(HandlerMap) - v := reflect.ValueOf(c) - t := v.Type() - methods := make(map[string]bool) - for _, v := range strings.Split(gHTTP_METHODS, ",") { - methods[v] = true - } - // 如果存在与HttpMethod对应名字的方法,那么绑定这些方法 - for i := 0; i < v.NumMethod(); i++ { - name := strings.ToUpper(t.Method(i).Name) - if name == "Init" || name == "Shut" { - continue - } - if _, ok := s.methodsMap[name]; !ok { - continue - } - key := name + ":" + pattern - m[key] = &HandlerItem{ - ctype : v.Elem().Type(), - fname : name, - faddr : nil, - } - } - return s.bindHandlerByMap(m) -} - -// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话 -// 第三个参数methods支持多个方法注册,多个方法以英文“,”号分隔,不区分大小写 -func (s *Server)BindControllerMethod(pattern string, c Controller, methods string) error { - m := make(HandlerMap) - cval := reflect.ValueOf(c) - for _, v := range strings.Split(methods, ",") { - ctype := reflect.ValueOf(c).Elem().Type() - method := strings.TrimSpace(v) - if !cval.MethodByName(method).IsValid() { - return errors.New("invalid method name:" + method) - } - key := s.appendMethodNameToUriWithPattern(pattern, method) - m[key] = &HandlerItem{ - ctype : ctype, - fname : method, - faddr : nil, - } - } - return s.bindHandlerByMap(m) -} - -// 绑定指定的hook回调函数, pattern参数同BindHandler,支持命名路由;hook参数的值由ghttp server设定,参数不区分大小写 -func (s *Server)BindHookHandler(pattern string, hook string, handler HandlerFunc) error { - //domain, method, uri, err := s.parsePatternForBindHookHandler(pattern) - //if err != nil { - // return errors.New("invalid pattern") - //} - //var l *glist.List - //if method == gDEFAULT_METHOD { - // for v, _ := range s.methodsMap { - // if v := s.hooksMap.GetWithDefault(s.handlerHookKey(domain, v, uri, hook), glist.New()); v != nil { - // l = v.(*glist.List) - // } - // l.PushBack(handler) - // } - //} else { - // if v := s.hooksMap.GetWithDefault(s.handlerHookKey(domain, method, uri, hook), glist.New()); v == nil { - // l = v.(*glist.List) - // } - // l.PushBack(handler) - //} - return nil -} - -// 通过map批量绑定回调函数 -func (s *Server)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error { - for k, v := range hookmap { - if err := s.BindHookHandler(pattern, k, v); err != nil { - return err - } - } - return nil -} - -// 构造用于hooksMap检索的键名 -func (s *Server)handlerHookKey(domain, method, uri, hook string) string { - return strings.ToUpper(hook) + "^" + s.handlerKey(domain, method, uri) -} +} \ No newline at end of file diff --git a/g/net/ghttp/http_server_auto.go b/g/net/ghttp/http_server_auto.go index 13149b552..f8e9b0fad 100644 --- a/g/net/ghttp/http_server_auto.go +++ b/g/net/ghttp/http_server_auto.go @@ -3,6 +3,7 @@ // 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 diff --git a/g/net/ghttp/http_server_config.go b/g/net/ghttp/http_server_config.go index c78f6a8e0..efcf1b705 100644 --- a/g/net/ghttp/http_server_config.go +++ b/g/net/ghttp/http_server_config.go @@ -3,7 +3,7 @@ // 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 diff --git a/g/net/ghttp/http_server_domain.go b/g/net/ghttp/http_server_domain.go index 3cce586eb..7c9175c08 100644 --- a/g/net/ghttp/http_server_domain.go +++ b/g/net/ghttp/http_server_domain.go @@ -3,7 +3,7 @@ // 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 diff --git a/g/net/ghttp/http_server_handler.go b/g/net/ghttp/http_server_handler.go index bda92f877..8c9b745a6 100644 --- a/g/net/ghttp/http_server_handler.go +++ b/g/net/ghttp/http_server_handler.go @@ -3,7 +3,7 @@ // 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 @@ -13,7 +13,6 @@ import ( "sort" "reflect" "strings" - "strconv" "net/url" "net/http" "path/filepath" @@ -33,7 +32,6 @@ func (s *Server)defaultHttpHandle(w http.ResponseWriter, r *http.Request) { // 其次,如果没有对应的自定义处理接口配置,那么走默认的域名处理接口配置; // 最后,如果以上都没有找到处理接口,那么进行文件处理; func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) { - fmt.Println(s.handlerTree) request := newRequest(s, r, w) if h := s.getHandler(request); h != nil { s.callHandler(h, request) @@ -49,51 +47,6 @@ func (s *Server) getHandler(r *Request) *HandlerItem { return handler } -// 服务方法检索 -func (s *Server) searchHandler(r *Request) *HandlerItem { - s.hmmu.RLock() - defer s.hmmu.RUnlock() - domains := []string{gDEFAULT_DOMAIN, strings.Split(r.Host, ":")[0]} - // 首先进行静态匹配 - for _, domain := range domains { - if f, ok := s.handlerMap[s.handlerKey(domain, r.Method, r.URL.Path)]; ok { - return f - } - } - // 其次进行正则匹配(会比较耗效率) - for k, v := range s.handlerMap { - if array, err := gregx.MatchString(`([a-zA-Z]+):(.+)@([\w\.\-]+)`, k); len(array) > 2 && err == nil { - // method匹配 - if !strings.EqualFold(r.Method, array[1]) { - continue - } - // domain匹配 - for _, domain := range domains { - if !strings.EqualFold(domain, array[3]) { - continue - } - // method & domain匹配时,那么执行pattern的正则匹配 - regrule, querystr := s.patternToRegRule(array[2]) - if gregx.IsMatchString(regrule, r.URL.Path) { - // 如果需要query匹配,那么需要重新解析URL - if len(querystr) > 0 { - if query, err := gregx.ReplaceString(regrule, querystr, r.URL.Path); err == nil && len(query) > 0 { - if vals, err := url.ParseQuery(query); err == nil { - for k, v := range vals { - r.values[k] = v - } - } - } - } - return v - } - } - } - } - return nil -} - - // 按照指定hook回调函数的注册顺序进行调用 func (s *Server)callHookHandler(r *Request, hook string) { l := s.searchHookHandler(r, hook) @@ -164,40 +117,6 @@ func (s *Server)searchHookHandler(r *Request, hook string) []HandlerFunc { return funcs } -// 将pattern(不带method和domain)解析成正则表达式匹配以及对应的query字符串 -func (s *Server) patternToRegRule(rule string) (regrule string, querystr string) { - if len(rule) < 2 { - return rule, "" - } - regrule = "^/" - array := strings.Split(rule[1:], "/") - index := 1 - for _, v := range array { - if len(v) == 0 { - continue - } - switch v[0] { - case ':': - regrule += `/([\w\.\-]+)` - if len(querystr) > 0 { - querystr += "&" - } - querystr += v[1:] + "=$" + strconv.Itoa(index) - index++ - case '*': - regrule += `/(.*)` - if len(querystr) > 0 { - querystr += "&" - } - querystr += v[1:] + "=$" + strconv.Itoa(index) - return - default: - regrule += v - } - } - return -} - // 初始化控制器 func (s *Server)callHandler(h *HandlerItem, r *Request) { // 会话处理 diff --git a/g/net/ghttp/http_server_options.go b/g/net/ghttp/http_server_options.go index a26ed90a6..7af1dc3f0 100644 --- a/g/net/ghttp/http_server_options.go +++ b/g/net/ghttp/http_server_options.go @@ -3,6 +3,7 @@ // 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 diff --git a/g/net/ghttp/http_server_router.go b/g/net/ghttp/http_server_router.go new file mode 100644 index 000000000..81183a727 --- /dev/null +++ b/g/net/ghttp/http_server_router.go @@ -0,0 +1,241 @@ +// 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 ghttp + +import ( + "errors" + "strings" + "container/list" + "gitee.com/johng/gf/g/util/gregx" +) + +// 解析pattern +func (s *Server)parsePatternForBindHandler(pattern string) (domain, method, uri string, err error) { + uri = pattern + domain = gDEFAULT_DOMAIN + method = gDEFAULT_METHOD + if array, err := gregx.MatchString(`([a-zA-Z]+):(.+)`, pattern); len(array) > 1 && err == nil { + method = array[1] + pattern = array[2] + } + if array, err := gregx.MatchString(`(.+)@([\w\.\-]+)`, pattern); len(array) > 1 && err == nil { + uri = array[1] + domain = array[2] + } + if uri == "" { + err = errors.New("invalid pattern") + } + return +} + +// 注册服务处理方法 +func (s *Server) setHandler(pattern string, item *HandlerItem) error { + domain, method, uri, err := s.parsePatternForBindHandler(pattern) + if err != nil { + return errors.New("invalid pattern") + } + item.uri = uri + item.domain = domain + item.method = method + // 静态注册 + s.hmmu.Lock() + defer s.hmmu.Unlock() + if method == gDEFAULT_METHOD { + for v, _ := range s.methodsMap { + s.handlerMap[s.handlerKey(domain, v, pattern)] = item + } + } else { + s.handlerMap[s.handlerKey(domain, method, pattern)] = item + } + + // 动态注册,首先需要判断是否是动态注册,如果不是那么就没必要添加到动态注册记录变量中 + // 非叶节点为哈希表检索节点,按照URI注册的层级进行高效检索,直至到叶子链表节点; + // 叶子节点是链表,按照优先级进行排序,优先级高的排前面,按照遍历检索,按照哈希表层级检索后的叶子链表一般数据量不大,所以效率比较高; + if s.isUriHasRule(uri) { + s.htmu.Lock() + defer s.htmu.Unlock() + if _, ok := s.handlerTree[domain]; !ok { + s.handlerTree[domain] = make(map[string]interface{}) + } + p := s.handlerTree[domain] + array := strings.Split(uri[1:], "/") + item.priority = len(array) + for _, v := range array { + if len(v) == 0 { + continue + } + switch v[0] { + case ':': + fallthrough + case '*': + v = "/" + fallthrough + default: + if _, ok := p.(map[string]interface{})[v]; !ok { + p.(map[string]interface{})[v] = make(map[string]interface{}) + } + p = p.(map[string]interface{})[v] + + } + } + // 到达叶子节点 + var l *list.List + if v, ok := p.(map[string]interface{})["*list"]; !ok { + l = list.New() + p.(map[string]interface{})["*list"] = l + } else { + l = v.(*list.List) + } + //b,_ := gjson.New(s.handlerTree).ToJsonIndent() + //fmt.Println(string(b)) + // 从头开始遍历链表,优先级高的放在前面 + for e := l.Front(); e != nil; e = e.Next() { + if s.compareHandlerItemPriority(item, e.Value.(*HandlerItem)) { + l.InsertBefore(item, e) + return nil + } + } + l.PushBack(item) + } + return nil +} + +// 对比两个HandlerItem的优先级,需要非常注意的是,注意新老对比项的参数先后顺序 +func (s *Server) compareHandlerItemPriority(newItem, oldItem *HandlerItem) bool { + if newItem.priority > oldItem.priority { + return true + } + if newItem.priority < oldItem.priority { + return false + } + if strings.Count(newItem.uri, "/:") > strings.Count(oldItem.uri, "/:") { + return true + } + return false +} + +// 服务方法检索 +func (s *Server) searchHandler(r *Request) *HandlerItem { + s.hmmu.RLock() + domains := []string{gDEFAULT_DOMAIN, strings.Split(r.Host, ":")[0]} + // 首先进行静态匹配 + for _, domain := range domains { + if f, ok := s.handlerMap[s.handlerKey(domain, r.Method, r.URL.Path)]; ok { + s.hmmu.RUnlock() + return f + } + } + s.hmmu.RUnlock() + // 其次进行动态匹配 + array := strings.Split(r.URL.Path[1:], "/") + s.htmu.RLock() + for _, domain := range domains { + p, ok := s.handlerTree[domain] + if !ok { + continue + } + // 多层链表的目的是当叶子节点未有任何规则匹配时,让父级模糊匹配规则继续处理 + lists := make([]*list.List, 0) + for k, v := range array { + if _, ok := p.(map[string]interface{})["*list"]; ok { + lists = append(lists, p.(map[string]interface{})["*list"].(*list.List)) + } + if _, ok := p.(map[string]interface{})[v]; !ok { + if _, ok := p.(map[string]interface{})["/"]; ok { + p = p.(map[string]interface{})["/"] + if k == len(array) - 1 { + if _, ok := p.(map[string]interface{})["*list"]; ok { + lists = append(lists, p.(map[string]interface{})["*list"].(*list.List)) + } + } + } + } else { + p = p.(map[string]interface{})[v] + if k == len(array) - 1 { + if _, ok := p.(map[string]interface{})["*list"]; ok { + lists = append(lists, p.(map[string]interface{})["*list"].(*list.List)) + } + } + } + } + + // 多层链表遍历检索,从数组末尾的链表开始遍历,末尾的深度高优先级也高 + for i := len(lists) - 1; i >= 0; i-- { + for e := lists[i].Front(); e != nil; e = e.Next() { + item := e.Value.(*HandlerItem) + if strings.EqualFold(item.method, gDEFAULT_METHOD) || strings.EqualFold(item.method, r.Method) { + regrule, names := s.patternToRegRule(item.uri) + if gregx.IsMatchString(regrule, r.URL.Path) { + // 如果需要query匹配,那么需要重新解析URL + if len(names) > 0 { + if match, err := gregx.MatchString(regrule, r.URL.Path); err == nil { + array := strings.Split(names, ",") + if len(match) > len(array) { + for index, name := range array { + r.values[name] = []string{match[index + 1]} + } + } + } + } + s.htmu.RUnlock() + return item + } + } + } + } + } + s.htmu.RUnlock() + return nil +} + +// 将pattern(不带method和domain)解析成正则表达式匹配以及对应的query字符串 +func (s *Server) patternToRegRule(rule string) (regrule string, names string) { + if len(rule) < 2 { + return rule, "" + } + regrule = "^" + array := strings.Split(rule[1:], "/") + for _, v := range array { + if len(v) == 0 { + continue + } + switch v[0] { + case ':': + regrule += `/([\w\.\-]+)` + if len(names) > 0 { + names += "," + } + names += v[1:] + case '*': + regrule += `/(.*)` + if len(names) > 0 { + names += "," + } + names += v[1:] + return + default: + regrule += "/" + v + } + } + regrule += `$` + return +} + +// 判断URI中是否包含动态注册规则 +func (s *Server) isUriHasRule(uri string) bool { + if len(uri) > 1 && (strings.Index(uri, "/:") != -1 || strings.Index(uri, "/*") != -1) { + return true + } + return false +} + +// 生成回调方法查询的Key +func (s *Server) handlerKey(domain, method, pattern string) string { + return strings.ToUpper(method) + ":" + pattern + "@" + strings.ToLower(domain) +} + diff --git a/g/net/ghttp/http_server_service.go b/g/net/ghttp/http_server_service.go new file mode 100644 index 000000000..1c0052eff --- /dev/null +++ b/g/net/ghttp/http_server_service.go @@ -0,0 +1,237 @@ +// 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. +// 服务注册 + hook管理. + +package ghttp + +import ( + "errors" + "strings" + "reflect" + "gitee.com/johng/gf/g/util/gutil" +) + +// 绑定URI到操作函数/方法 +// pattern的格式形如:/user/list, put:/user, delete:/user, post:/user@johng.cn +// 支持RESTful的请求格式,具体业务逻辑由绑定的处理方法来执行 +func (s *Server)bindHandlerItem(pattern string, item *HandlerItem) error { + if s.status == 1 { + return errors.New("server handlers cannot be changed while running") + } + return s.setHandler(pattern, item) +} + +// 通过映射数组绑定URI到操作函数/方法 +func (s *Server)bindHandlerByMap(m HandlerMap) error { + for p, h := range m { + if err := s.bindHandlerItem(p, h); err != nil { + return err + } + } + return nil +} + +// 将方法名称按照设定的规则转换为URI并附加到指定的URI后面 +func (s *Server)appendMethodNameToUriWithPattern(pattern string, name string) string { + // 检测域名后缀 + array := strings.Split(pattern, "@") + // 分离URI(其实可能包含HTTP Method) + uri := array[0] + uri = strings.TrimRight(uri, "/") + "/" + // 方法名中间存在大写字母,转换为小写URI地址以“-”号链接每个单词 + for i := 0; i < len(name); i++ { + if i > 0 && gutil.IsLetterUpper(name[i]) { + uri += "-" + } + uri += strings.ToLower(string(name[i])) + } + // 加上指定域名后缀 + if len(array) > 1 { + uri += "@" + array[1] + } + return uri +} + +// 注意该方法是直接绑定函数的内存地址,执行的时候直接执行该方法,不会存在初始化新的控制器逻辑 +func (s *Server)BindHandler(pattern string, handler HandlerFunc) error { + return s.bindHandlerItem(pattern, &HandlerItem{ + ctype : nil, + fname : "", + faddr : handler, + }) +} + +// 绑定对象到URI请求处理中,会自动识别方法名称,并附加到对应的URI地址后面 +// 需要注意对象方法的定义必须按照ghttp.HandlerFunc来定义 +func (s *Server)BindObject(pattern string, obj interface{}) error { + m := make(HandlerMap) + v := reflect.ValueOf(obj) + t := v.Type() + for i := 0; i < v.NumMethod(); i++ { + name := t.Method(i).Name + key := s.appendMethodNameToUriWithPattern(pattern, name) + m[key] = &HandlerItem{ + ctype : nil, + fname : "", + faddr : v.Method(i).Interface().(func(*Request)), + } + } + return s.bindHandlerByMap(m) +} + +// 绑定对象到URI请求处理中,会自动识别方法名称,并附加到对应的URI地址后面 +// 第三个参数methods支持多个方法注册,多个方法以英文“,”号分隔,区分大小写 +func (s *Server)BindObjectMethod(pattern string, obj interface{}, methods string) error { + m := make(HandlerMap) + for _, v := range strings.Split(methods, ",") { + method := strings.TrimSpace(v) + fval := reflect.ValueOf(obj).MethodByName(method) + if !fval.IsValid() { + return errors.New("invalid method name:" + method) + } + key := s.appendMethodNameToUriWithPattern(pattern, method) + m[key] = &HandlerItem{ + ctype : nil, + fname : "", + faddr : fval.Interface().(func(*Request)), + } + } + return s.bindHandlerByMap(m) +} + +// 绑定对象到URI请求处理中,会自动识别方法名称,并附加到对应的URI地址后面 +// 需要注意对象方法的定义必须按照ghttp.HandlerFunc来定义 +func (s *Server)BindObjectRest(pattern string, obj interface{}) error { + m := make(HandlerMap) + v := reflect.ValueOf(obj) + t := v.Type() + for i := 0; i < v.NumMethod(); i++ { + name := t.Method(i).Name + if _, ok := s.methodsMap[strings.ToUpper(name)]; !ok { + continue + } + key := name + ":" + pattern + m[key] = &HandlerItem{ + ctype : nil, + fname : "", + faddr : v.Method(i).Interface().(func(*Request)), + } + } + return s.bindHandlerByMap(m) +} + +// 绑定控制器,控制器需要实现gmvc.Controller接口 +// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话 +func (s *Server)BindController(pattern string, c Controller) error { + // 遍历控制器,获取方法列表,并构造成uri + m := make(HandlerMap) + v := reflect.ValueOf(c) + t := v.Type() + for i := 0; i < v.NumMethod(); i++ { + name := t.Method(i).Name + if name == "Init" || name == "Shut" { + continue + } + key := s.appendMethodNameToUriWithPattern(pattern, name) + m[key] = &HandlerItem{ + ctype : v.Elem().Type(), + fname : name, + faddr : nil, + } + } + return s.bindHandlerByMap(m) +} + +// 绑定控制器(RESTFul),控制器需要实现gmvc.Controller接口 +// 方法会识别HTTP方法,并做REST绑定处理,例如:Post方法会绑定到HTTP POST的方法请求处理,Delete方法会绑定到HTTP DELETE的方法请求处理 +// 因此只会绑定HTTP Method对应的方法,其他方法不会自动注册绑定 +// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话 +func (s *Server)BindControllerRest(pattern string, c Controller) error { + // 遍历控制器,获取方法列表,并构造成uri + m := make(HandlerMap) + v := reflect.ValueOf(c) + t := v.Type() + methods := make(map[string]bool) + for _, v := range strings.Split(gHTTP_METHODS, ",") { + methods[v] = true + } + // 如果存在与HttpMethod对应名字的方法,那么绑定这些方法 + for i := 0; i < v.NumMethod(); i++ { + name := strings.ToUpper(t.Method(i).Name) + if name == "Init" || name == "Shut" { + continue + } + if _, ok := s.methodsMap[name]; !ok { + continue + } + key := name + ":" + pattern + m[key] = &HandlerItem{ + ctype : v.Elem().Type(), + fname : name, + faddr : nil, + } + } + return s.bindHandlerByMap(m) +} + +// 这种方式绑定的控制器每一次请求都会初始化一个新的控制器对象进行处理,对应不同的请求会话 +// 第三个参数methods支持多个方法注册,多个方法以英文“,”号分隔,不区分大小写 +func (s *Server)BindControllerMethod(pattern string, c Controller, methods string) error { + m := make(HandlerMap) + cval := reflect.ValueOf(c) + for _, v := range strings.Split(methods, ",") { + ctype := reflect.ValueOf(c).Elem().Type() + method := strings.TrimSpace(v) + if !cval.MethodByName(method).IsValid() { + return errors.New("invalid method name:" + method) + } + key := s.appendMethodNameToUriWithPattern(pattern, method) + m[key] = &HandlerItem{ + ctype : ctype, + fname : method, + faddr : nil, + } + } + return s.bindHandlerByMap(m) +} + +// 绑定指定的hook回调函数, pattern参数同BindHandler,支持命名路由;hook参数的值由ghttp server设定,参数不区分大小写 +func (s *Server)BindHookHandler(pattern string, hook string, handler HandlerFunc) error { + //domain, method, uri, err := s.parsePatternForBindHookHandler(pattern) + //if err != nil { + // return errors.New("invalid pattern") + //} + //var l *glist.List + //if method == gDEFAULT_METHOD { + // for v, _ := range s.methodsMap { + // if v := s.hooksMap.GetWithDefault(s.handlerHookKey(domain, v, uri, hook), glist.New()); v != nil { + // l = v.(*glist.List) + // } + // l.PushBack(handler) + // } + //} else { + // if v := s.hooksMap.GetWithDefault(s.handlerHookKey(domain, method, uri, hook), glist.New()); v == nil { + // l = v.(*glist.List) + // } + // l.PushBack(handler) + //} + return nil +} + +// 通过map批量绑定回调函数 +func (s *Server)BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error { + for k, v := range hookmap { + if err := s.BindHookHandler(pattern, k, v); err != nil { + return err + } + } + return nil +} + +// 构造用于hooksMap检索的键名 +func (s *Server)handlerHookKey(domain, method, uri, hook string) string { + return strings.ToUpper(hook) + "^" + s.handlerKey(domain, method, uri) +} diff --git a/geg/net/ghttp/hello.go b/geg/net/ghttp/hello.go index 56f417a20..8802776a4 100644 --- a/geg/net/ghttp/hello.go +++ b/geg/net/ghttp/hello.go @@ -3,8 +3,18 @@ package main import "gitee.com/johng/gf/g/net/ghttp" func main() { - ghttp.GetServer().BindHandler("/:name", func(r *ghttp.Request) { - r.Response.WriteString("Hello World!") + ghttp.GetServer().BindHandler("/:name/*any", func(r *ghttp.Request) { + r.Response.WriteString("any") + r.Response.WriteString(r.GetQueryString("name")) + r.Response.WriteString(r.GetQueryString("any")) + }) + //ghttp.GetServer().BindHandler("/:name/action", func(r *ghttp.Request) { + // r.Response.WriteString(r.GetQueryString("name")) + //}) + ghttp.GetServer().BindHandler("/:name/:action/:aaa", func(r *ghttp.Request) { + r.Response.WriteString("name") + r.Response.WriteString(r.GetQueryString("name")) + r.Response.WriteString(r.GetQueryString("action")) }) ghttp.GetServer().SetPort(10000) ghttp.GetServer().Run()