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。
          
golang http of eudore
            
            
                关于 LearnKu
              
                    
                    
                    
 
推荐文章: