Go Gin源碼學習(二) 主流程模擬

開場

上一篇學習了Gin框架的整體流程,可是本身查看源代碼中被許多的零散小功能影響了主體流程的研究。因此以爲能夠模仿Gin框架,本身寫一個最簡單僅僅含有主流程的demo。可讓咱們更加深刻了解Gin本身也能夠再模仿的過程當中更加了解源碼。app

功能

這個demo是我本身對Gin初步理解以後模仿寫的,其中只包含主要功能框架

  • 建立路由
  • 新建group
  • 添加中間件
  • 啓動http服務,接受客戶端請求並返回簡單數據

Demo中的路由並無使用Gin的tree只是簡單的使用了map來實現,基數樹是一個相對獨立邏輯準備以後單獨再學習函數

代碼

package mygin

import (
    "fmt"
    "net/http"
    "path"
    "sync"
)

//上下文context 簡單的他添加response request engine指針 isabort就能夠支持最簡單的流程
type Context struct {
    Request *http.Request
    ResponseWrite http.ResponseWriter
    engine *Engine
    isAbort bool
}

type HandlerFun func(ctx *Context)

type HandlerList []HandlerFun

type Engine struct {
    RouterGroup
    Handlers []HandlerFun
    router map[string]HandlerList
    pool sync.Pool
}

type Message struct {
    Message string
}

type IRouter interface {
    Use(...HandlerFun) IRouter
    GET(string, ...HandlerFun) IRouter
    Group(string, ...HandlerFun) *RouterGroup
}

type RouterGroup struct {
    Handlers []HandlerFun
    engine *Engine
    basePath string
}

func NewEngine()(*Engine){
    en := new(Engine)
    en.router = make(map[string]HandlerList)
    en.pool.New = func() interface{} {
        return en.allocateContext()
    }
    en.RouterGroup = RouterGroup{
        basePath:"/",
        Handlers:nil,
        engine:en,
    }

    return en
}

func (engine *Engine)Run(addr string)(err error){
    fmt.Println("Listening and serving HTTP on", addr)
    err = http.ListenAndServe(addr, engine)
    return
}

//繼承http包中的handler接口,在run中便可傳入engine
func (engine *Engine)ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)
    c.ResponseWrite = w
    c.Request = req
    engine.handleHTTPRequest(c)

    engine.pool.Put(c)
}

//客戶端請求以後具體執行的函數 以前文章所說的獲取全部handler 一個個執行
// 這裏簡單用了for循環 判斷isabort屬性來判斷是否中止
func (engine *Engine) handleHTTPRequest(c *Context){
    httpMethod := c.Request.Method
    path := c.Request.URL.Path

    if handlers,ok := engine.router[httpMethod + "^" + path];ok{
        for _,fu := range handlers{
            fu(c)
            if c.isAbort{
                return
            }
        }
    }
}

func (engine *Engine) allocateContext() *Context{
    return &Context{engine:engine}
}

func (engine *Engine)addRoute(httpMethod, absolutePath string, handlers HandlerList){
    engine.router[httpMethod + "^" + absolutePath] = handlers
}

//添加group方法 設置group的basepath 和handler
func (routerGroup *RouterGroup)Group(path string,handlers ...HandlerFun) *RouterGroup{
    rg := RouterGroup{}
    rg.Handlers = routerGroup.CombineHandlers(handlers)
    rg.basePath = path
    rg.engine = routerGroup.engine

    return &rg
}

func (routerGroup *RouterGroup)Use(handlers ...HandlerFun) IRouter{
    routerGroup.Handlers = append(routerGroup.Handlers, handlers...)
    return routerGroup
}

func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
    return joinPaths(group.basePath, relativePath)
}

func joinPaths(absolutePath, relativePath string) string {
    if relativePath == ""{
        return absolutePath
    }

    finalPath := path.Join(absolutePath,relativePath)

    appendSlash := lastChar(relativePath) == '/' && lastChar(finalPath)!='/'
    if appendSlash{
        return finalPath + "/"
    }

    return finalPath
}

//工具方法 獲取字符串最後一個字符
func lastChar(str string) uint8 {
    if str ==""{
        panic("The length of the string can't be 0")
    }

    return str[len(str)-1]
}

//計算路徑合併handler 而後添加到map中
func (group *RouterGroup)handle(httpMethod, relativePath string, handlers HandlerList) IRouter{
    absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.CombineHandlers(handlers)
    group.engine.addRoute(httpMethod, absolutePath, handlers)

    return group
}

//合併handler 以後返回
func (group *RouterGroup)CombineHandlers(handlers HandlerList)HandlerList{
    finalSize := len(group.Handlers) + len(handlers)
    mergedHandler := make(HandlerList, finalSize)
    copy(mergedHandler, group.Handlers)
    copy(mergedHandler[len(group.Handlers):], handlers)
    return mergedHandler
}

//添加get method路由
func (group *RouterGroup)GET(path string, handlers ...HandlerFun)(IRouter){
    group.handle("GET", path, handlers)

    return group
}

測試

func TestEngine_Run(t *testing.T) {
    router := NewEngine()
    router.GET("/test", func(ctx *Context) {
        fmt.Println("get request")
        //這邊能夠嘗試拿一下參數 在gin中獲取參數提供了不少的方法
        //這些不是主流程就沒有在這裏體現 有興趣能夠看一下源碼其實也沒有想象中的複雜
        //這邊就先獲取一下get參數
        pm := ctx.Request.URL.Query()
        if v,ok := pm["id"];ok{
            fmt.Println("request url", ctx.Request.URL.String()," parameter id value =",v)
        }
        ctx.ResponseWrite.WriteHeader(200)
        r := render.JSON{Data:"success"}
        r.WriteContentType(ctx.ResponseWrite)

        if err := r.Render(ctx.ResponseWrite); err != nil{
            panic(err)
        }
    })
    router.Run(":2222")
}

結果工具

//在console中,獲得了客戶端的請求並打印參數
Listening and serving HTTP on :2222
get request
request url /test?id=2  parameter id value = [2]
//客戶端請求以後獲取到了 success 的返回值
http://localhost:2222/test?id=2
"success"

總結

這個demo大概100多行代碼,僅僅實現了Gin最最小的功能。還有大部分的以前提到的功能都未實現,可是隻是這100多行代碼就已經能看出Gin的主要流程和主體思路。學習

相關文章
相關標籤/搜索