通過(guò)項(xiàng)目學(xué)習(xí)Go語(yǔ)言之 gin簡(jiǎn)析

gin是go語(yǔ)言實(shí)現(xiàn)的一個(gè)http web框架,是一個(gè)類Martini的API且比其執(zhí)行快40倍闯袒,gin支持插件模式加載在項(xiàng)目中需要的功能磨确。在使用gin框架構(gòu)建項(xiàng)目的時(shí)候,我們使用操作最多的就是request屯换、response,gin為了方便使用設(shè)計(jì)了巧妙的context与学。
下面我們看一個(gè)簡(jiǎn)單的基于gin構(gòu)建的輸出json數(shù)據(jù)的接口彤悔。

func main() {

    r := gin.Default()

    r.GET("/ping", func(c *gin.Context) {

        c.JSON(200, gin.H{

            "message": "pong",
        })

    })
    fmt.Printf("come on")
    r.Run() // listen and serve on 0.0.0.0:8080 

}

運(yùn)行后,通過(guò)瀏覽器訪問(wèn)127.0.0.1/ping,我們看到輸出

{
message: "pong"
}

使用gin構(gòu)建api服務(wù)就是如此簡(jiǎn)單索守。下面我們剖析一下gin的重要組成和一個(gè)http請(qǐng)求到達(dá)晕窑,解析到輸出結(jié)果的整個(gè)流程。

Engine

從官方給出的例子的第一行代碼說(shuō)開(kāi)來(lái):r := gin.Default()
通過(guò)這一行代碼卵佛,我們初始換了一個(gè)Engine杨赤,Engine是整個(gè)gin的核心入口,承擔(dān)著路由截汪、中間件疾牲、參數(shù)等設(shè)置,是對(duì)http server的封裝衙解。
通過(guò)gin.Default(),初始化一個(gè)默認(rèn)的engine實(shí)例阳柔,它包含了日志和錯(cuò)誤自恢復(fù)插件。

// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
    debugPrintWARNINGDefault()
    engine := New()
//加載兩個(gè)插件
    engine.Use(Logger(), Recovery())
    return engine
}

在Engine默認(rèn)初始化方面中蚓峦,調(diào)用的New()方法盔沫,我看看一下這個(gè)方法做了哪些工作:

// New returns a new blank Engine instance without any middleware attached.
// By default the configuration is:
// - RedirectTrailingSlash:  true
// - RedirectFixedPath:      false
// - HandleMethodNotAllowed: false
// - ForwardedByClientIP:    true
// - UseRawPath:             false
// - UnescapePathValues:     true
func New() *Engine {
    debugPrintWARNINGNew()
    engine := &Engine{
        RouterGroup: RouterGroup{
            Handlers: nil,
            basePath: "/",
            root:     true,
        },
        FuncMap:                template.FuncMap{},
        RedirectTrailingSlash:  true,
        RedirectFixedPath:      false,
        HandleMethodNotAllowed: false,
        ForwardedByClientIP:    true,
        AppEngine:              defaultAppEngine,
        UseRawPath:             false,
        UnescapePathValues:     true,
        MaxMultipartMemory:     defaultMultipartMemory,
        trees:                  make(methodTrees, 0, 9),
        delims:                 render.Delims{Left: "{{", Right: "}}"},
        secureJsonPrefix:       "while(1);",
    }
    engine.RouterGroup.engine = engine
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }
    return engine
}

在上面也提到医咨,Engine負(fù)責(zé)一系列的設(shè)置工作,通過(guò)初始化源代碼可以看到架诞,通過(guò)Engine可以進(jìn)行很多默認(rèn)參數(shù)的設(shè)置,在實(shí)際的項(xiàng)目中可以根據(jù)實(shí)際情況都默認(rèn)參數(shù)進(jìn)行合理設(shè)置谴忧。
比如很泊,在使用gin通過(guò)模板進(jìn)行開(kāi)發(fā)時(shí),gin默認(rèn)的delims為{{xxx}},這與vue的相沖突沾谓,可以通過(guò)下面代碼來(lái)改變默認(rèn)配置

r.Delims("{[{", "}]}")

