GoFrame第二天

本文涉及的产品
.cn 域名,1个 12个月
简介: GoFrame第二天

GoFrame day2


前言


昨天基本的介绍了一遍GoFrame的基本内容,但是实际Web开发中涉及到的知识还有很多,今天就来总结一下GoFrame在Web开发中的一些内容.


Web相关


昨天通过查看源码,可以看到GoFrame主要是通过对http的封装得到ghttp作为功能丰富的Web服务


image.png


其中提供了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()
}
复制代码


image.png


创建多个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()
}


image.png

但是这里的域名必须是准确的,不能使用带有通配符*等的泛域名作为输入.


路由管理


前面提到GoFrame提供了非常强大的路由功能,这部分就讲讲GoFrame是怎么解析路由参数,匹配路由以及路由注册的.


路由注册参数


昨天的那个基本例子使用了BindHandler(),下面是它的方法原型


image.png


今天就来好好分析一下路由注册的参数使用,参数格式如下


[HTTPMethod:]路由规则[@域名]


HTTP方法例如GET,PUT,DELETE都已经封装好了,当然这些并不是必需参数,默认没有的话就是ALL也就是支持所有的HTTP方法.域名也是类似,如果给了就是只在给定的域名下生效,否则就是默认全部都生效.更多的时候我们还是使用上面对Server对象的域名绑定来指定路由注册,很少使用参数中的域名设置.


精准匹配


像路由中使用固定的名称/index这种就是精准匹配


动态路由


动态路由分为命名匹配规则,模糊匹配规则,字段匹配规则.动态路由底层是哈希表和双向链表构成的路由树,可以高效匹配URI并且可以进行路由优先级控制.同时使用了缓存,可以不用重复执行相同路由的匹配.关于这部分的构造可以看看下面的源码

image.pngimage.png


命名匹配规则


使用: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()
}


image.png



优先级控制


路由的优先级控制也是非常重要的,往往没有设计好路由的优先级就有可能导致某些路由永远不能被访问到(被其他路由匹配覆盖)


优先级控制按照深度优先策略,主要是下面几个规则

  • 层级越深的规则优先级越高
  • 同一层级下精准匹配优先级高于动态路由规则的优先级
  • 同一层级下,动态路由规则优先级如下 字段匹配>命名匹配>模糊匹配,即{xxx}>:xxx>*xxx


路由注册


函数注册


函数注册BindHandler()最常见的方式有三种

  1. 像之前的例子一样,使用回调函数注册
  2. 也可以在注册外面自己实现处理器方法,然后注册自己写的处理器方法
  3. 实例化一个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()
}


image.png

当然如果我们想方法名对应路由的话框架也支持


UriTypeDefault  = 0 // (默认)全部转为小写,单词以'-'连接符号连接
UriTypeFullName = 1 // 不处理名称,以原有名称构建成URI
UriTypeAllLower = 2 // 仅转为小写,单词间不使用连接符号
UriTypeCamel    = 3 // 采用驼峰命名方式
复制代码


当然如果我们的对象有多个方法,但是只想注册其中的部分方法,那我们可以在BindObject()中添加方法名来注册我们想要的,例如s.BindObject("/{xxx}",c,"ShowData"),这里方法名需要对应上区分大小写

BindObjectMethod()就可以更加具体,将具体的对象方法绑定注册到对应的路由上.还有更加严格的RESTful路由设计对应的方法,要求对象的方法名必须是HTTP的Method,然后将类方法对应映射.

在对象注册中还有隐藏的方法,那就是InitShut,这两个方法可以实现对象绑定时隐式自动调用.比如我们的对象需要初始化加载一些内容,这个时候就可以写在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()
}


image.png


但是最新版本的已经更改为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()
}
复制代码


image.png

这样熟悉的swagger页面就展示在我们眼前了


目录
相关文章
|
编解码 前端开发
重学JavaWeb第二天(七)
重学JavaWeb第二天
95 0
重学JavaWeb第二天(三)
重学JavaWeb第二天
166 0
重学JavaWeb第二天(五)
重学JavaWeb第二天
96 0
|
前端开发 容器
重学JavaWeb第二天(二)
重学JavaWeb第二天
149 0
|
前端开发
重学JavaWeb第二天(一)
重学JavaWeb第二天
137 0
|
前端开发 数据安全/隐私保护
重学JavaWeb第二天(六)
重学JavaWeb第二天
148 0
重学JavaWeb第二天(四)
重学JavaWeb第二天
164 0
|
前端开发 JavaScript 程序员
重学JavaWeb第三天(十一)
重学JavaWeb第三天(十一)
88 0
|
运维 Java Spring
求求你们别再用kill-9了,这才是SpringBoot停机的正确方式!!!
求求你们别再用kill-9了,这才是SpringBoot停机的正确方式!!!
328 0
|
JavaScript 前端开发 API
Electron实践第二天
Electron实践第二天
88 0
Electron实践第二天