一、程序启动
所有的程序入口都是由main.go进入,该文件主要是调用internal/cmd包的对应命令引导程序启动。在项目模板中,默认会执行internal/cmd包的Main对象Run命令引导程序启动。
// 调用internal/cmd包的对应命令引导程序启动。
// 框架的核心组件均需要传递context上下文参数,这里使用gctx.New表示创建一个带链路跟踪特性的context上下文对象给下游链路。
cmd.Main.Run(gctx.GetInitCtx())
引导启动
Main
对象的Run
命令的主要作用是做引导启动
// 默认创建一个HTTP Server(s := g.Server()),然后通过分组路由的方式注册路由,并启动HTTP Server,
// 随后HTTP Server将会阻塞运行,它同时也会异步监听系统信号,直至收到退出信号后,它会优雅关闭连接随后退出进程。
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.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Bind(
hello.New(),
)
})
s.Run()
return nil
},
}
)
路由注册
在web开发中,“route”是指根据url分配到对应的处理程序。
以这个页面为例:
把这一整个页面看成一个大的Group(/api),左上角的那些button可以看成子Group,然后每个子Group再对应一个页面,该页面的每个按钮又可以进行一个Group划分。
在项目模板中使用了Group
方法创建了分组路由,框架的HTTP Server
支持多种路由注册方式,而分组路由
也是最常见的路由注册方式
。
- - 路由管理-路由规则(示例)–以哪种方式来定义规则
package main
import (
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
// s := g.Server(): 创建一个 HTTP 服务器实例。
s := g.Server()
// s.BindHandler("/:name", func(r *ghttp.Request) { ... }):将处理函数绑定到路由规则 /:name 上,该规则匹配带有名为 name 的路径参数的请求。
s.BindHandler("/:name", func(r *ghttp.Request){
r.Response.Writeln(r.Router.Uri)
})
// 将处理函数绑定到路由规则 /:name/update
s.BindHandler("/:name/update", func(r *ghttp.Request){
r.Response.Writeln(r.Router.Uri)
})
s.BindHandler("/:name/:action", func(r *ghttp.Request){
r.Response.Writeln(r.Router.Uri)
})
s.BindHandler("/:name/*any", func(r *ghttp.Request){
r.Response.Writeln(r.Router.Uri)
})
s.BindHandler("/user/list/{field}.html", func(r *ghttp.Request){
r.Response.Writeln(r.Router.Uri)
})
s.SetPort(8199)
s.Run()
}
— 路由管理-注册规则(示例)
package main
import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)
func main() {
// 创建一个 HTTP 服务器实例
s := g.Server()
//创建一个名为 /api 的路由组。
group := s.Group("/api")
//将处理函数绑定到路由规则 /api/all 上,该规则匹配所有 HTTP 方法的请求
group.ALL("/all", func(r *ghttp.Request) {
r.Response.Write("all")
})
// 将处理函数绑定到路由规则 /api/get 上,该规则匹配 GET 方法的请求
group.GET("/get", func(r *ghttp.Request) {
r.Response.Write("get")
})
// 将处理函数绑定到路由规则 /api/post 上,该规则匹配 POST 方法的请求。
group.POST("/post", func(r *ghttp.Request) {
r.Response.Write("post")
})
// 设置服务器的监听端口为 8199。
s.SetPort(8199)
s.Run()
}
其中,/api/get仅允许GET方式访问,/api/post仅允许POST方式访问,/api/all允许所有的方式访问。
是吗?是的
GoFrame
框架的分组路由注册支持更加直观优雅层级的注册方式,以便于开发者更方便地管理路由列表。路由层级注册方式也是推荐的路由注册方式。
package main
import (
"net/http"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
)
// 中间件用来判断是不是你,可以借助“钥匙开门”来理解
// 这个中间件函数用于进行认证,判断请求中是否包含正确的 token,如果 token 是 "123456",则通过调用 r.Middleware.Next() 继续执行下一个中间件或处理函数,否则返回 403 Forbidden 状态码。
func MiddlewareAuth(r *ghttp.Request) {
token := r.Get("token")
if gconv.String(token) == "123456" {
r.Middleware.Next()
} else {
r.Response.WriteStatus(http.StatusForbidden)
}
}
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
// 这个中间件函数用于在请求处理前后打印日志信息,然后继续执行下一个中间件或处理函数
func MiddlewareLog(r *ghttp.Request) {
r.Middleware.Next()
fmt.Println(r.Response.Status, r.URL.Path)
}
func main() {
// 创建一个 HTTP 服务器实例。
s := g.Server()
// 注册全局中间件函数 MiddlewareLog,用于打印请求日志信息。
s.Use(MiddlewareLog)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareAuth, MiddlewareCORS)
group.GET("/test", func(r *ghttp.Request) {
r.Response.Write("test")
})
group.Group("/order", func(group *ghttp.RouterGroup) {
group.GET("/list", func(r *ghttp.Request) {
r.Response.Write("list")
})
group.PUT("/update", func(r *ghttp.Request) {
r.Response.Write("update")
})
})
group.Group("/user", func(group *ghttp.RouterGroup) {
group.GET("/info", func(r *ghttp.Request) {
r.Response.Write("info")
})
group.POST("/edit", func(r *ghttp.Request) {
r.Response.Write("edit")
})
group.DELETE("/drop", func(r *ghttp.Request) {
r.Response.Write("drop")
})
})
group.Group("/hook", func(group *ghttp.RouterGroup) {
group.Hook("/*", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Write("hook any")
})
group.Hook("/:name", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Write("hook name")
})
})
})
s.SetPort(8100)
s.Run()
}
梳理一下**路由
**:点击一个功能按钮,输入参数,点击运行,返回一个值。
如何测试,输入对应功能的路由地址,看看返回什么值
问题:依据某一个规则生成路由,由别人测试。依据什么规则呢?---- 就是路由规则呗
// 这个程序的主要作用是创建一个简单的 HTTP 服务器,并定义了多个路由规则和相应的处理函数。
// 它使用了 GoFrame 框架提供的功能来处理客户端的请求,并返回相应的结果。
func main() {
s := g.Server()
// 一个简单的分页路由示例
// {page} 是一个路径参数,匹配在 /user/list/ 后面的任意字符串,并且以.html 结尾。当匹配成功时,请求处理函数会打印出路径参数的值。
s.BindHandler("/user/list/{page}.html", func(r *ghttp.Request) {
r.Response.Writeln(r.Get("page"))
})
// {xxx} 规则与 :xxx 规则混合使用
// /{object}/:attr/{act}.php:这个路由规则混合了 {xxx} 和 :xxx 两种参数匹配规则。
// {object} 是一个路径参数,匹配在路径中任意的字符串。:attr 是一个命名参数,匹配在 / 后面的任意字符串。
// {act} 是一个路径参数,匹配以 .php 结尾的任意字符串。当匹配成功时,请求处理函数会打印出这三个参数的值。
s.BindHandler("/{object}/:attr/{act}.php", func(r *ghttp.Request) {
r.Response.Writeln(r.Get("object"))
r.Response.Writeln(r.Get("attr"))
r.Response.Writeln(r.Get("act"))
})
// 多种模糊匹配规则混合使用
// {class}-{course}/:name/*act:这个路由规则混合了多种模糊匹配规则。{class} 和 {course} 都是路径参数,分别匹配以 - 分隔的任意字符串。
// :name 是一个命名参数,匹配在 / 后面的任意字符串。*act 是一个通配符参数,匹配以 / 开头的任意路径。当匹配成功时,请求处理函数会打印出这四个参数的值。
s.BindHandler("/{class}-{course}/:name/*act", func(r *ghttp.Request) {
r.Response.Writeln(r.Get("class"))
r.Response.Writeln(r.Get("course"))
r.Response.Writeln(r.Get("name"))
r.Response.Writeln(r.Get("act"))
})
s.SetPort(8199)
s.Run()
}
$ curl -XGET http://127.0.0.1:8199/user/list/1.html
1
$ curl -XGET http://127.0.0.1:8199/user/info/save.php
user
info
save
$ curl -XGET http://127.0.0.1:8199/class3-math/john/score
class3
math
john
score
–路由注册
在 Web 服务器中,"路由注册"是指将 URL 路径
与相应的处理函数
或处理逻辑
进行关联
(给url指条路,让它知道访问谁)的过程。
解释:
-
客户端发送请求到 Web 服务器(发过来的时候就已经按规则搞好了)
-
服务器根据请求的 URL 路径找到对应的路由规则
-
执行相应的处理函数来处理请求。
路由注册的作用是
确定请求的路径
与处理逻辑之间的映射关系
,使服务器能够正确地路由和处理不同的请求。
假设你是一家餐厅的老板,你拥有一个网站用于接收客户的在线订餐请求。当客户访问你的网站并选择特定的菜品时,你的网站需要知道如何处理这些请求并提供正确的响应。
在这个例子中,路由注册的过程可以类比为在你的餐厅建立一张菜单。菜单上列出了不同的菜品和它们的价格。每个菜品对应着一个处理函数,用于准备并提供客户点餐所需的食物。
你的服务员会在客户到达餐厅时将菜单交给他们。当客户选择菜品并告诉服务员时,服务员会根据菜单上的信息确定该菜品的价格和制作方式。最后,服务员将订单传递给厨房,并在完成时将食物带到客户桌上。
在这个例子中:
菜单就相当于路由注册,它定义了不同菜品(URL 路径)和它们的价格(处理函数)之间的映射关系。
客户点餐就相当于客户端发送请求到服务器,请求包含了客户的选择(URL 路径)。
服务员的角色类似于服务器,它根据客户点餐的内容(URL 路径)在菜单(路由注册)中查找相应的菜品(处理函数)并进行处理。
通过路由注册,Web 服务器能够根据客户端请求的路径来确定执行的处理函数,以实现相应的功能或提供相应的服务。这样,服务器可以根据不同的路径来路由请求,执行不同的处理逻辑,并返回适当的响应给客户端。
路由注册是大老知,派活。通过路由注册,即按钮A,需要执行什么操作;按钮B,需要执行什么操作…
- - - 1、函数注册
注册的服务可以是一个实例化对象的方法地址,也可以是一个包方法地址。服务需要的数据可以通过模块内部变量形式
或者对象内部变量形式
进行管理,相关方法:
BindHandler(pattern string, handler interface{})
方法的逻辑是将路由的匹配模式—pattern string 和相应的处理函数或处理逻辑—handler interface{}进行关联。当收到符合指定模式的请求时,服务器将调用指定的处理函数来处理请求。
// 通过BindHandler方法完成回调函数的注册
// handler是一个接口类型的参数,用于指定处理请求的处理函数或处理逻辑。
// BindHandler 方法的作用是将指定的路由模式和处理函数进行关联
func (s *Server) BindHandler(pattern string, handler interface{})
示例:
func main() {
s := g.Server()
s.BindHandler("/", func(r *ghttp.Request) {
r.Response.Write("哈喽世界!")
})
s.SetPort(8199)
s.Run()
}
---- 包方法注册
在该示例中,我们通过包方法的形式来注册路由。该方法返回总共访问的次数,由于涉及到并发安全,因此total
变量使用了gtype.Int
并发安全类型。执行后,当我们不停访问 http://127.0.0.1:8199/total 时,可以看到返回的数值不停递增。
var (
total = gtype.NewInt()
)
func Total(r *ghttp.Request) {
r.Response.Write("total:", total.Add(1))
}
func main() {
s := g.Server()
s.BindHandler("/total", Total)
s.SetPort(8199)
s.Run()
}
---- 对象方法注册
// Controller 结构体定义
type Controller struct {
total *gtype.Int // 使用 gtype 包中的 gtype.Int 类型
}
// Total 方法定义
// 单参,无返回值,如果有返回值的话,就得写成“Total()(r *ghttp.Request)”这种形式了
func (c *Controller) Total(r *ghttp.Request) {
r.Response.Write("total:", c.total.Add(1))
}
func main() {
s := g.Server() // 创建一个 HTTP 服务器实例
// 实例化
c := &Controller{
total: gtype.NewInt(), // 使用 gtype 包中的 gtype.NewInt() 创建一个 gtype.Int 实例
}
s.BindHandler("/total", c.Total) // 将路由路径 "/total" 与 Controller 结构体中的 Total 函数进行关联
s.SetPort(8199) // 设置服务器监听端口为 8199
s.Run() // 启动服务器并开始监听来自客户端的请求
}
使用了对象来封装业务逻辑所需的变量,使用回调函数注册来灵活注册对应的对象方法。
回调函数是一种将函数作为参数传递给其他函数,并在需要的时候被调用的机制。回调函数常用于异步编程、事件处理、扩展框架等场景中,它允许我们将某些逻辑的执行权交给调用方,使代码更加灵活和可扩展。
— 2、对象注册
对象注册注册一个实例化的对象,以后每一个请求都交给该对象(同一对象)处理,该对象常驻内存不释放。
func (s *Server) BindObject(pattern string, object interface{}, methods ...string) error
// methods 是一个可变参数,用于指定允许的请求方法,如果不传递 methods 参数,则默认为允许所有请求方法。
//比如:s.BindObject("/object", c, "GET", "POST") 表示只允许 GET 和 POST 请求方法,并将其与处理对象 c 进行关联。
// 结构体定义
// 为什么要用结构体?
// 因为结构体里不只有"index",还有show
type Controller struct {
}
// Index 方法定义,用于处理 /object 路径的请求(有/object了,你想用它访问啥)
// 通过调用 r.Response.Write("index") 将字符串 "index" 写入响应中。
// ghttp.Request 是 GoFrame 框架中的类型,表示一个 HTTP 请求对象。
func (c *Controller) Index(r *ghttp.Request) {
r.Response.Write("index")
}
// Show 方法定义
func (c *Controller) Show(r *ghttp.Request) {
r.Response.Write("show")
}
func main() {
// 对象在进行路由注册时便生成了一个对象s(对象在Server启动时便生成),此后不管多少请求进入,Server都是将请求转交给该对象对应的方法c进行处理。
s := g.Server()
// 结构体实例化
c := new(Controller)
// 对象注册BindObject
s.BindObject("/object", c)
s.SetPort(8199)
s.Run()
}
路由内置变量(路由你的内置变量)
当使用BindObject
方法进行对象注册时,在路由规则中可以使用两个内置的变量:{.struct}
和{.method}
,前者表示当前对象名称,后者表示当前注册的方法名。
例子:
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()
}
命名风格规则
type User struct {
}
func (u *User) ShowList(r *ghttp.Request) {
r.Response.Write("list")
}
func main() {
k := new(User)
// 对象方法名称的路由生成方式
s1 := g.Server("UriTypeDefault")
s2 := g.Server("UriTypeFullName")
s3 := g.Server("UriTypeAllLower")
s4 := g.Server("UriTypeCamel")
// (默认)全部转为小写,单词以'-'连接符号连接
s1.SetNameToUriType(ghttp.UriTypeDefault)
// 不处理名称,以原有名称构建成URI
s2.SetNameToUriType(ghttp.UriTypeFullName)
// 仅转为小写,单词间不使用连接符号
s3.SetNameToUriType(ghttp.UriTypeAllLower)
// 采用驼峰命名方式
s4.SetNameToUriType(ghttp.UriTypeCamel)
s1.BindObject("/{.struct}/{.method}", k)
s2.BindObject("/{.struct}/{.method}", k)
s3.BindObject("/{.struct}/{.method}", k)
s4.BindObject("/{.struct}/{.method}", k)
s1.SetPort(8100)
s2.SetPort(8200)
s3.SetPort(8300)
s4.SetPort(8400)
s1.Start()
s2.Start()
s3.Start()
s4.Start()
// 没有它还无法保持
g.Wait()
}
对象方法注册
假如控制器中有若干公开方法,但是我只想注册其中几个,其余的方法我不想对外公开,怎么办?我们可以通过BindObject
传递第三个非必需参数替换实现,参数支持传入多个方法名称,多个名称以英文,
号分隔(方法名称参数区分大小写
因为**
BindObject
**第三个参数是一个可变参数,可接受任意参数:methods …string
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)
// 不应该是Show吗,怎么是"Show"
s.BindObject("/object", c, "Show")
s.SetPort(8199)
s.Run()
}
----绑定路由方法
我们可以通过BindObjectMethod
方法绑定指定的路由到指定的方法执行(方法名称参数区分大小写)
BindObjectMethod
和BindObject
的区别:BindObjectMethod
将对象中的指定方法与指定路由规则进行绑定(比如Show方法就给这个路由),第三个method
参数只能指定一个方法名称;BindObject
注册时,所有的路由都是对象方法名称按照规则生成的,第三个methods
参数可以指定多个注册的方法名称(如s4.BindObject(“/{.struct}/{.method}”, k),k可以指定多个注册方法)。
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)
// 这个就生成不出来"index"
s.BindObjectMethod("/show", c, "Show")
s.SetPort(8199)
s.Run()
}
----RESTful
对象注册
RESTful
设计方式的控制器通常用于API
服务。在这种模式下,HTTP
的Method
将会映射到控制器对应的方法名称,如:POST
方式将会映射到控制器的Post
方法中(公开方法,首字母大写),DELETE
方式将会映射到控制器的Delete
方法中…。其他非HTTP Method
命名的方法,即使是定义的包公开方法,将不会自动注册,对于应用端不可见。当然,如果控制器并未定义对应HTTP Method
的方法,该Method
请求下将会返回 HTTP Status 404
。
type Controller struct{}
func (c *Controller) Get(r *ghttp.Request) {
r.Response.Write("Get")
}
func (c *Controller) Post(r *ghttp.Request) {
r.Response.Write("Post")
}
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()
}
----构造方法Init
与析构方法Shut
对象中的Init
和Shut
是两个在HTTP
请求流程中被Server
自动调用的特殊方法(类似构造函数
和析构函数
的作用)。不会自动注册Init
和Shut
这两个方法的路由,但其中的方法可以被获取。
-
Init
回调方法对象收到请求时的初始化方法,在服务接口调用之前被回调执行。
方法定义:
// "构造函数"对象方法 func (c *Controller) Init(r *ghttp.Request) { }
-
Shut
回调方法当请求结束时被
Server
自动调用,可以用于对象执行一些收尾处理的操作。方法定义:
// "析构函数"对象方法 func (c *Controller) Shut(r *ghttp.Request) { }
— 3、路由注册-分组路由
----分组路由
GoFrame
框架支持分组路由的注册方式,可以给分组路由指定一个prefix
前缀(也可以直接给定/
前缀,表示注册在根路由下),在该分组下的所有路由注册都将注册在该路由前缀下。分组路由注册方式也是推荐的路由注册方式。
func main() {
s := g.Server()
// Group方法用于创建一个分组路由对象,并且支持在指定域名对象上创建。
// 接下来的方法都基于它
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(8199)
s.Run()
}
---- 层级注册
妈的,输入key和value后,还是禁止我访问,怎么事???
package main
import (
"net/http"
"fmt"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gconv"
)
func MiddlewareAuth(r *ghttp.Request) {
token := r.Get("token")
if gconv.String(token) == "123456" {
r.Middleware.Next()
} else {
r.Response.WriteStatus(http.StatusForbidden)
}
}
func MiddlewareCORS(r *ghttp.Request) {
r.Response.CORSDefault()
r.Middleware.Next()
}
func MiddlewareLog(r *ghttp.Request) {
r.Middleware.Next()
fmt.Println(r.Response.Status, r.URL.Path)
}
func main() {
s := g.Server()
s.Use(MiddlewareLog)
s.Group("/api.v2", func(group *ghttp.RouterGroup) {
group.Middleware(MiddlewareAuth, MiddlewareCORS)
group.GET("/test", func(r *ghttp.Request) {
r.Response.Write("test")
})
group.Group("/order", func(group *ghttp.RouterGroup) {
group.GET("/list", func(r *ghttp.Request) {
r.Response.Write("list")
})
group.PUT("/update", func(r *ghttp.Request) {
r.Response.Write("update")
})
})
group.Group("/user", func(group *ghttp.RouterGroup) {
group.GET("/info", func(r *ghttp.Request) {
r.Response.Write("info")
})
group.POST("/edit", func(r *ghttp.Request) {
r.Response.Write("edit")
})
group.DELETE("/drop", func(r *ghttp.Request) {
r.Response.Write("drop")
})
})
group.Group("/hook", func(group *ghttp.RouterGroup) {
group.Hook("/*", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Write("hook any")
})
group.Hook("/:name", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Write("hook name")
})
})
})
s.SetPort(8199)
s.Run()
}
group.GET("/info", func(r *ghttp.Request) {
r.Response.Write("info")
})
group.POST("/edit", func(r *ghttp.Request) {
r.Response.Write("edit")
})
group.DELETE("/drop", func(r *ghttp.Request) {
r.Response.Write("drop")
})
})
group.Group("/hook", func(group *ghttp.RouterGroup) {
group.Hook("/*", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Write("hook any")
})
group.Hook("/:name", ghttp.HookBeforeServe, func(r *ghttp.Request) {
r.Response.Write("hook name")
})
})
})
s.SetPort(8199)
s.Run()
}