在示例代碼中委造,直接調(diào)用了r.Get 這個(gè)方法,其實(shí)Engine是實(shí)現(xiàn)了RouterGroup的均驶,所以我們可以通過(guò)Engine直接進(jìn)行路由的配置昏兆。gin對(duì)請(qǐng)求路徑是使用了樹(shù)形結(jié)構(gòu)組織的,RouterGroup是對(duì)請(qǐng)求路徑路由樹(shù)的封裝妇穴,項(xiàng)目中所有的路由規(guī)則都是有其來(lái)組織的爬虱。每次調(diào)用Get、Post等路由方法時(shí)腾它,都會(huì)向路有樹(shù)中添加注冊(cè)路由跑筝,

// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
    return group.handle("GET", relativePath, handlers)
}
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
    absolutePath := group.calculateAbsolutePath(relativePath)
    handlers = group.combineHandlers(handlers)
    group.engine.addRoute(httpMethod, absolutePath, handlers)
    return group.returnObj()
}

通過(guò)RouterGroup可以很方便的對(duì)接口進(jìn)行分組,比如將請(qǐng)求接口分為不通的版本瞒滴,以實(shí)現(xiàn)平滑的接口升級(jí)曲梗。

v1 := r.Group("/v1")

    {
        v1.GET("/ping",func(c *gin.Context){
            c.JSON(200, gin.H{

                "message": " v1 pong",
            })
        })
    }

Context

在上面分析Engine初始化代碼時(shí),有這樣一段代碼:

engine.RouterGroup.engine = engine
    engine.pool.New = func() interface{} {
        return engine.allocateContext()
    }

這段代碼是對(duì)gin中另一個(gè)重要的對(duì)象Context做池化的初始化代碼妓忍,通過(guò)對(duì)Context進(jìn)行池化處理虏两,可以提高很大的效率。Engine作為gin的核心和入口世剖,底層是對(duì)http server的封裝定罢,通過(guò)Engine實(shí)現(xiàn)的ServeHttp方法可以看到在此處對(duì)Context進(jìn)行了初始化操作

// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    c := engine.pool.Get().(*Context)
    c.writermem.reset(w)
    c.Request = req
    c.reset()

    engine.handleHTTPRequest(c)

    engine.pool.Put(c)
}

下面我們針對(duì)gin.Context做詳細(xì)的講解。打開(kāi)Context的源碼我們看到官方對(duì)Context的描述:Context是gin框架最重要的部分搁廓,通過(guò)context可以在組件間傳遞變量引颈,管理路由耕皮,驗(yàn)證請(qǐng)求的json數(shù)據(jù)和渲染輸出json數(shù)據(jù)境蜕。Context貫穿著整個(gè)的http請(qǐng)求,保存請(qǐng)求流程的上下文信息凌停。
首先我們看一下Context中定義的變量粱年,

type Context struct {
    writermem responseWriter
    Request   *http.Request
    Writer    ResponseWriter

    Params   Params
    handlers HandlersChain
    index    int8
    fullPath string

    engine *Engine

    // Keys is a key/value pair exclusively for the context of each request.
    Keys map[string]interface{}

    // Errors is a list of errors attached to all the handlers/middlewares who used this context.
    Errors errorMsgs

    // Accepted defines a list of manually accepted formats for content negotiation.
    Accepted []string

    // queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
    queryCache url.Values

    // formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
    // or PUT body parameters.
    formCache url.Values
}

Context對(duì)請(qǐng)求的參數(shù)、清理路徑都進(jìn)行了封裝罚拟,可以很方便的直接或間接的操作http交互中的各種變量台诗。
同時(shí)完箩,Context也內(nèi)置了多種響應(yīng)渲染形式,在項(xiàng)目構(gòu)建過(guò)程中可以很方便的通過(guò)一行代碼就完成json拉队、xml等數(shù)據(jù)格式的輸出弊知。

// Status sets the HTTP response code.
func (c *Context) Status(code int) {...}

// Header is a intelligent shortcut for c.Writer.Header().Set(key, value).
// It writes a header in the response.
// If value == "", this method removes the header `c.Writer.Header().Del(key)`
func (c *Context) Header(key, value string) {...}

// GetHeader returns value from request headers.
func (c *Context) GetHeader(key string) string {...}
.....

// SetCookie adds a Set-Cookie header to the ResponseWriter's headers.
// The provided cookie must have a valid Name. Invalid cookies may be
// silently dropped.
func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) {...}
// PureJSON serializes the given struct as JSON into the response body.
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
func (c *Context) PureJSON(code int, obj interface{}) {...}

// XML serializes the given struct as XML into the response body.
// It also sets the Content-Type as "application/xml".
func (c *Context) XML(code int, obj interface{}) {...}

