frame of golang 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

Application

application一般都是框架的主体,通常XXX框架的主体叫做XXX,当然也有叫App、Application、Server的实现,具体情况不你一致,不过一般都是叫XXX,源码就是XXX.go。

这部分一般都会实现两个方法Start() errorServeHTTP(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等对象。

简单实现:

Router简单实现


type Application struct {
    mux        *Router
    pool    sync.Pool
}

func NewApplication() *Application{
    return &Application{
        mux:    new(Router),
        pool:    sync.Pool{
            New:    func() interface{} {
                return &Context{}
            },
        }
    }
}


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)
}


func (app *Application) Start(addr string) error {
    
    return http.Server{
        Addr:        addr,
        Handler:    app,
    }.ListenAndServe()
}


func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    
    ctx := app.pool.Get().(*Context)
    
    ctx.Reset(w, r)    
    
    app.router.Match(r.Method, r.URL.Path)(ctx)
    
    app.pool.Put(ctx)
}

Context

Context包含Request和Response两部分,Request部分是请求,Response是响应。Context的各种方法基本都是围绕Request和Response来实现来,通常就是各种请求信息的获取和写入的封装。

简单实现:


type Context struct {
    http.ResponseWriter
    req *http.Request
}


func (ctx *Context) Reset(w http.ResponseWriter, r *http.Request) {
    ctx.ResponseWriter, ctx.req = w, r
}


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
}


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
}


func Handle405(ctx Context) {
    ctx.Response().WriteHeader(405)
    ctx.Response().Header().Add("Allow", "GET, POST, HEAD")
}



func Handle404(ctx Context) {
    ctx.Response().WriteHeader(404)
}

这个简单路由仅支持了rsetful风格,连通配符匹配都没有实现;但是体现了路由器的作用,输出一个参数,返回一个对应的处理者。

至于如何对应一个处理路由,就是路由器规则的设定了;例如通配符、参数、正则、数据校验等功能。

Middleware

通常是多个Handler函数组合,在handler之前之后增一些处理函数。

echo

type MiddlewareFunc func(HandlerFunc) HandlerFunc


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)


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



type Handler func(Context)




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                            
        Set(key, value interface{}) error    
        Get(key interface{}) interface{}    
        Del(key interface{}) error            
        Release(w http.ResponseWriter)        
    }
    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

源码github

使用例子:

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

协议见文档

Last updated

Was this helpful?