適用框架: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存儲實現接口便可,這樣就能夠實現共享緩存和持久化存儲。
協議見文檔