// YAML serializes the given struct as YAML into the response body.
func (c *Context) YAML(code int, obj interface{}) {...}
...

所有渲染組件最后還是通過(guò)http writer進(jìn)行輸出,

// Render writes the response headers and calls render.Render to render data.
func (c *Context) Render(code int, r render.Render) {
    c.Status(code)

    if !bodyAllowedForStatus(code) {
        r.WriteContentType(c.Writer)
        c.Writer.WriteHeaderNow()
        return
    }

    if err := r.Render(c.Writer); err != nil {
        panic(err)
    }
}

HTML模板渲染

gin內(nèi)置了對(duì)html模板的渲染粱快,使用方式也很簡(jiǎn)單秩彤,只需要指定模板的存放路徑即可,

func main() {
    router := gin.Default()
    router.LoadHTMLGlob("templates/*")
    //router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
    router.GET("/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.tmpl", gin.H{
            "title": "Main website",
        })
    })
    router.Run(":8080")
}

emplates/index.tmpl

<html>
    <h1>
        {{ .title }}
    </h1>
</html>

除了上面調(diào)到的各項(xiàng)超強(qiáng)的功能為事哭,gin也為開(kāi)發(fā)者提供靜態(tài)文件服務(wù)漫雷、請(qǐng)求參數(shù)驗(yàn)證、文件上傳等其他強(qiáng)大的功能鳍咱,可以通過(guò)訪問(wèn)
https://github.com/gin-gonic/gin
去了解認(rèn)識(shí)gin降盹。

本節(jié)完。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谤辜,一起剝皮案震驚了整個(gè)濱河市蓄坏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌每辟,老刑警劉巖剑辫,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異渠欺,居然都是意外死亡妹蔽,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門挠将,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)胳岂,“玉大人,你說(shuō)我怎么就攤上這事舔稀∪榉幔” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵内贮,是天一觀的道長(zhǎng)产园。 經(jīng)常有香客問(wèn)我,道長(zhǎng)夜郁,這世上最難降的妖魔是什么什燕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮竞端,結(jié)果婚禮上屎即,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好技俐,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布乘陪。 她就那樣靜靜地躺著,像睡著了一般雕擂。 火紅的嫁衣襯著肌膚如雪啡邑。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天井赌,我揣著相機(jī)與錄音谣拣,去河邊找鬼。 笑死族展,一個(gè)胖子當(dāng)著我的面吹牛森缠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仪缸,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼贵涵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了恰画?” 一聲冷哼從身側(cè)響起宾茂,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拴还,沒(méi)想到半個(gè)月后跨晴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡片林,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年端盆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片费封。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡焕妙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弓摘,到底是詐尸還是另有隱情焚鹊,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布韧献,位于F島的核電站末患,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏锤窑。R本人自食惡果不足惜璧针,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望果复。 院中可真熱鬧陈莽,春花似錦渤昌、人聲如沸虽抄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)迈窟。三九已至私植,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間车酣,已是汗流浹背曲稼。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留湖员,地道東北人贫悄。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像娘摔,于是被迫代替她去往敵國(guó)和親窄坦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 有時(shí)凳寺,“失去”并非一件壞事鸭津。它是儀式,告訴我們?cè)赵谑种械膶こ3τВ瓉?lái)如此珍貴逆趋。 也許,人生的本質(zhì)就是遺憾晒奕。 我們無(wú)...
    Ruby_1696閱讀 164評(píng)論 0 0
  • 文 | 李偉誠(chéng) 001 《這樣做指導(dǎo)脑慧,難帶員工變能干》這本書(shū)是講關(guān)于企業(yè)管理方面惠窄,在企業(yè)中最大的財(cái)富便是人,對(duì)于一...
    騎象人偉誠(chéng)閱讀 735評(píng)論 0 0
  • 冷冷地早晨 天空終于肯放晴了 金色的云朵浮在天邊 萬(wàn)丈霞光穿過(guò)它們 要來(lái)溫暖我們的世界
    謝京豆閱讀 299評(píng)論 0 1
  • 在時(shí)間的長(zhǎng)河里漾橙,十年只是一朵細(xì)微的浪花杆融,而在人的交往中,能相交相知十年的霜运,定是摯友了脾歇,一個(gè)人若十年堅(jiān)持做同一件事...
    博贊教育張老師閱讀 281評(píng)論 0 0