gf/net/ghttp/ghttp_server_service_controller.go
2020-05-09 19:19:42 +08:00

233 lines
7.8 KiB
Go

// Copyright 2018 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
package ghttp
import (
"fmt"
"reflect"
"strings"
"github.com/gogf/gf/os/gfile"
"github.com/gogf/gf/text/gregex"
"github.com/gogf/gf/text/gstr"
)
// BindController registers controller to server routes with specified pattern. The controller
// needs to implement the gmvc.Controller interface. Each request of the controller bound in
// this way will initialize a new controller object for processing, corresponding to different
// request sessions.
//
// The optional parameter <method> is used to specify the method to be registered, which
// supports multiple method names, multiple methods are separated by char ',', case sensitive.
func (s *Server) BindController(pattern string, controller Controller, method ...string) {
bindMethod := ""
if len(method) > 0 {
bindMethod = method[0]
}
s.doBindController(pattern, controller, bindMethod, nil, "")
}
// BindControllerMethod registers specified method to server routes with specified pattern.
//
// The optional parameter <method> is used to specify the method to be registered, which
// does not supports multiple method names but only one, case sensitive.
func (s *Server) BindControllerMethod(pattern string, controller Controller, method string) {
s.doBindControllerMethod(pattern, controller, method, nil, "")
}
// BindControllerRest registers controller in REST API style to server with specified pattern.
// The controller needs to implement the gmvc.Controller interface. Each request of the controller
// bound in this way will initialize a new controller object for processing, corresponding to
// different request sessions.
// The method will recognize the HTTP method and do REST binding, for example:
// The method "Post" of controller will be bound to the HTTP POST method request processing,
// and the method "Delete" will be bound to the HTTP DELETE method request processing.
// Therefore, only the method corresponding to the HTTP Method will be bound, other methods will
// not automatically register the binding.
func (s *Server) BindControllerRest(pattern string, controller Controller) {
s.doBindControllerRest(pattern, controller, nil, "")
}
func (s *Server) doBindController(
pattern string, controller Controller, method string,
middleware []HandlerFunc, source string,
) {
// Convert input method to map for convenience and high performance searching.
var methodMap map[string]bool
if len(method) > 0 {
methodMap = make(map[string]bool)
for _, v := range strings.Split(method, ",") {
methodMap[strings.TrimSpace(v)] = true
}
}
domain, method, path, err := s.parsePattern(pattern)
if err != nil {
s.Logger().Fatal(err)
return
}
if strings.EqualFold(method, gDEFAULT_METHOD) {
pattern = s.serveHandlerKey("", path, domain)
}
// Retrieve a list of methods, create construct corresponding URI.
m := make(map[string]*handlerItem)
v := reflect.ValueOf(controller)
t := v.Type()
pkgPath := t.Elem().PkgPath()
pkgName := gfile.Basename(pkgPath)
structName := t.Elem().Name()
for i := 0; i < v.NumMethod(); i++ {
methodName := t.Method(i).Name
if methodMap != nil && !methodMap[methodName] {
continue
}
if methodName == "Init" || methodName == "Shut" || methodName == "Exit" {
continue
}
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if ctlName[0] == '*' {
ctlName = fmt.Sprintf(`(%s)`, ctlName)
}
if _, ok := v.Method(i).Interface().(func()); !ok {
if len(methodMap) > 0 {
// If registering with specified method, print error.
s.Logger().Errorf(
`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
pkgPath, ctlName, methodName, v.Method(i).Type().String(),
)
} else {
// Else, just print debug information.
s.Logger().Debugf(
`ignore route method: %s.%s.%s defined as "%s", no match "func()" for controller registry`,
pkgPath, ctlName, methodName, v.Method(i).Type().String(),
)
}
continue
}
key := s.mergeBuildInNameToPattern(pattern, structName, methodName, true)
m[key] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName),
itemType: gHANDLER_TYPE_CONTROLLER,
ctrlInfo: &handlerController{
name: methodName,
reflect: v.Elem().Type(),
},
middleware: middleware,
source: source,
}
// If there's "Index" method, then an additional route is automatically added
// to match the main URI, for example:
// If pattern is "/user", then "/user" and "/user/index" are both automatically
// registered.
//
// Note that if there's built-in variables in pattern, this route will not be added
// automatically.
if strings.EqualFold(methodName, "Index") && !gregex.IsMatchString(`\{\.\w+\}`, pattern) {
p := gstr.PosRI(key, "/index")
k := key[0:p] + key[p+6:]
if len(k) == 0 || k[0] == '@' {
k = "/" + k
}
m[k] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName),
itemType: gHANDLER_TYPE_CONTROLLER,
ctrlInfo: &handlerController{
name: methodName,
reflect: v.Elem().Type(),
},
middleware: middleware,
source: source,
}
}
}
s.bindHandlerByMap(m)
}
func (s *Server) doBindControllerMethod(
pattern string,
controller Controller,
method string,
middleware []HandlerFunc,
source string,
) {
m := make(map[string]*handlerItem)
v := reflect.ValueOf(controller)
t := v.Type()
structName := t.Elem().Name()
methodName := strings.TrimSpace(method)
methodValue := v.MethodByName(methodName)
if !methodValue.IsValid() {
s.Logger().Fatal("invalid method name: " + methodName)
return
}
pkgPath := t.Elem().PkgPath()
pkgName := gfile.Basename(pkgPath)
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if ctlName[0] == '*' {
ctlName = fmt.Sprintf(`(%s)`, ctlName)
}
if _, ok := methodValue.Interface().(func()); !ok {
s.Logger().Errorf(
`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
pkgPath, ctlName, methodName, methodValue.Type().String(),
)
return
}
key := s.mergeBuildInNameToPattern(pattern, structName, methodName, false)
m[key] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName),
itemType: gHANDLER_TYPE_CONTROLLER,
ctrlInfo: &handlerController{
name: methodName,
reflect: v.Elem().Type(),
},
middleware: middleware,
source: source,
}
s.bindHandlerByMap(m)
}
func (s *Server) doBindControllerRest(
pattern string, controller Controller,
middleware []HandlerFunc, source string,
) {
m := make(map[string]*handlerItem)
v := reflect.ValueOf(controller)
t := v.Type()
pkgPath := t.Elem().PkgPath()
structName := t.Elem().Name()
for i := 0; i < v.NumMethod(); i++ {
methodName := t.Method(i).Name
if _, ok := methodsMap[strings.ToUpper(methodName)]; !ok {
continue
}
pkgName := gfile.Basename(pkgPath)
ctlName := gstr.Replace(t.String(), fmt.Sprintf(`%s.`, pkgName), "")
if ctlName[0] == '*' {
ctlName = fmt.Sprintf(`(%s)`, ctlName)
}
if _, ok := v.Method(i).Interface().(func()); !ok {
s.Logger().Errorf(
`invalid route method: %s.%s.%s defined as "%s", but "func()" is required for controller registry`,
pkgPath, ctlName, methodName, v.Method(i).Type().String(),
)
return
}
key := s.mergeBuildInNameToPattern(methodName+":"+pattern, structName, methodName, false)
m[key] = &handlerItem{
itemName: fmt.Sprintf(`%s.%s.%s`, pkgPath, ctlName, methodName),
itemType: gHANDLER_TYPE_CONTROLLER,
ctrlInfo: &handlerController{
name: methodName,
reflect: v.Elem().Type(),
},
middleware: middleware,
source: source,
}
}
s.bindHandlerByMap(m)
}