- Created by 郭强, last modified on Nov 01, 2023
对象注册是通过一个实例化的对象来执行路由注册,以后每一个请求都交给该对象(同一对象)处理,该对象常驻内存不释放。
相关方法:
func (s *Server) BindObject(pattern string, object interface{}, methods ...string) error func (s *Server) BindObjectMethod(pattern string, object interface{}, method string) error func (s *Server) BindObjectRest(pattern string, object interface{}) error
前置约定:需要进行路由注册的方法必须为公开方法,并且方法定义如下:
func(r *ghttp.Request)
否则无法完成注册,路由注册时将会有错误提示,例如:
panic: interface conversion: interface {} is xxx, not func(*ghttp.Request)
对象注册-BindObject
我们可以通过BindObject
方法完成对象的注册。
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) type Controller struct{} func (c *Controller) Index(r *ghttp.Request) { r.Response.Write("index") } func (c *Controller) Show(r *ghttp.Request) { r.Response.Write("show") } func main() { s := g.Server() c := new(Controller) s.BindObject("/object", c) s.SetPort(8199) s.Run() }
可以看到,对象在进行路由注册时便生成了一个对象(对象在Server
启动时便生成),此后不管多少请求进入,Server
都是将请求转交给该对象对应的方法进行处理。该示例执行后,终端输出的路由表如下:
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE |---------|---------|---------|--------|---------------|--------------------------|------------| default | default | :8199 | ALL | /object | main.(*Controller).Index | |---------|---------|---------|--------|---------------|--------------------------|------------| default | default | :8199 | ALL | /object/index | main.(*Controller).Index | |---------|---------|---------|--------|---------------|--------------------------|------------| default | default | :8199 | ALL | /object/show | main.(*Controller).Show | |---------|---------|---------|--------|---------------|--------------------------|------------|
随后可以通过 http://127.0.0.1:8199/object/show 查看效果。
控制器中的Index
方法是一个特殊的方法,例如,当注册的路由规则为/user
时,HTTP请求到/user
时,将会自动映射到控制器的Index
方法。也就是说,访问地址/user
和/user/index
将会达到相同的执行效果。
路由内置变量
当使用BindObject
方法进行对象注册时,在路由规则中可以使用两个内置的变量:{.struct}
和{.method}
,前者表示当前对象名称,后者表示当前注册的方法名。我们来看一个例子:
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) type Order struct{} func (o *Order) List(r *ghttp.Request) { r.Response.Write("list") } func main() { s := g.Server() o := new(Order) s.BindObject("/{.struct}-{.method}", o) s.SetPort(8199) s.Run() }
执行后,终端输出的路由表如下:
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE |---------|---------|---------|--------|-------------|--------------------|------------| default | default | :8199 | ALL | /order-list | main.(*Order).List | |---------|---------|---------|--------|-------------|--------------------|------------|
我们尝试着访问 http://127.0.0.1:8199/order-list ,可以看到页面输出list
。如果路由规则中不使用内置变量,那么默认的情况下,方法将会被追加到指定的路由规则末尾。
命名风格规则
通过对象进行路由注册时,可以根据对象及方法名称自动生成路由规则,默认的路由规则为:当方法名称带有多个单词
(按照字符大写区分单词)时,路由控制器默认会自动使用英文连接符号-
进行拼接,因此访问的时候方法名称需要带-
号。
例如,方法名为UserName
时,生成的路由为user-name
;方法名为ShowListItems
时,生成的路由为show-list-items
;以此类推。
此外,我们可以通过.Server.SetNameToUriType
方法来设置对象方法名称的路由生成方式。支持的方式目前有4
种,对应4
个常量定义:
UriTypeDefault = 0 // (默认)全部转为小写,单词以'-'连接符号连接 UriTypeFullName = 1 // 不处理名称,以原有名称构建成URI UriTypeAllLower = 2 // 仅转为小写,单词间不使用连接符号 UriTypeCamel = 3 // 采用驼峰命名方式
注意:需要在通过对象进行路由注册前进行该参数的设置,在路由注册后设置将不会生效,那么将使用默认规则。
我们来看一个示例:
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) type User struct{} func (u *User) ShowList(r *ghttp.Request) { r.Response.Write("list") } func main() { u := new(User) s1 := g.Server("UriTypeDefault") s2 := g.Server("UriTypeFullName") s3 := g.Server("UriTypeAllLower") s4 := g.Server("UriTypeCamel") s1.SetNameToUriType(ghttp.UriTypeDefault) s2.SetNameToUriType(ghttp.UriTypeFullName) s3.SetNameToUriType(ghttp.UriTypeAllLower) s4.SetNameToUriType(ghttp.UriTypeCamel) s1.BindObject("/{.struct}/{.method}", u) s2.BindObject("/{.struct}/{.method}", u) s3.BindObject("/{.struct}/{.method}", u) s4.BindObject("/{.struct}/{.method}", u) s1.SetPort(8100) s2.SetPort(8200) s3.SetPort(8300) s4.SetPort(8400) s1.Start() s2.Start() s3.Start() s4.Start() g.Wait() }
为了对比演示效果,这个示例采用了多Server
运行方式,将不同的名称转换方式使用了不同的Server
来配置运行,因此我们可以方便地在同一个程序中,访问不同的Server
(通过不同的端口绑定)看到不同的结果。
执行后,终端输出的路由表如下:
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE -----------------|---------|---------|--------|-----------------|-----------------------|------------- UriTypeDefault | default | :8100 | ALL | /user/show-list | main.(*User).ShowList | -----------------|---------|---------|--------|-----------------|-----------------------|------------- SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE ------------------|---------|---------|--------|----------------|-----------------------|------------- UriTypeFullName | default | :8200 | ALL | /User/ShowList | main.(*User).ShowList | ------------------|---------|---------|--------|----------------|-----------------------|------------- SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE ------------------|---------|---------|--------|----------------|-----------------------|------------- UriTypeAllLower | default | :8300 | ALL | /user/showlist | main.(*User).ShowList | ------------------|---------|---------|--------|----------------|-----------------------|------------- SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE ---------------|---------|---------|--------|----------------|-----------------------|------------- UriTypeCamel | default | :8400 | ALL | /user/showList | main.(*User).ShowList | ---------------|---------|---------|--------|----------------|-----------------------|-------------
可以分别访问以下URL
地址得到期望的结果:
http://127.0.0.1:8100/user/show-list http://127.0.0.1:8200/User/ShowList http://127.0.0.1:8300/user/showlist http://127.0.0.1:8400/user/showList
对象方法注册
假如控制器中有若干公开方法,但是我只想注册其中几个,其余的方法我不想对外公开,怎么办?我们可以通过BindObject
传递第三个非必需参数替换实现,参数支持传入多个方法名称,多个名称以英文,
号分隔(方法名称参数区分大小写)。
使用示例:
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) type Controller struct{} func (c *Controller) Index(r *ghttp.Request) { r.Response.Write("index") } func (c *Controller) Show(r *ghttp.Request) { r.Response.Write("show") } func main() { s := g.Server() c := new(Controller) s.BindObject("/object", c, "Show") s.SetPort(8199) s.Run() }
执行后,终端输出路由表为:
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE |---------|---------|---------|--------|--------------|-------------------------|------------| default | default | :8199 | ALL | /object/show | main.(*Controller).Show | |---------|---------|---------|--------|--------------|-------------------------|------------|
绑定路由方法-BindObjectMethod
我们可以通过BindObjectMethod
方法绑定指定的路由到指定的方法执行(方法名称参数区分大小写)。
BindObjectMethod
和BindObject
的区别:BindObjectMethod
将对象中的指定方法与指定路由规则进行绑定,第三个method
参数只能指定一个方法名称;BindObject
注册时,所有的路由都是对象方法名称按照规则生成的,第三个methods
参数可以指定多个注册的方法名称。
来看一个例子:
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) type Controller struct{} func (c *Controller) Index(r *ghttp.Request) { r.Response.Write("index") } func (c *Controller) Show(r *ghttp.Request) { r.Response.Write("show") } func main() { s := g.Server() c := new(Controller) s.BindObjectMethod("/show", c, "Show") s.SetPort(8199) s.Run() }
执行后,终端输出的路由表如下:
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE |---------|---------|---------|--------|-------|-------------------------|------------| default | default | :8199 | ALL | /show | main.(*Controller).Show | |---------|---------|---------|--------|-------|-------------------------|------------|
RESTful
对象注册-BindObjectRest
RESTful
设计方式的控制器,通常用于API
服务。在这种模式下,HTTP
的Method
将会映射到控制器对应的方法名称,例如:POST
方式将会映射到控制器的Post
方法中(公开方法,首字母大写),DELETE
方式将会映射到控制器的Delete
方法中,以此类推。其他非HTTP Method
命名的方法,即使是定义的包公开方法,将不会自动注册,对于应用端不可见。当然,如果控制器并未定义对应HTTP Method
的方法,该Method
请求下将会返回 HTTP Status 404
。
GoFrame
的这种RESTful
对象注册方式是一种严格的REST
路由注册方式,我们可以将控制器的对象看做REST
中的资源,而其中的HTTP Method
方法即为REST
规范的资源操作方法。如果大家不太熟悉REST
规范,或者不想太过严格的RESTful
路由设计,那么请忽略该章节。
我们可以通过BindObjectRest
方法完成REST
对象的注册,示例:
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) type Controller struct{} // RESTFul - GET func (c *Controller) Get(r *ghttp.Request) { r.Response.Write("GET") } // RESTFul - POST func (c *Controller) Post(r *ghttp.Request) { r.Response.Write("POST") } // RESTFul - DELETE func (c *Controller) Delete(r *ghttp.Request) { r.Response.Write("DELETE") } // 该方法无法映射,将会无法访问到 func (c *Controller) Hello(r *ghttp.Request) { r.Response.Write("Hello") } func main() { s := g.Server() c := new(Controller) s.BindObjectRest("/object", c) s.SetPort(8199) s.Run() }
执行后,终端输出路由表如下;
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE |---------|---------|---------|--------|---------|---------------------------|------------| default | default | :8199 | DELETE | /object | main.(*Controller).Delete | |---------|---------|---------|--------|---------|---------------------------|------------| default | default | :8199 | GET | /object | main.(*Controller).Get | |---------|---------|---------|--------|---------|---------------------------|------------| default | default | :8199 | POST | /object | main.(*Controller).Post | |---------|---------|---------|--------|---------|---------------------------|------------|
构造方法Init
与析构方法Shut
对象中的Init
和Shut
是两个在HTTP
请求流程中被Server
自动调用的特殊方法(类似构造函数
和析构函数
的作用)。
Init
回调方法对象收到请求时的初始化方法,在服务接口调用之前被回调执行。
方法定义:
// "构造函数"对象方法 func (c *Controller) Init(r *ghttp.Request) { }
Shut
回调方法当请求结束时被
Server
自动调用,可以用于对象执行一些收尾处理的操作。方法定义:
// "析构函数"对象方法 func (c *Controller) Shut(r *ghttp.Request) { }
举个例子:
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) type Controller struct{} func (c *Controller) Init(r *ghttp.Request) { r.Response.Writeln("Init") } func (c *Controller) Shut(r *ghttp.Request) { r.Response.Writeln("Shut") } func (c *Controller) Hello(r *ghttp.Request) { r.Response.Writeln("Hello") } func main() { s := g.Server() c := new(Controller) s.BindObject("/object", c) s.SetPort(8199) s.Run() }
执行后,终端输出路由表如下:
SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE |---------|---------|---------|--------|---------------|--------------------------|------------| default | default | :8199 | ALL | /object/hello | main.(*Controller).Hello | |---------|---------|---------|--------|---------------|--------------------------|------------|
可以看到,并没有自动注册Init
和Shut
这两个方法的路由,我们访问 http://127.0.0.1:8199/object/hello 后,输出结果为:
Init Hello Shut
- No labels
24 Comments
KNCX
对象注册的方式很方便。但当一个对象里面包含了POST和GET方法时,只能使用*ghttp.RouterGroup.ALL()这个方法来注册,会被一些漏扫工具认为这个接口支持了所有请求方法,这样做是不安全的。请问在有这种安全要求的情况下只能针对每一个函数进行注册吗,有什么好的解决方法吗?
ming
是说这种方式嘛?就可以指定请求方法
朱华 Hunk
建议增加一种可以绑定实时生成对象的方式(非单对象)。我自己搞了一个,不过代码写得不够规范。所以也不好意思pull request。我把文件代码帖在下面,看看能不能添加个类似的功能。
gf/ghttp_server_service_multi_object.go at feature/f_router_bind_object_multi_instances · joy999/gf (github.com)
郭强
不错。下一个版本计划增加类似于
grpc
那样的服务注册方式。朱华 Hunk
若从router层开始,每个请求中对象就是唯一的,那就可以做很多事了。比如可以写个基类,在里面存放许多只针对当前请求的数据及方法。这样不用一直显式传递ctx(利用对象隐式传递)。下层的service对象,就可以利用这个基类来生成,然后在生成时,将ctx传递给这个service。同理,在service中也可以添加类似的功能来生成dao,然后将service的ctx传递给dao层。这样整个ctx的传递,就是作为对象成员而传递。各个对象生成时加上池处理,性能上应该不会有太大影响,但是开发效率可以提升许多。
郭强
使用的示例有吗?
朱华 Hunk
你说这个完整的传递流程?还没搞出来呢,只是理论上想到这样去做。正想写个简单的gf-boost库(现在只是针对这块处理下)。把之前直接修改的Server类放进去。这样包装下,不会影响原有GF。
php01
有没有考虑路由命名功能啊?
汪保山
使用对象注册方式注册路由,对象内某个接口方法又需要路由模糊匹配,目前支持吗?
朱华 Hunk
可以多次注册的,你把你要模糊匹配那个接口,再次注册下就行了。
abchen
那这会不会很繁琐啊
nginx007
路由对象注册 用起来很爽, 配置和修改起来很方便省事, 但是目前还是遇到一些问题
(1): 比如访问 abc.com/user 与 abc.com/user/index 需要不要一样的效果,
(2): 需要一个通用处理 对象中没有定义的 URL ,类似 iris 中的 GetByWildcard 和 PostByWildcard 方法
综上我觉得以下的建议可以解决这些问题, 尽量不要占用实际可能会用到的URL名称
魏金虎
对象注册很方便,但是怎么设置局部中间件呢?
wsy998
s := g.Server()
s.BindMiddlewareDefault(ghttp.MiddlewareHandlerResponse)
c := new(controller.CLogin)
s.BindObjectMethod("POST:/login", c, "Login")
c1 := new(controller.CRegister)
s.BindObjectMethod("GET:/a", c1, "Register")
s.Run()
return nil
为什么会报这个错
2022-06-23 12:04:14.742 [FATA] duplicated route registry "GET:/a" at /home/wsy998/workspace/go/stu_mgt/internal/cmd/cmd.go:24 , already registered at /home/wsy998/workspace/go/stu_mgt/internal/cmd/cmd.go:22
海亮
重复注册了
wsy998
关键我也没重复注册呀,涉及到路由的只有这个地方,我只要去掉其中任意一个,就能跑起来
海亮
你用的是多少版本的库,要不贴下你的对象代码?
wsy998
我用的是gf init生成出来的代码然后改了一下 库是v2.0.6
// cmd.go
package cmd
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/os/gcmd"
"stu_mgt/internal/controller"
)
var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
s.BindMiddlewareDefault(ghttp.MiddlewareHandlerResponse)
c := new(controller.CLogin)
s.BindObjectMethod("POST:/login", c, "Login")
c1 := new(controller.CRegister)
s.BindObjectMethod("GET:/a", c1, "Register")
s.Run()
return nil
},
}
)
// login.go
// register.go
package controller
import (
"context"
"github.com/gogf/gf/v2/frame/g"
"stu_mgt/api/v1/req"
"stu_mgt/api/v1/res"
)
type CRegister struct{}
func (c *CRegister) Register(ctx context.Context, req *req.LoginReq) (res *res.APIResultRes, err error) {
g.Model("login_info").Insert(g.Map{
"username": "admin888",
"password": "admin888",
})
return
}
wsy998
我突然明白了,谢谢
hanwei
gf版本: 2.5.6 go版本: go1.21.4
浏览器输出空白
harbor
划重点!!!
如果路由规则中不使用内置变量,那么默认的情况下,方法将会被追加到指定的路由规则末尾 参见第一个例子,第二个例子下有备注,但是建议标红! (通过 "/" 串联)
ninjashixuan
新版的对象注册自动生成swagger很好,但是想针对 r.Parse(req) 错误时在中间进行进行统一hack一下,比如抛一些自定错误,希望可以暴露req 对象。
ninjashixuan
接上,因为想拿到req 反射读取tag 做一些定制化的工作。
ninjashixuan
冒失了,原来可以拿到。