4.2. 基于net/http框架个部分总结
基于net/http框架个部分总结
适用框架:golf、echo、gin、dotweb、iris、beego。
golang大部分框架都是基于标准库net/http包现实,fasthttp框架就是自己解析http协议,从新实现了类似net/http包的功能。
通常框架包含的部分有Application、Context、Request、Response、Router、Middleware、Logger、Binder、Render、View、Session、Cache这些部分,一般都是有部分,不过前五个是一定存在。
以下列出了各框架主要部分定义位置:
golf | echo | gin | dotweb | iris | beego | |
---|---|---|---|---|---|---|
Application | app.go | echo.go | gin.go | dotweb.go | iris.go | app.go |
Context | context.go | context.go | context.go | context.go | context.go | context.go |
Request | http.Request | http.Request | http.Request | request.go | http.Request | input.go |
Response | http.ResponseWriter | response.go | response_writer_1.8.go | response.go | response_writer.go | output.go |
Router | router.go | router.go | routergroup.go | router.go | router.go | router.go |
Middleware | middleware.go | echo.go | gin.go | middleware.go | handler.go | app.go |
Logger | log.go | logger.go | logger.go | log.go | ||
Binder | bind.go | binding.go | bind.go | |||
Render | render.go | render.go | ||||
View | view.go | engine.go | ||||
Session | session.go | session.go | session.go | session.go | ||
Cache | cache.go | cache.go | cache.go | |||
Websocket | websocket.go | server.go | ||||
MVC | controller.go | controller.go |
源码解析:golf
Application
application一般都是框架的主体,通常XXX框架的主体叫做XXX,当然也有叫App、Application、Server的实现,具体情况不你一致,不过一般都是叫XXX,源码就是XXX.go。
这部分一般都会实现两个方法Start() error
和ServeHTTP(ResponseWriter, *Request)
Start() error
一般是框架的启动函数,用来启动服务,名称可能会是Run,创建一个http.Server对象,设置TLS相关等配置,然后启动服务,当然也会出现Shutdown方法。
ServeHTTP(ResponseWriter, *Request)
函数实现http.Handler接口,一般框架都是使用http.Server对象来启动的服务,所以需要实现此方法。
此本方法大概就三步,Init、Handle、Release。
第一步Init,一般就是初始化Context对象,其中包括Request和Response的初始化Reset,使用ResponseWriter
和*Request
对象来初始化,通常会使用Sync.Pool来回收释放减少GC。
第二步Handle,通常就是框架处理请求,其中一定包含路由处理,使用路由匹配出对应的Handler来处理当前请求。
第三步释放Context等对象。
简单实现:
// 定义Application
type Application struct {
mux *Router
pool sync.Pool
}
func NewApplication() *Application{
return &Application{
mux: new(Router),
pool: sync.Pool{
New: func() interface{} {
return &Context{}
},
}
}
}
// 注册一个GET请求方法,其他类型
func (app *Application) Get(path string, handle HandleFunc) {
app.RegisterFunc("GET", path, handle)
}
// 调用路由注册一个请求
func (app *Application) RegisterFunc(method string, path string, handle HandleFunc) {
app.router.RegisterFunc(method, path, handle)
}
// 启动Application
func (app *Application) Start(addr string) error {
// 创建一个http.Server并启动
return http.Server{
Addr: addr,
Handler: app,
}.ListenAndServe()
}
// 实现http.Handler接口,并出去net/http请求。
func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 创建一个ctx对象
ctx := app.pool.Get().(*Context)
// 初始化ctx
ctx.Reset(w, r)
// 路由器匹配请求并处理ctx
app.router.Match(r.Method, r.URL.Path)(ctx)
// 回收ctx
app.pool.Put(ctx)
}
Context
Context包含Request和Response两部分,Request部分是请求,Response是响应。Context的各种方法基本都是围绕Request和Response来实现来,通常就是各种请求信息的获取和写入的封装。
简单实现:
// Context简单实现使用结构体,不使用接口,如有其他需要可继续增加方法。
type Context struct {
http.ResponseWriter
req *http.Request
}
// 初始化ctx
func (ctx *Context) Reset(w http.ResponseWriter, r *http.Request) {
ctx.ResponseWriter, ctx.req = w, r
}
// Context实现获取方法
func (ctx *Context) Method() string {
return ctx.req.Method
}
RequestReader & ResponseWriter
http协议解析文档。
实现RequestReader和ResponseWriter接口。
根据http协议请求报文和响应报文RequestReader和ResponseWriter大概定义如下:
type (
Header map[string][]string
RequestReader interface {
Method() string
RequestURI() string
Proto() string
Header() Header
Read([]byte) (int, error)
}
ResponseWriter interface {
WriteHeader(int)
Header() Header
Write([]byte) (int, error)
}
)
RequestReader用于读取http协议请求的请求行(Request Line)、请求头(Request Header)、body。
ResponseWriter用于返回http写法响应的状态行(Statue Line)、响应头(Response Header)、body这些数据。
在实际过程还会加入net.Conn对象的tcp连接信息。
通常net/http库下的RequestReader和ResponseWriter定义为http.Request和http.ResponseWriter,请求是一个结构体,拥有请求信息,不同情况下可能会有不同封装,或者直接使用net/http定义的读写对象。
Router
Router是请求匹配的路由,并不复杂,但是每个框架都是必备的。
通常实现两个方法Match和RegisterFunc,给路由器注册新路由,匹配一个请求的路由,然后处理请求。
type (
HandleFunc func(*Context)
Router interface{
Match(string, string) HandleFunc
RegisterFunc(string, string, HandleFunc)
}
)
定义一个非常非常简单的路由器。
type Router struct {
Routes map[string]map[string]HandleFunc
}
// 匹配一个Context的请求
func (r *Router) Match(path ,method string) HandleFunc {
// 查找方法定义的路由
rs, ok := r.Routes[method]
if !ok {
return Handle405
}
// 查找路由
h, ok := rs[path]
if !ok {
return Handle404
}
return h
}
// 注册路由处理函数
func (r *Router) RegisterFunc(method string, path string, handle HandleFunc) {
rs, ok := r.Routes[ctx.Method()]
if !ok {
rs = make(map[string]HandleFunc)
r.Routes[ctx.Method()] = rs
}
rs[path] = handle
}
// 处理405响应,方法不允许;Allow Header返回允许的方法。
func Handle405(ctx Context) {
ctx.Response().WriteHeader(405)
ctx.Response().Header().Add("Allow", "GET, POST, HEAD")
}
// 处理404响应,没有找到对应的资源。
func Handle404(ctx Context) {
ctx.Response().WriteHeader(404)
}
这个简单路由仅支持了rsetful风格,连通配符匹配都没有实现;但是体现了路由器的作用,输出一个参数,返回一个对应的处理者。
至于如何对应一个处理路由,就是路由器规则的设定了;例如通配符、参数、正则、数据校验等功能。
Middleware
通常是多个Handler函数组合,在handler之前之后增一些处理函数。
echo
type MiddlewareFunc func(HandlerFunc) HandlerFunc
// echo.ServeHTTP
h := NotFoundHandler
for i := len(e.premiddleware) - 1; i >= 0; i-- {
h = e.premiddleware[i](h)
}
if err := h(c); err != nil {
e.HTTPErrorHandler(err, c)
}
echo中间件使用装饰器模式。
echo中间件使用HandlerFunc进行一层层装饰,最后返回一个HandlerFunc处理Context
gin
gin在路由注册的会中间件和route合并成一个handlers对象,然后httprouter返回匹配返回handlrs,在context reset时设置ctx的handlers为路由匹配出现的,handlers是一个HanderFunc数组,Next方法执行下一个索引的HandlerFunc,如果在一个HandlerFunc中使用ctx.Next()就先将后续的HandlerFunc执行,后续执行完才会继续那个HandlerFunc,调用ctx.End() 执行索引直接修改为最大值,应该是64以上,毕竟Handlers合并时的数据长度限制是64,执行索引成最大值了,那么后面就没有HandlerFunc,就完整了一次ctx的处理。
type HandlerFunc func(*Context)
// https://github.com/gin-gonic/gin/blob/master/context.go#L105
func (c *Context) Next() {
c.index++
for s := int8(len(c.handlers)); c.index < s; c.index++ {
c.handlers[c.index](c)
}
}
echo通过路由匹配返回一个[]HandlerFunc对象,并保存到Context里面,执行时按ctx.index一个个执行。
如果HandlerFunc里面调用ctx.Next(),就会提前将后序HandlerFunc执行,返回执行ctx.Next()后的内容,可以简单的指定调用顺序,ctx.Next()之前的在Handler前执行,ctx.Next()之后的在Handler后执行。
Context.handlers存储本次请求所有HandlerFunc,然后使用c.index标记当然处理中HandlerFunc,
iris
// If Handler panics, the server (the caller of Handler) assumes that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log, and hangs up the connection.
type Handler func(Context)
// Handlers is just a type of slice of []Handler.
//
// See `Handler` for more.
type Handlers []Handler
Beego
func (app *App) Run(mws ...MiddleWare)
func (p *ControllerRegister) InsertFilter(pattern string, pos int, filter FilterFunc, params ...bool) error
func NSBefore(filterList ...FilterFunc) LinkNamespace
func NSAfter(filterList ...FilterFunc) LinkNamespace
Logger
框架基础日志接口
type Logger interface {
Debug(...interface{})
Info(...interface{})
Warning(...interface{})
Error(...interface{})
Fatal(...interface{})
WithField(key string, value interface{}) Logger
WithFields(fields Fields) Logger
}
Binder & Render & View
Binder的作用以各种格式方法解析Request请求数据,然后赋值给一个interface{}。
Render的作用和Binder相反,会把数据安装选择的格式序列化,然后写回给Response部分。
View和Render的区别是Render输入的数据,View是使用对应的模板渲染引擎渲染html页面。
Session & Cache
seesion是一种服务端数据存储方案
持久化
Cache持久化就需要把数据存到其他地方,避免程序关闭时缓存丢失。
存储介质一般各种DB、file等都可以,实现一般都是下面几步操作。
1、getid:从请求读取sessionid。
2、initSession:用sessionid再存储中取得对应的数据,一般底层就是[]byte
3、newSession:反序列化成一个map[string]interface{}这样类似结构Session对象。
4、Set and Get:用户对Session对象各种读写操作。
5、Release:将Session对象序列化成[]byte,然后写会到存储。
存储介质
-
内存:使用简单,供测试使用,无法持久化。
-
文件:存储简单。
-
sqlite:和文件差不多,使用sql方式操作。
-
mysql等:数据共享、数据库持久化。
-
redis:数据共享、协议对缓存支持好。
-
memcache:协议简单、方法少、效率好。
-
etcd:少量数据缓存,可用性高。
golang session
一组核心的session接口定义。
type (
Session interface {
ID() string // back current sessionID
Set(key, value interface{}) error // set session value
Get(key interface{}) interface{} // get session value
Del(key interface{}) error // delete session value
Release(w http.ResponseWriter) // release value, save seesion to store
}
type Provider interface {
SessionRead(sid string) (Session, error)
}
)
Provider.SessionRead(sid string) (Session, error)
用sid来从Provider读取一个Session返回,sid就是sessionid一般存储与cookie中,也可以使用url参数值,Session对象会更具sid从对应存储中读取数据,然后将数据反序列化来初始化Session对象。
Session.Release(w http.ResponseWriter)
从名称上是释放这个Seesion,但是一般实际作用是将对应Session对象序列化,然后存储到对应的存储实现中,如果只是读取Session可以不Release。
简单的Seession实现可以使用一个map,那么你的操作就是操作这个map。
在初始化Session对象的时候,使用sessionId去存储里面取数据,数据不在内存中,你们通常不是map,比较常用的是[]byte,例如memcache就是[]byte,[]byte可以map之间就需要序列化和反序列化了。
在初始化时,从存储读取[]map,反序列化成一个map,然后返回给用户操作;最后释放Session对象时,就要将map序列化成[]byte,然后再回写到存储之中,保存新修改的数据。
Beego.Seesion
使用例子:
func login(w http.ResponseWriter, r *http.Request) {
sess, _ := globalSessions.SessionStart(w, r)
defer sess.SessionRelease(w)
username := sess.Get("username")
if r.Method == "GET" {
t, _ := template.ParseFiles("login.gtpl")
t.Execute(w, nil)
} else {
sess.Set("username", r.Form["username"])
}
}
在beego.session中Store就是一个Session。
1、SessionStart定义在session.go#L193,用户获取Store对象,在194line获取sessionid,在200line用sid从存储读取sessio.Store对象。
2、memcache存储在[124line][4]定义SessionRead函数来初始化对象。
3、其中在[sess_memcache.go#L139][5]使用memcache获取的数据,使用gob编码反序列化生成了map[interface{}]interface{}类型的值kv,然后144line把kv赋值给了store。
4、在57、65 [set&get][6]操作的rs.values,就是第三部序列化生成的对象。
5、最后释放store对象,定义在[94line][7],先把rs.values使用gob编码序列化成[]byte,然后使用memcache的set方法存储到memcache里面了,完成了持久化存储。
源码分析总结:
-
session在多线程读写是不安全的,数据可能冲突,init和release中间不要有耗时操作,参考其他思路一样,暂无解决方案,实现读写对存储压力大。
-
对session只读就不用释放session,只读释放是无效操作,因为set值和原值一样。
-
beego.session可以适配一个beego.cache的后端,实现模块复用,不过也多封装了一层。
golang cache
实现Get and Set接口的一种实现。
简单实现
type Cache interface {
Delete(key interface{})
Load(key interface{}) (value interface{}, ok bool)
Store(key, value interface{})
}
这组接口[sync.Map][]简化出来的,这种简单的实现了get&set操作,数据存储于内存中,map类型也可以直接实现存储。
复杂实现
封装各种DB存储实现接口即可,这样就可以实现共享缓存和持久化存储。
Websocket
协议见文档
反馈和交流请加群组:QQ群373278915。
推荐文章: