GoFrame day2
前言
昨天基本的介绍了一遍GoFrame的基本内容,但是实际Web开发中涉及到的知识还有很多,今天就来总结一下GoFrame在Web开发中的一些内容.
Web相关
昨天通过查看源码,可以看到GoFrame主要是通过对http
的封装得到ghttp
作为功能丰富的Web服务
其中提供了Router
,Cookie
,Session
,路由注册
,插件
等等功能,还支持很多特性,下面就一一举例
基本功能
多端口监听
如果我们想要服务支持多个端口监听,那就可以在SetPort
中设置多个端口号去实现,对于HTTPS的服务我们可以利用SetHTTPSPort
来设置多个端口.
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) func main() { s := g.Server() s.BindHandler("/", func(r *ghttp.Request) { r.Response.Writefln("Hello World!") }) //set Ports s.SetPort(8000, 8001, 8002) s.Run() } 复制代码
创建多个ghttp实例
GoFrame还支持创建多个ghttp
实例,只需要每次初始实例化的时候传入不同的单例名称.这样的话不仅在一个进程中可以运行多个实例,还可以保证每个实例的唯一性,在不同的goroutine甚至不同模块中都能确保使用的是同一个实例.
域名绑定
Server支持多个域名绑定,这样可以实现不同域名下的同一个路由会有不同的服务
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) func main() { s := g.Server() s.Domain("127.0.0.1").BindHandler("/", func(r *ghttp.Request) { r.Response.Writefln("127.0.0.1: Hello") }) s.Domain("localhost").BindHandler("/", func(r *ghttp.Request) { r.Response.Writefln("localhost: Hello") }) s.Run() }
但是这里的域名必须是准确的,不能使用带有通配符*等的泛域名作为输入.
路由管理
前面提到GoFrame提供了非常强大的路由功能,这部分就讲讲GoFrame是怎么解析路由参数,匹配路由以及路由注册的.
路由注册参数
昨天的那个基本例子使用了BindHandler()
,下面是它的方法原型
今天就来好好分析一下路由注册的参数使用,参数格式如下
[HTTPMethod:]路由规则[@域名]
HTTP方法例如GET
,PUT
,DELETE
都已经封装好了,当然这些并不是必需参数,默认没有的话就是ALL
也就是支持所有的HTTP方法.域名也是类似,如果给了就是只在给定的域名下生效,否则就是默认全部都生效.更多的时候我们还是使用上面对Server
对象的域名绑定来指定路由注册,很少使用参数中的域名设置.
精准匹配
像路由中使用固定的名称/index
这种就是精准匹配
动态路由
动态路由分为命名匹配规则,模糊匹配规则,字段匹配规则.动态路由底层是哈希表和双向链表构成的路由树,可以高效匹配URI并且可以进行路由优先级控制.同时使用了缓存,可以不用重复执行相同路由的匹配.关于这部分的构造可以看看下面的源码
命名匹配规则
使用:xxx
进行匹配,也就是该URI层级必须有值,然后对应的匹配参数会被解析为路由参数传递给路由使用
模糊匹配规则
使用*xxx
进行匹配,对URI指定位置之后其他规则之前的所有参数进行模糊匹配,该URI层可以为空
字段匹配规则
使用{xxx}
进行匹配,可以对URI任意位置的参数进行截取匹配,这层URI必须有值,并且在这一层可以进行多个字段的匹配
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) func main() { s := g.Server() s.BindHandler("/api/:attr/{object}/*act", func(r *ghttp.Request) { r.Response.Writeln("attr: ", r.Get("attr")) r.Response.Writeln("object: ", r.Get("object")) r.Response.Writeln("act: ", r.Get("act")) }) s.SetPort(8080) s.Run() }
优先级控制
路由的优先级控制也是非常重要的,往往没有设计好路由的优先级就有可能导致某些路由永远不能被访问到(被其他路由匹配覆盖)
优先级控制按照深度优先策略,主要是下面几个规则
- 层级越深的规则优先级越高
- 同一层级下精准匹配优先级高于动态路由规则的优先级
- 同一层级下,动态路由规则优先级如下 字段匹配>命名匹配>模糊匹配,即
{xxx}>:xxx>*xxx
路由注册
函数注册
函数注册BindHandler()
最常见的方式有三种
- 像之前的例子一样,使用回调函数注册
- 也可以在注册外面自己实现处理器方法,然后注册自己写的处理器方法
- 实例化一个gtype对象,然后包装这个对象的一些方法,接着注册时注册对象的方法.关于gtype会在后面详细介绍
对象注册
对象注册会注册一个实例化对象,以后的每一次请求都会交给这个对象来处理,主要的方法如下
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 复制代码
这里我们同样是定义一个对象,并且实现一些对象的方法使得针对不同路由调用不同的方法名.这里注意一个问题,因为URI默认不支持大小写区分,所以当方法名带有多个单词时(以大写字符区分),会自动将解析的路由名全部改为小写,并且以-
连接这些小写单词.看看下面这个例子
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) type Control struct{} func (c *Control) Index(r *ghttp.Request) { r.Response.Writeln("Control Index") } func (c *Control) ShowData(r *ghttp.Request) { r.Response.Writeln("Control Show Data") } func main() { s := g.Server() c := new(Control) s.BindObject("/{xxx}", c) s.SetPort(8080) s.Run() }
当然如果我们想方法名对应路由的话框架也支持
UriTypeDefault = 0 // (默认)全部转为小写,单词以'-'连接符号连接 UriTypeFullName = 1 // 不处理名称,以原有名称构建成URI UriTypeAllLower = 2 // 仅转为小写,单词间不使用连接符号 UriTypeCamel = 3 // 采用驼峰命名方式 复制代码
当然如果我们的对象有多个方法,但是只想注册其中的部分方法,那我们可以在BindObject()
中添加方法名来注册我们想要的,例如s.BindObject("/{xxx}",c,"ShowData")
,这里方法名需要对应上区分大小写
BindObjectMethod()
就可以更加具体,将具体的对象方法绑定注册到对应的路由上.还有更加严格的RESTful路由设计对应的方法,要求对象的方法名必须是HTTP的Method,然后将类方法对应映射.
在对象注册中还有隐藏的方法,那就是Init
和Shut
,这两个方法可以实现对象绑定时隐式自动调用.比如我们的对象需要初始化加载一些内容,这个时候就可以写在Init
中自动实现;同样的,当我们对象销毁时需要释放一些资源可以将这部分操作写在Shut
中.
分组路由
当我们有大量路由需要控制时,如果一个个注册是很难管理的.这个时候就需要设置一些路由组,拥有相同前缀的路由可以分在同一个路由组下,这样统一注册,方便了管理也减少了出错.
// 创建分组路由 func (s *Server) Group(prefix string, groups ...func(g *RouterGroup)) *RouterGroup func (d *Domain) Group(prefix string, groups ...func(g *RouterGroup)) *RouterGroup // 注册Method路由 func (g *RouterGroup) ALL(pattern string, object interface{}, params...interface{}) func (g *RouterGroup) GET(pattern string, object interface{}, params...interface{}) func (g *RouterGroup) PUT(pattern string, object interface{}, params...interface{}) func (g *RouterGroup) POST(pattern string, object interface{}, params...interface{}) func (g *RouterGroup) DELETE(pattern string, object interface{}, params...interface{}) func (g *RouterGroup) PATCH(pattern string, object interface{}, params...interface{}) func (g *RouterGroup) HEAD(pattern string, object interface{}, params...interface{}) func (g *RouterGroup) CONNECT(pattern string, object interface{}, params...interface{}) func (g *RouterGroup) OPTIONS(pattern string, object interface{}, params...interface{}) func (g *RouterGroup) TRACE(pattern string, object interface{}, params...interface{}) // 中间件绑定 func (g *RouterGroup) Middleware(handlers ...HandlerFunc) *RouterGroup // REST路由 func (g *RouterGroup) REST(pattern string, object interface{}) // 批量注册 func (g *RouterGroup) Map(m map[string]interface{}) func (g *RouterGroup) ALLMap(m map[string]interface{}) // 规范化路由方式,自动绑定Handler或者路由对象 func (g *RouterGroup) Bind(handlerOrObject ...interface{}) *RouterGroup 复制代码
我们可以创建一个路由组对象,然后在路由组中实现不同的方法.
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) func main() { s := g.Server() group := s.Group("/api") group.ALL("/all", func(r *ghttp.Request) { r.Response.Write("all") }) group.GET("/get", func(r *ghttp.Request) { r.Response.Write("get") }) group.POST("/post", func(r *ghttp.Request) { r.Response.Write("post") }) s.SetPort(8080) s.Run() } 复制代码
当然除了简单的路由组,还支持层级注册.层级注册也就是回调函数中不再使用简单的ghttp.Request
而是使用ghttp.RouterGroup
实现不同的路由层级,这样一来可以很轻易的使用RouterGroup
对象创建不同的层级,并且支持不同的路由注册方法和中间件;如果觉得还是不够清晰,还可以尝试批量注册ALLMap
,这样可以清晰地看到每个路由组下对应的路由以及调用的方法,比层级注册更加清楚但是貌似无法限制每个路由对应的请求方法,都是ALL.
规范化路由
昨天看到源码发现有openapi
的字样,当时就在想难道GoFrame也支持这个接口协议?那不就可以生成接口文档了.今天学习果然支持了这个功能.这个功能之前在FastAPI
中就体验过,很轻松就能理清每一个API的功能以及调用方式,这为前后端的协调提供了很好的帮助.
由于默认是关闭接口文档功能的,所以我们需要先在config.yaml
设置文件中添加设置
server: address: "8088" openapiPath: "/api.json" swaggerPath: "/swagger" 复制代码
然后写个例子
package main import ( "context" "fmt" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) type HelloReq struct { g.Meta `path:"/hello" method:"get"` Name string `v:"required" dc:"Your name"` } type HelloRes struct { Reply string `dc:"Reply content"` } type Hello struct{} func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { g.Log().Debugf(ctx, `receive say: %+v`, req) res = &HelloRes{ Reply: fmt.Sprintf(`Hi %s`, req.Name), } return } func main() { s := g.Server() s.Use(ghttp.MiddlewareHandlerResponse) s.Group("/", func(group *ghttp.RouterGroup) { group.Bind( new(Hello), ) }) s.SetPort(8080) s.Run() }
但是最新版本的已经更改为redoc支持了,并不能像原来swagger一样方便在页面上直接做简单测试,根据官网的例子我们还是可以通过自定义UI来解决这个问题
package main import ( "context" "fmt" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" ) const ( swaggerUIPageContent = ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="description" content="SwaggerUI"/> <title>SwaggerUI</title> <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@latest/swagger-ui.css" /> </head> <body> <div id="swagger-ui"></div> <script src="https://unpkg.com/swagger-ui-dist@latest/swagger-ui-bundle.js" crossorigin></script> <script> window.onload = () => { window.ui = SwaggerUIBundle({ url: '/api.json', dom_id: '#swagger-ui', }); }; </script> </body> </html> ` ) type HelloReq struct { g.Meta `path:"/hello" method:"get"` Name string `v:"required" dc:"Your name"` } type HelloRes struct { Reply string `dc:"Reply content"` } type Hello struct{} func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { g.Log().Debugf(ctx, `receive say: %+v`, req) res = &HelloRes{ Reply: fmt.Sprintf(`Hi %s`, req.Name), } return } func main() { s := g.Server() s.Group("/", func(group *ghttp.RouterGroup) { group.GET("/swagger", func(r *ghttp.Request) { r.Response.Write(swaggerUIPageContent) }) group.Bind( new(Hello), ) }) s.SetOpenApiPath("/api.json") s.SetPort(8080) s.Run() } 复制代码
这样熟悉的swagger页面就展示在我们眼前了