入门16_goframe框架学习
WEB服务开发:https://goframe.org/pages/viewpage.action?pageId=1114405
开始使用
先来一个Hello World:
package main
import (
"github.com/gogf/gf/frame/g"
"github.com/gogf/gf/net/ghttp"
)
func main() {
s := g.Server() //单例模式,默认监听80
// Set*方法来设置Server的属性
s.SetIndexFolder(true) //是否允许列出Server主目录的文件列表(默认为false)。
s.SetServerRoot("/home/www/") //SetServerRoot用来设置Server的主目录(类似nginx中的root配置,默认为空)。(只有设置了主目录,才支持对应主目录下的静态文件的访问。)
//动态路由
s.BindHandler("/{class}-{course}/:name/*act", func(r *ghttp.Request) {
r.Response.Writef(
"%v %v %v %v",
r.Get("class"),
r.Get("course"),
r.Get("name"),
r.Get("act"),
)
})
//多端口
s.SetPort(8100, 8200, 8300)
//域名绑定
s.Domain("127.0.0.1").BindHandler("/", Hello1)
s.Domain("localhost").BindHandler("/", Hello2)
s.Run()
}其他:平滑重启,HTTPS支持
路由注册
路由规则
func main() {
s := g.Server()
// 三种模糊匹配路由规则,:name、*any、{field}分别表示命名匹配规则、模糊匹配规则及字段匹配规则
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()
}BindHandler的原型:
func (s *Server) BindHandler(pattern string, handler HandlerFunc) error
[HTTPMethod:]路由规则[@域名] 其中HTTPMethod(支持的Method:GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE)和@域名为非必需参数
// 该路由规则仅会在GET请求及localhost域名下有效
s.BindHandler("GET:/order/info/{order_id}@localhost", func(r *ghttp.Request){
r.Response.WriteJson(r.Router)
})优先级控制
优先级控制按照深度优先策略,最主要的几点因素:
层级越深的规则优先级越高;
同一层级下,精准匹配优先级高于模糊匹配;
同一层级下,模糊匹配优先级:字段匹配 > 命名匹配 > 模糊匹配; 路由注册
| 注册方式 | 使用难度 | 安全系数 | 执行性能 | 内存消耗 |
|---|---|---|---|---|
| 回调函数注册 | 高 | 低 | 高 | 低 |
| 执行对象注册 | 中 | 中 | 中 | 中 |
| 控制器方式注册 | 低 | 高 | 低 | 高 |
比较指标说明:
- 使用难度:主要指对于执行流程以及数据管理维护的复杂度;
- 安全系数:主要指在异步多协程下的数据安全管理维护;
- 执行性能:执行性能,相对比较的结果;
- 内存消耗:内存消耗,相对比较的结果;
注册方法
func (s *Server) BindHandler(pattern string, handler HandlerFunc) error
func (s *Server) BindObject(pattern string, obj interface{}, methods ...string) error
func (s *Server) BindObjectMethod(pattern string, obj interface{}, method string) error
func (s *Server) BindObjectRest(pattern string, obj interface{}) error域名路由注册方法(略)
注册方式
函数注册(略)
对象注册
func main() {
u := new(User)
s1 := g.Server("xxx")
s1.SetNameToUriType(ghttp.URI_TYPE_DEFAULT)//命名风格规则
s1.BindObject("/{.struct}/{.method}", u)//路由内置变量
s1.SetPort(8100)
s1.Start()
g.Wait()
}对象方法注册
假如控制器中有若干公开方法,但是我只想注册其中几个,其余的方法我不想对外公开
我们可以通过BindObject传递第三个非必需参数替换实现,参数支持传入多个方法名称,多个名称以英文,号分隔(方法名称参数区分大小写)。
s.BindObject("/object", c, "Show")绑定路由方法
我们可以通过BindObjectMethod方法绑定指定的路由到指定的方法执行(方法名称参数区分大小写)。
BindObjectMethod和BindObject的区别: BindObjectMethod将对象中的指定方法与指定路由规则进行绑定,第三个method参数只能指定一个方法名称; BindObject注册时,所有的路由都是对象方法名称按照规则生成的,第三个methods参数可以指定多个注册的方法名称。
RESTful对象注册 构造方法Init与析构方法Shut
对象中的Init和Shut是两个在HTTP请求流程中被Server自动调用的特殊方法(类似构造函数和析构函数的作用)。
分组路由
层级注册
中间件/拦截器
前置中间件
后置中间件
请求输入
提交方式
GF框架下,有以下几种提交类型: Router: 路由参数,来源于路由规则匹配。
Query: URL中的Query String参数解析,如:http://127.0.0.1/index?id=1&name=john 中的id=1&name=john。
Form: 表单提交参数,最常见的提交方式,提交的Content-Type往往为:application/x-www-form-urlencoded、multipart/form-data、multipart/mixed。
Body: 原始提交内容,从Body中获取并解析得到的参数,JSON/XML请求往往使用这种方式提交。
Custom: 自定义参数,往往在服务端的中间件、服务函数中通过SetParam/GetParam方法管理。
参数类型
获取的参数方法可以对指定键名的数据进行自动类型转换,例如:http://127.0.0.1:8199/?amount=19.66,通过GetQueryString将会返回19.66的字符串类型,GetQueryFloat32/GetQueryFloat64将会分别返回float32和float64类型的数值19.66
如果有更多参数类型转换的需求,可以使用Get*Var参数获取方法,获得*gvar.Var变量再进行相应类型转换。例如,假如我们要获取一个int8类型的参数,我们可以这样GetVar(“id”).Int8()。
参数优先级
Get*及GetRequset*方法:Router < Query < Body < Form < Custom,也就是说自定义参数的优先级最高,其次是Form表单参数,再次是Body提交参数,以此类推。例如,Query和Form中都提交了同样名称的参数id,参数值分别为1和2,那么Get(“id”)/GetForm(“id”)将会返回2,而GetQuery(“id”)将会返回1。
GetQuery*方法:Query > Body,也就是说query string的参数将会覆盖Body中提交的同名参数。例如,Query和Body中都提交了同样名称的参数id,参数值分别为1和2,那么Get(“id”)将会返回2,而GetQuery(“id”)将会返回1。
GetForm*方法:由于该类型的方法仅用于获取Form表单参数,因此没什么优先级的差别。
数据返回
Write*方法用于数据的输出,可为任意的数据格式,内部通过断言对参数做自动分析。
Write*Exit方法用于数据输出后退出当前服务方法,可用于替代return返回方法。
WriteJson*/WriteXml方法用于特定数据格式的输出,这是为开发者提供的简便方法。
WriteTpl*方法用于模板输出,解析并输出模板文件,也可以直接解析并输出给定的模板内容。
ParseTpl*方法用于模板解析,解析模板文件或者模板内容,返回解析后的内容。
缓冲控制
Response输出采用了缓冲控制,输出的内容预先写入到一块缓冲区,等待服务方法执行完毕后才真正地输出到客户端。该特性在提高执行效率同时为输出内容的控制提供了更高的灵活性。
举个例子:
我们通过后置中间件统一对返回的数据做处理,如果服务方法产生了异常,那么不能将敏感错误信息推送到客户端,而统一设置错误提示信息。
func MiddlewareErrorHandler(r *ghttp.Request) {
r.Middleware.Next()
if r.Response.Status >= http.StatusInternalServerError {
r.Response.ClearBuffer()
r.Response.Writeln("服务器居然开小差了,请稍后再试吧!")
}
}
模板解析
Response支持模板文件/内容解析输出,或者模板文件/内容解析返回。与直接使用模板对象解析模板功能不同的是,Response的解析支持一些请求相关的内置变量。模板解析包含以下方法:
WriteTpl*方法用于模板输出,解析并输出模板文件,也可以直接解析并输出给定的模板内容。
ParseTpl*方法用于模板解析,解析模板文件或者模板内容,返回解析后的内容。
s.BindHandler("/", func(r *ghttp.Request){
r.Cookie.Set("theme", "default")
r.Session.Set("name", "john")
content :=`Config:{{.Config.redis.cache}}, Cookie:{{.Cookie.theme}}, Session:{{.Session.name}}, Query:{{.Query.name}}`
r.Response.WriteTplContent(content, nil)
})JSON/XML
WriteJson* 方法用于返回JSON数据格式,参数为任意类型,可以为string、map、struct等等。返回的Content-Type为application/json。
WriteXml* 方法用于返回XML数据格式,参数为任意类型,可以为string、map、struct等等。返回的Content-Type为application/xml。
group.ALL("/json", func(r *ghttp.Request) {
r.Response.WriteJson(g.Map{
"id": 1,
"name": "john",
})
})Cookie
s.BindHandler("/cookie", func(r *ghttp.Request) {
datetime := r.Cookie.Get("datetime")
r.Cookie.Set("datetime", gtime.Datetime())
r.Response.Write("datetime:", datetime)
})Session
任何时候都可以通过ghttp.Request获取Session对象,因为Cookie和Session都是和请求会话相关,因此都属于Request的成员对象,并对外公开。GF框架的Session默认过期时间是24小时。
SessionId默认通过Cookie来传递,并且也支持客户端通过Header传递SessionId,SessionId的识别名称可以通过ghttp.Server的SetSessionIdName进行修改。
Session的操作是支持并发安全的,这也是框架在对Session的设计上不采用直接以map的形式操作数据的原因。在HTTP请求流程中,我们可以通过ghttp.Request对象来获取Session对象,并执行相应的数据操作。
ghttp.Server中的SessionId使用的是客户端的 RemoteAddr + Header 请求信息通过guid模块来生成的,保证随机及唯一性:https://github.com/gogf/ gf/blob/master/net/ghttp/ghttp_request.go
Session-文件存储
Session-内存存储
Session-Redis存储
s.SetConfigWithMap(g.Map{
"SessionMaxAge": time.Minute,
"SessionStorage": gsession.NewStorageMemory(),内存// 空,文件,"SessionStorage": gsession.NewStorageRedis(g.Redis()), redis
})服务配置(略)
异常处理
我们这里使用一个全局的后置中间件来捕获异常,当异常产生后,捕获并写入到指定的日志文件中,返回固定友好的提示信息,避免敏感的报错信息暴露给调用端。
func MiddlewareErrorHandler(r *ghttp.Request) {
r.Middleware.Next()
if err := r.GetError(); err != nil {
// 记录到自定义错误日志文件
g.Log("exception").Error(err)
//返回固定的友好信息
r.Response.ClearBuffer()
r.Response.Writeln("服务器居然开小差了,请稍后再试吧!")
}
}HTTPClient(略)
gf-jwt说明
// 重新定义GfJWTMiddleware
authMiddleware, err := jwt.New(&jwt.GfJWTMiddleware{
Realm: "test zone", // 用于展示中间件的名称
Key: []byte("secret key"), // 密钥
Timeout: time.Minute * 5, // token过期时间
MaxRefresh: time.Minute * 5,
IdentityKey: "id", // 身份验证的key值
TokenLookup: "header: Authorization, query: token, cookie: jwt", // token检索模式,用于提取token-> Authorization
TokenHeadName: "Bearer", // token在请求头时的名称,默认值为Bearer
// 客户端在header中传入Authorization 对一个值是Bearer + 空格 + token
TimeFunc: time.Now, // 测试或服务器在其他时区可设置该属性
Authenticator: Authenticator, // 根据登录信息对用户进行身份验证的回调函数
LoginResponse: LoginResponse, // 完成登录后返回的信息,用户可自定义返回数据,默认返回
RefreshResponse: auth.RefreshResponse, // 刷新token后返回的信息,用户可自定义返回数据,默认返回
Unauthorized: auth.Unauthorized, // 处理不进行授权的逻辑
IdentityHandler: auth.IdentityHandler, // 解析并设置用户身份信息,使得r.get('JWT_PAYLOAD")可取的
PayloadFunc: auth.PayloadFunc, // 登录期间的回调的函数
})整体调用链如下 
有了上图就大致清楚,我们希望校验token时,做某些操作(比如查询刷新菜单权限等),就是在IdentityHandler中修改了。
用户名密码登录时,记录登录日志,在Authenticator里更合适。