适用框架:golf、echo、gin、dotweb、iris、beego。html
本文章未彻底完成,指定部分框架的主要结构mysql
golang大部分框架都是基于标准库net/http包现实,fasthttp框架就是本身解析http协议,重新实现了相似net/http包的功能。git
一般框架包含的部分有Application、Context、Request、Response、Router、Middleware、Logger、Binder、render、View、Session、Cache这些部分,通常都是有部分,不过前五个是必定存在。github
如下列出了各框架主要部分定义位置:golang
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 | session.go | |
Cache | cache.go | cache.go | cache.go | |||
Websocket | websocket.go | server.go | ||||
MVC | controller.go | controller.go |
源码解析:golfweb
application通常都是框架的主体,一般XXX框架的主体叫作XXX,固然也有叫App、Application、Server的实现,具体状况不你一致,不过通常都是叫XXX,源码就是XXX.go。redis
这部分通常都会实现两个方法Start() error
和ServeHTTP(ResponseWriter, *Request)
sql
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包含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
}
复制代码
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是请求匹配的路由,并不复杂,可是每一个框架都是必备的。
一般实现两个方法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风格,连通配符匹配都没有实现;可是体现了路由器的做用,输出一个参数,返回一个对应的处理者。
至于如何对应一个处理路由,就是路由器规则的设定了;例如通配符、参数、正则、数据校验等功能。
一般是多个Handler函数组合,在handler以前以后增一些处理函数。
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在路由注册的会中间件和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,
dotweb Middleware接口中Exclude()、HasExclude()、ExistsExcludeRouter()用来排除路由级调用。
BaseMiddlware通常是Middleware的基础类,一般只重写Handle()方法,同时能够调用Next()方法执行写一个Middleware。
在Next()方法中,若是next为空就调用Context的RouterNode()方法,得到RouterNode对象,而后使用AppMiddlewares()方法读取app级别[]Middleware,对其中第一个Middleware调用Handle()就会开始执行app级别Middleware。
同级多个Middleware是单链的形式存储,那么只有最后一个Middleware的next为空,若是执行到这个Middleware,那么表示这一级Middleware所有处理完毕,就须要Context的middleware级别降级,而后开始执行下一级。
ServerHttp()时,调用
//middleware执行优先级:
//优先级1:app级别middleware
//优先级2:group级别middleware
//优先级3:router级别middleware
// Middleware middleware interface
type Middleware interface {
Handle(ctx Context) error
SetNext(m Middleware)
Next(ctx Context) error
Exclude(routers ...string)
HasExclude() bool
ExistsExcludeRouter(router string) bool
}
type RouterNode interface {
Use(m ...Middleware) *Node
AppMiddlewares() []Middleware
GroupMiddlewares() []Middleware
Middlewares() []Middleware
Node() *Node
}
// Use registers a middleware
func (app *DotWeb) Use(m ...Middleware) {
step := len(app.Middlewares) - 1
for i := range m {
if m[i] != nil {
if step >= 0 {
app.Middlewares[step].SetNext(m[i])
}
app.Middlewares = append(app.Middlewares, m[i])
step++
}
}
}
func (bm *BaseMiddlware) Next(ctx Context) error {
httpCtx := ctx.(*HttpContext)
if httpCtx.middlewareStep == "" {
httpCtx.middlewareStep = middleware_App
}
if bm.next == nil {
if httpCtx.middlewareStep == middleware_App {
httpCtx.middlewareStep = middleware_Group
if len(httpCtx.RouterNode().GroupMiddlewares()) > 0 {
return httpCtx.RouterNode().GroupMiddlewares()[0].Handle(ctx)
}
}
if httpCtx.middlewareStep == middleware_Group {
httpCtx.middlewareStep = middleware_Router
if len(httpCtx.RouterNode().Middlewares()) > 0 {
return httpCtx.RouterNode().Middlewares()[0].Handle(ctx)
}
}
if httpCtx.middlewareStep == middleware_Router {
return httpCtx.Handler()(ctx)
}
} else {
//check exclude config
if ctx.RouterNode().Node().hasExcludeMiddleware && bm.next.HasExclude() {
if bm.next.ExistsExcludeRouter(ctx.RouterNode().Node().fullPath) {
return bm.next.Next(ctx)
}
}
return bm.next.Handle(ctx)
}
return nil
}
func (x *xMiddleware) Handle(ctx Context) error {
httpCtx := ctx.(*HttpContext)
if httpCtx.middlewareStep == "" {
httpCtx.middlewareStep = middleware_App
}
if x.IsEnd {
return httpCtx.Handler()(ctx)
}
return x.Next(ctx)
}
复制代码
// 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 复制代码
框架基础日志接口
type Logger interface {
Debug(...interface{})
Info(...interface{})
Warning(...interface{})
Error(...interface{})
Fatal(...interface{})
WithField(key string, value interface{}) Logger
WithFields(fields Fields) Logger
}
复制代码
Binder的做用以各类格式方法解析Request请求数据,而后赋值给一个interface{}。
Render的做用和Binder相反,会把数据安装选择的格式序列化,而后写回给Response部分。
View和Render的区别是Render输入的数据,View是使用对应的模板渲染引擎渲染html页面。
seesion是一种服务端数据存储方案
Cache持久化就须要把数据存到其余地方,避免程序关闭时缓存丢失。
存储介质通常各类DB、file等均可以,实现通常都是下面几步操做。
一、getid:从请求读取sessionid。
二、initSession:用sessionid再存储中取得对应的数据,通常底层就是[]byte
三、newSession:反序列化成一个map[string]interface{}这样相似结构Session对象。
四、Set and Get:用户对Session对象各类读写操做。
五、Release:将Session对象序列化成[]byte,而后写会到存储。
内存:使用简单,供测试使用,没法持久化。
文件:存储简单。
sqlite:和文件差很少,使用sql方式操做。
mysql等:数据共享、数据库持久化。
redis:数据共享、协议对缓存支持好。
memcache:协议简单、方法少、效率好。
etcd:少许数据缓存,可用性高。
一组核心的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,而后再回写到存储之中,保存新修改的数据。
使用例子:
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。
一、SessionStart定义在session.go#L193,用户获取Store对象,在194line获取sessionid,在200line用sid从存储读取sessio.Store对象。
二、memcache存储在[124line][4]定义SessionRead函数来初始化对象。
三、其中在[sess_memcache.go#L139][5]使用memcache获取的数据,使用gob编码反序列化生成了map[interface{}]interface{}类型的值kv,而后144line把kv赋值给了store。
四、在5七、65 [set&get][6]操做的rs.values,就是第三部序列化生成的对象。
五、最后释放store对象,定义在[94line][7],先把rs.values使用gob编码序列化成[]byte,而后使用memcache的set方法存储到memcache里面了,完成了持久化存储。
源码分析总结:
session在多线程读写是不安全的,数据可能冲突,init和release中间不要有耗时操做,参考其余思路同样,暂无解决方案,实现读写对存储压力大。
对session只读就不用释放session,只读释放是无效操做,由于set值和原值同样。
beego.session能够适配一个beego.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存储实现接口便可,这样就能够实现共享缓存和持久化存储。
协议见文档