文章来源:http://gf.johng.cn/494368git
当用户访问某个URI时,Web Server可以精确的调用特定的服务接口提供服务,这些都是经过“服务注册”来实现的。Web Server提供服务须要回调函数/方法/对象/控制器的支持,ghttp包支持多种服务注册模式,为开发者提供很是强大和灵活的接口功能。服务注册是整个Web Server最核心的部分,也是gf框架中最精心设计的一个模块。本章节将会进行详细介绍。github
服务注册管理由ghttp包提供,API文档地址:godoc.org/github.com/johng-cn/gf。设计模式
本章开始以前,咱们再来看一下本手册开头的Hello World程序:安全
package main import "gitee.com/johng/gf/g/net/ghttp" func main() { ghttp.GetServer().BindHandler("/", func(r *ghttp.Request) { r.Response.Write("哈喽世界!") }) }
其中,使用BindHandler
方法进行服务注册的方式叫作“回调函数注册”,是最简单的一种服务注册方式。经过给指定的Web Server上对应的URI注册一个可执行的方法,当客户端访问该URI时,Web Server便自动调用对应注册的回调函数来执行处理。在回调函数注册中,每一个注册函数都会有一个ghttp.Request
对象参数指针,表示每一个请求特定的独立的请求处理对象,回调函数能够经过该对象获取提交请求参数,也能够返回处理结果数据。并发
在详细讲解每一种注册方式以前,先看看每种注册方式各自的优缺点,以便在不一样的业务场景中选择更适合的注册方式。若是暂时不理解这个表格没有关系,能够在了解完每一种注册方式以后再回过头来看,也许会更清晰。mvc
注册方式 | 使用难度 | 安全系数 | 执行性能 | 内存消耗 |
---|---|---|---|---|
控制器注册 | 低 | 高 | 低 | 高 |
执行对象注册 | 中 | 中 | 中 | 中 |
回调函数注册 | 高 | 低 | 高 | 低 |
比较指标说明:app
func (s *Server) BindController(pattern string, c Controller) error func (s *Server) BindControllerMethod(pattern string, c Controller, methods string) error func (s *Server) BindControllerRest(pattern string, c Controller) error func (s *Server) BindObject(pattern string, obj interface{}) error func (s *Server) BindObjectMethod(pattern string, obj interface{}, methods string) error func (s *Server) BindObjectRest(pattern string, obj interface{}) error func (s *Server) BindHandler(pattern string, handler HandlerFunc) error
其中BindController*
方法用于控制器相关注册,BindObject*
方法用于对象相关注册,BindHandler
方法用于特定的回调函数注册。框架
服务注册使用的pattern参数格式以下:异步
[HttpMethod:]路由规则[@域名]
其中HttpMethod(支持的Method:GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE
)和域名为非必需参数,通常来讲直接给定路由规则(路由规则分为静态路由和动态路由,为便于演示本章节全部服务注册均采用静态路由规则,路由规则的详细介绍请查看【路由控制】章节)参数便可。由于须要使用HttpMethod注册的状况大多数为RESTful控制器,直接使用RESTful相关方法注册便可,域名支持也可使用Domain方法来进行绑定。函数
此外BindController*
系列方法第二个参数为控制器接口,给定的参数必须实现ghttp.Controller
接口。简便的作法是用户自定义的控制器直接继承gmvc.Controller
基类便可,gmvc.Controller
已经实现了对应的接口方法。
服务注册支持绑定域名,如下是对应的方法列表:
func (d *Domain) BindController(pattern string, c Controller) error func (d *Domain) BindControllerMethod(pattern string, c Controller, methods string) error func (d *Domain) BindControllerRest(pattern string, c Controller) error func (d *Domain) BindObject(pattern string, obj interface{}) error func (d *Domain) BindObjectMethod(pattern string, obj interface{}, methods string) error func (d *Domain) BindObjectRest(pattern string, obj interface{}) error func (d *Domain) BindHandler(pattern string, handler HandlerFunc) error
各项参数说明和Server的对应方法一致,只不过在Domain对象的底层会自动将方法绑定到Domain指定的域名列表中,只有对应的域名才能提供访问。
咱们来看一个简单的例子,咱们将前面的Hello World程序改为以下形式:
package main import "gitee.com/johng/gf/g/net/ghttp" func init() { ghttp.GetServer().Domain("localhost").BindHandler("/", func(r *ghttp.Request) { r.Response.Write("Hello World!") }) }
咱们再次使用 http://127.0.0.1/ 进行访问,发现Web Server返回404,为何呢?由于该程序中的回调函数只注册到了localhost域名中,其余域名天然没法访问。固然,前面也提到Domain方法的域名参数支持多个,自定义域名的服务注册至关方便。
全部的服务注册统一在包的init初始化方法中完成(init是Go语言内置的包初始化方法,而且一个包中支持多个init方法),一个包能够包含多个文件,每一个文件均可以有一个init初始化方法,能够分开注册,在使用的时候会经过同一个包引入进程序,自动调用初始化方法完成注册。能够参考示例文件。
来看一个例子:
gitee.com/johng/gf/blob/master/geg/frame/mvc/main.go
package main import ( "gitee.com/johng/gf/g/net/ghttp" _ "gitee.com/johng/gf/geg/frame/mvc/controller/demo" ) func main() { ghttp.GetServer().SetPort(8199) ghttp.GetServer().Run() }
其中经过:
import _ "gitee.com/johng/gf/geg/frame/mvc/controller/demo"
这样一条相似于all in one的语句便完成了对包中的全部控制器的引入和注册(固然,包中的init应当实现注册方法调用),在demo包中包含了多个控制器、执行对象、回调函数的注册,demo包具体的控制器注册以及相关逻辑咱们将在后续章节继续介绍。
这种方式将每个请求都当作一个控制器对象来处理,比较相似且媲美于PHP的请求执行模式,当一个请求进来以后,当即初始化一个新的控制器对象进行处理,处理完成以后释放控制器资源。这种服务注册方式的优势是简单、安全、OOP设计,每一个请求的控制器严格数据隔离,成员变量没法相互共享。
咱们能够经过ghttp.BindController
方法完成控制器的注册。
gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/user.go
package demo import ( "gitee.com/johng/gf/g/net/ghttp" "gitee.com/johng/gf/g/frame/gmvc" ) // 定义业务相关的控制器对象, // 建议命名规范中控制器统一使用Controller前缀,后期代码维护时便于区分 type ControllerUser struct { gmvc.Controller } // 初始化控制器对象,并绑定操做到Web Server func init() { // 绑定控制器到指定URI,全部控制器的公开方法将会映射到指定URI末尾 // 例如该方法执行后,查看效果可访问: // http://127.0.0.1:8199/user/name // http://127.0.0.1:8199/user/age ghttp.GetServer().BindController("/user", &ControllerUser{}) } // 定义操做逻辑 - 展现姓名 func (c *ControllerUser) Name() { c.Response.Write("John") } // 定义操做逻辑 - 展现年龄 func (c *ControllerUser) Age() { c.Response.Write("18") }
服务注册必须提供注册的URI,注册时ghttp会将全部控制器的公开方法将会映射到指定URI末尾,具体参见示例代码说明。注册的控制器参数是一个ghttp.Controller
接口,参数直接传递自定义的控制器对象指针便可(&ControllerUser{}
,实际上只要继承了gmvc.Controller
基类,控制器的指针对象便已经自动实现了ghttpController
接口)。ghttp经过解析该对象指针获取对应的控制器方法,生成反射类型,处理请求时再根据该反射类型自动生成对应的控制器对象,处理客户端请求,处理完后自动销毁该控制器对象。
假如控制器中有若干公开方法,可是我只想注册其中几个,其他的方法我不想对外公开,怎么办?
实际开发中不免会遇到这种场景,固然ghttp也是支持这种需求。咱们能够经过ghttp.BindControllerMethod
方法完成对控制器指定方法的注册。相对于ghttp.BindController
注册方法,ghttp.BindControllerMetho
仅仅多了一个方法名称参数methods,参数支持传入多个方法名称,多个名称以英文“,”号分隔(方法参数区分大小写)。
看下面这个例子,执行后ControllerMethod
的Name和Age方法将被注册到Web Server提供服务,而Info方法却不会对外公开。
gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/method.go
package demo import ( "gitee.com/johng/gf/g/net/ghttp" "gitee.com/johng/gf/g/frame/gmvc" ) type ControllerMethod struct { gmvc.Controller } func init() { ghttp.GetServer().BindControllerMethod("/method", &ControllerMethod{}, "Name, Age") } func (c *ControllerMethod) Name() { c.Response.Write("John") } func (c *ControllerMethod) Age() { c.Response.Write("18") } func (c *ControllerMethod) Info() { c.Response.Write("Info") }
启动外层的main.go,咱们尝试着访问http://127.0.0.1:8199/method/info
,由于没有对该方法执行注册,所以会发现返回404;而http://127.0.0.1:8199/method/name
及http://127.0.0.1:8199/method/age
却可以正常访问。
RESTful设计方式的控制器,一般用于API服务。在这种模式下,HTTP的Method将会映射到控制器对应的方法名称,例如:POST方式将会映射到控制器的Post方法中,DELETE方式将会映射到控制器的Delete方法中。其余非HTTP Method命名的方法,即便是定义的包公开方法,将没法完成自动注册,对于应用端不可见。固然,若是控制器并未定义对应HTTP Method的方法,该Method请求下将会返回 HTTP Status 404。此外,控制器方法名称须要保证是与HTTP Method相同的公开方法且方法名首字母大写。
这种方式注册的控制器,运行模式和“控制器注册”模式相同。咱们能够经过ghttp.BindControllerRest
方法完成RESTful控制器的注册。
如下是一个示例:
gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/rest.go
package demo import ( "gitee.com/johng/gf/g/net/ghttp" "gitee.com/johng/gf/g/frame/gmvc" ) // 测试控制器 type ControllerRest struct { gmvc.Controller } // 初始化控制器对象,并绑定操做到Web Server func init() { // 控制器公开方法中与HTTP Method方法同名的方法将会自动绑定映射 ghttp.GetServer().BindControllerRest("/user", &ControllerRest{}) } // RESTFul - GET func (c *ControllerRest) Get() { c.Response.Write("RESTFul HTTP Method GET") } // RESTFul - POST func (c *ControllerRest) Post() { c.Response.Write("RESTFul HTTP Method POST") } // RESTFul - DELETE func (c *ControllerRest) Delete() { c.Response.Write("RESTFul HTTP Method DELETE") } // 该方法没法映射,将会没法访问到 func (c *ControllerRest) Hello() { c.Response.Write("Hello") }
ghttp.Controller
接口中的Init
和Shut
是两个在HTTP请求流程中被Web Server自动调用的特殊方法(相似构造函数和析构函数的做用)。gmvc.Controller
基类中已经实现了这两个方法,用户自定义的控制器类直接继承gmvc.Controller
便可。若是须要自定义请求初始化以及请求结束时的一些业务逻辑操做,能够在自定义控制器中重载这两个方法。
ghttp.Controller接口
type Controller interface { Init(*Request) Shut(*Request) }
gmvc.Controller基类
type Controller struct { Request *ghttp.Request // 请求数据对象 Response *ghttp.Response // 返回数据对象(r.Response) Server *ghttp.Server // Web Server对象(r.Server) Cookie *ghttp.Cookie // COOKIE操做对象 Session *ghttp.Session // SESSION操做对象 View *View // 视图对象 }
gmvc.Controller
基类中的Init方法是对自身成员对象的初始化。控制器注册的设计模式相对来讲比较简单,也易于管理,可是因为每一次请求都是新建一个控制器对象来处理,而且使用了反射机制,会有必定的性能损耗。
执行对象注册相对来讲是一种比较高效的执行方式,可是运行机制、设计模式与控制器注册彻底不一样。与字面意思相同,执行对象注册是在注册时便给定一个实例化的对象,之后每个请求都交给该对象(同一对象)处理,该对象常驻内存不释放。因为相比较控制器注册来讲,执行对象注册方式在处理请求的时候不须要不停地建立/销毁控制器对象,所以请求处理效率会高不少。
这种注册方式的缺点也很明显,服务端进程在启动时便须要初始化这些执行对象,而且这些对象须要自行负责对自身数据的并发安全维护。执行对象的定义没有严格要求,也没有强行要求继承gmvc.Controller
控制器基类,由于在请求进入时没有自动初始化流程,内部的成员变量须要自行维护(包括变量初始化,变量销毁等)。
咱们能够经过ghttp.BindObject
方法完成执行对象的注册。
gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/object.go
package demo import "gitee.com/johng/gf/g/net/ghttp" type Object struct {} func init() { ghttp.GetServer().BindObject("/object", &Object{}) } func (o *Object) Show(r *ghttp.Request) { r.Response.Write("It's show time bibi!") }
能够看到,执行对象在进行服务注册时便生成了一个对象(执行对象在Web Server启动时便生成),此后无论多少请求进入,Web Server都是将请求转交给该对象对应的方法进行处理。须要注意的是,公开方法的定义与控制器注册不一样,必须为如下形式:
func(r *ghttp.Request)
不然没法完成注册,调用注册方法时会有错误提示,形如:
panic: interface conversion: interface {} is xxx, not func(*ghttp.Request)
该示例执行后能够经过,经过http://127.0.0.1:8199/object/show
查看效果。
对象方法注册原理相似于控制器方法注册,只公开执行对象中的特定方法。
来看一个例子:
package demo import ( "gitee.com/johng/gf/g/net/ghttp" ) type ObjectMethod struct {} func init() { obj := &ObjectMethod{} ghttp.GetServer().BindObjectMethod("/object-method", obj, "Show1, Show2, Show3") ghttp.GetServer().Domain("localhost").BindObjectMethod("/object-method", obj, "Show4") } func (o *ObjectMethod) Show1(r *ghttp.Request) { r.Response.Write("show 1") } func (o *ObjectMethod) Show2(r *ghttp.Request) { r.Response.Write("show 2") } func (o *ObjectMethod) Show3(r *ghttp.Request) { r.Response.Write("show 3") } func (o *ObjectMethod) Show4(r *ghttp.Request) { r.Response.Write("show 4") }
这个例子比较简单,而且也演示了域名绑定执行对象方法的操做。ObjectMethod对象的http://127.0.0.1:8199/object-method/show1
http://127.0.0.1:8199/object-method/show2
http://127.0.0.1:8199/object-method/show3
这3个接口只能经过127.0.0.1的IP访问,而Show4这个方法只能经过http://localhost:8199/object-method/show4
访问。
和REST控制器注册相似,只不过注册的是一个实例化的对象。咱们能够经过ghttp.BindObjectRest
方法完成REST对象的注册。
gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/object_rest.go
package demo import "gitee.com/johng/gf/g/net/ghttp" // 测试绑定对象 type ObjectRest struct {} func init() { ghttp.GetServer().BindObjectRest("/object-rest", &ObjectRest{}) } // RESTFul - GET func (o *ObjectRest) Get(r *ghttp.Request) { r.Response.Write("RESTFul HTTP Method GET") } // RESTFul - POST func (c *ObjectRest) Post(r *ghttp.Request) { r.Response.Write("RESTFul HTTP Method POST") } // RESTFul - DELETE func (c *ObjectRest) Delete(r *ghttp.Request) { r.Response.Write("RESTFul HTTP Method DELETE") } // 该方法没法映射,将会没法访问到 func (c *ObjectRest) Hello(r *ghttp.Request) { r.Response.Write("Hello") }
这种方式的运行机制相似于执行对象注册,不过注册的是一个函数/方法。相比较于执行对象注册,注册时不会有额外的对象实例化开销,注册时只是保存了一个函数/方法的指针地址。这种方式的服务注册比较灵活,注册的服务能够是一个实例化对象的方法地址,也能够是一个包方法地址。服务须要的数据能够经过包内部变量形式或者对象内部变量形式进行管理,开发者可根据实际状况进行灵活控制。
咱们能够经过ghttp.BindHandler
方法完成回调函数的注册。
gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/apple_pen.go
package demo import "gitee.com/johng/gf/g/net/ghttp" func init() { ghttp.GetServer().BindHandler("/apple", Apple) ghttp.GetServer().BindHandler("/pen", Pen) ghttp.GetServer().BindHandler("/apple-pen", ApplePen) } func Apple(r *ghttp.Request) { r.Response.Write("Apple") } func Pen(r *ghttp.Request) { r.Response.Write("Pen") } func ApplePen(r *ghttp.Request) { r.Response.Write("Apple-Pen") }
ghttp.Server提供了事件回调注册功能,支持用户对于某一事件进行自定义监听处理,按照URI pattern方式进行绑定注册。支持多个方法对同一事件进行监听,ghttp.Server将会按照注册顺序进行回调方法调用。
相关方法以下:
func (s *Server) BindHookHandler(pattern string, hook string, handler HandlerFunc) error func (s *Server) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error
固然域名对象也支持事件回调注册:
func (d *Domain) BindHookHandler(pattern string, hook string, handler HandlerFunc) error func (d *Domain) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error
支持的事件列表:
BeforeServe
AfterServe
BeforePatch
AfterrPatch
BeforeOutput
AfterOutput
BeforeClose
AfterClose
使用示例:
package main import ( "fmt" "gitee.com/johng/gf/g/net/ghttp" ) func main() { p := "/" s := ghttp.GetServer() s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{ "BeforeServe" : func(r *ghttp.Request){ fmt.Println("BeforeServe") }, "AfterServe" : func(r *ghttp.Request){ fmt.Println("AfterServe") }, "BeforePatch" : func(r *ghttp.Request){ fmt.Println("BeforePatch") }, "AfterPatch" : func(r *ghttp.Request){ fmt.Println("AfterPatch") }, "BeforeOutput" : func(r *ghttp.Request){ fmt.Println("BeforeOutput") }, "AfterOutput" : func(r *ghttp.Request){ fmt.Println("AfterOutput") }, "BeforeClose" : func(r *ghttp.Request){ fmt.Println("BeforeClose") }, "AfterClose" : func(r *ghttp.Request){ fmt.Println("AfterClose") }, }) s.BindHandler(p, func(r *ghttp.Request) { r.Response.Write("哈喽世界!") }) s.SetPort(8199) s.Run() }
当访问http://127.0.0.1:8199/
时,运行Web Server进程的终端将会按照事件的执行流程打印出对应的事件名称。