路由注册-对象注册

对象注册 是通过一个实例化的对象来执行路由注册,以后每一个请求都交给该对象(同一对象)处理, 该对象常驻内存不释放 。

相关方法 :

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 查看效果。

路由注册-对象注册 - 图1 提示

控制器中的 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 方法绑定指定的路由到指定的方法执行( 方法名称参数区分大小写 )。

路由注册-对象注册 - 图2 提示

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。

路由注册-对象注册 - 图3 注意

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 自动调用的特殊方法(类似 构造函数 和 析构函数 的作用)。

  1. Init 回调方法

对象收到请求时的初始化方法,在服务接口调用之前被回调执行。

方法定义:

// "构造函数"对象方法
func (c *Controller) Init(r *ghttp.Request) {

}
  1. 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 后,输出结果为:

Hello
Shut