iris框架的控制器解析和handler注冊

主要內(nèi)容:控制器解析需曾、handler注冊涩禀。

調(diào)用方法:

func (app *Application) Handle(controller interface{}) *Application {
    c := newControllerActivator(app.Router, controller, app.Dependencies)
    if before, ok := controller.(interface {
        BeforeActivation(BeforeActivation)
    }); ok {
        before.BeforeActivation(c)
    }

    c.activate()

    if after, okAfter := controller.(interface {
        AfterActivation(AfterActivation)
    }); okAfter {
        after.AfterActivation(c)
    }
    return app
}

go語言接口查詢檢測自定義handler是否需要加載

對于JAVA惫撰,需要檢測控制器杆兵。一旦繼承了某個接口,就要實(shí)現(xiàn)其全部的接口方法,因而不需要檢測咆畏,直接調(diào)用重寫的方法南捂。

對于JAVA,go語言的的作用域更加開放旧找,所有的變量類型只區(qū)分包內(nèi)可見溺健,或是包外。用變量名的大小寫實(shí)現(xiàn)钦讳。對于接口也是不需要顯式指定實(shí)現(xiàn)是的哪些接口矿瘦,可以直接實(shí)現(xiàn)接口方法。更加靈活地繼承任何接口愿卒。這樣淡化了接口設(shè)計缚去,使類可以靈活實(shí)現(xiàn)各種接口。

由于自定義Handler需要重寫B(tài)eforeActivation方法琼开。用接口查詢的方式易结,檢測控制器實(shí)現(xiàn)了哪些接口,再調(diào)用方法柜候。對應(yīng)于JAVA顯式指明implements哪些接口搞动,調(diào)用用接口方法。

ControllerActivator結(jié)構(gòu)體

控制器BeoreActivation傳入的實(shí)例渣刷,是ControllerActivator{}對象鹦肿。實(shí)現(xiàn)了BeforeActivation接口和AfterActivation接口。這些接口形式如下:

type shared interface {
    Name() string
    Router() router.Party
    GetRoute(methodName string) *router.Route
    Handle(httpMethod, path, funcName string, middleware ...context.Handler) *router.Route
}
type BeforeActivation interface {
    shared
    Dependencies() *di.Values
}
type AfterActivation interface {
    shared
    DependenciesReadOnly() ValuesReadOnly
    Singleton() bool
}

ControllerActivator數(shù)據(jù)結(jié)構(gòu)如下:

type ControllerActivator struct {
    router router.Party

    fullName string

    routes map[string]*router.Route

    dependencies di.Values

    injector *di.StructInjector
}

router.Party接口辅柴。其實(shí)現(xiàn)類router.Router表示一組具有相同前綴和中間件的routes箩溃。router.Router是app(iris實(shí)例)的一個字段。最基本的功能:

  • 調(diào)用Handle HandleMany方法注冊路由碌嘀。
  • 調(diào)用Get/Post/Put/Delete/Head方法(根據(jù)不同的http請求類型)返回http請求Path對應(yīng)的路由*Route涣旨。
  • 調(diào)用Use方法注冊中間件。
  • 調(diào)用Done為路徑下的所有路由股冗,添加后處理handler霹陡。
  • 其它的信息打印、生成子路由集合等止状。

所以router.Router就是iris的路由管理器烹棉。再用routes map[string]*router.Route保存一份此控制器的路由。fullName字段保存控制器名稱怯疤,形式user/controller.g峦耘,則為user.Controller。以下3字段用來反射調(diào)用控制器:

Value reflect.Value
Type  reflect.Type
dependencies di.Values //di.Values是[]reflect.Value的別名

injector di.StructInjector字段旅薄,用于注入一個di.StructInjector實(shí)例。

ControllerActivator的構(gòu)建

ControllerActivator的功能是反射獲取控制器obj,構(gòu)建路由并保存到router.Router少梁。

ControllerActivator的構(gòu)建洛口,傳入了app.Router app.Dependencies及控制器obj。其中router字段直接由app拿到凯沪。Type Value由控制器反射拿到第焰。

dependencies字段包括了app本身的dependencies,以及controller中的字段妨马。注意到參數(shù)并不是引用傳遞挺举,而是深拷貝的。本例中controller沒有字段烘跺。

routes的數(shù)據(jù)結(jié)構(gòu)為map[string]*router.Route湘纵,key為控制器中的方法名稱。本身會預(yù)加載BeforeActivation滤淳,AfterActivation兩個方法梧喷,值為空,作為保留方法脖咐。本例中使用了用BeforeActivation來加載自定義的handler铺敌。如果實(shí)現(xiàn)了BaseController,還應(yīng)將BeginRequest屁擅,EndRequest作為保留方法偿凭。后續(xù)的handleRequest()方法在解析控制器中的方法,將保存到路由時派歌,會首先校驗(yàn)方法弯囊,是不是保留方法,如果是則直接返回硝皂,不進(jìn)行后續(xù)的解析和構(gòu)建常挚、創(chuàng)建路由操作。

“手動”和“自動”化地調(diào)用Handle注冊路由

由于iris使用了NamingGuide來寫處理http請求的方法稽物。不需要每次都寫RequestMethod RequestMapping奄毡。標(biāo)準(zhǔn)化了這些方法的函數(shù)名,含義清晰贝或『鸸框架以“自動”化地方工處理這些函數(shù)。而時又支持手動添加自定義的Handle()咪奖。

Handle方法API如下:

Handle(httpMethod, path, funcName string, middleware ...context.Handler) *router.Route

只需要指明http請求方法盗忱、http請求路徑、funcName羊赵、Handler趟佃。從funcName和controller實(shí)體扇谣,即可以反射調(diào)用對應(yīng)的方法。handler的實(shí)現(xiàn)形式非常簡單闲昭,只有一個傳入?yún)?shù)ctx context罐寨,包括了請求信息、響應(yīng)及其它上下文信息序矩。

自定義的方法鸯绿,會在BeforeActivation中直接調(diào)用Handle中注冊路由。

而對于NamingGuide的常規(guī)方法簸淀,會調(diào)用反射遍歷控制器中的所有方法瓶蝴。如上面所描述,先依據(jù)c.routes檢查是否是保護(hù)函數(shù)名租幕。有一個編程細(xì)節(jié)是舷手,保留字判斷方法c.isReservedMethod,使用了閉包傳遞令蛉。

反射獲取m.Name之后調(diào)用解析方法:

(p *methodParser) parse() (method, path string, err error) 

其中methodParser:

type methodParser struct {
    lexer *methodLexer
    fn    reflect.Method
}

methodLexer是有狀態(tài)的數(shù)組聚霜,由于go標(biāo)準(zhǔn)庫,只有container/list的鏈表實(shí)現(xiàn)珠叔,沒有ArrayList的實(shí)現(xiàn)蝎宇,并且沒有iterator。此數(shù)據(jù)結(jié)構(gòu)完成相同的功能祷安。

解析邏輯流

1姥芥、駝峰式轉(zhuǎn)數(shù)組

首先將駝峰式轉(zhuǎn)為字串?dāng)?shù)組,是一道算法題汇鞭。標(biāo)準(zhǔn)庫提供了判斷字母大小寫的方法unicode.IsUpper(c)

算法:

  • 遇到大寫凉唐,構(gòu)成一個單詞,存到res霍骄。
  • 處理最后一組台囱。
        end := len(s)
        start := -1

        for i, n := 0, end; i < n; i++ {
            c := rune(s[i])
            if unicode.IsUpper(c) {
                // it doesn't count the last uppercase
                if start != -1 {
                    end = i
                    words = append(words, s[start:end])
                }
                start = i
                continue
            }
            end = i + 1
        }

        if end > 0 && len(s) >= end {
            words = append(words, s[start:end])
        }

2、校驗(yàn)HTTP請求是否合法

從app.router.AllMethod拿字段读整,列出了合法的HTTP請求簿训,

AllMethods = [...]string{
        "GET",
        "POST",
        "PUT",
        "DELETE",
        "CONNECT",
        "HEAD",
        "PATCH",
        "OPTIONS",
        "TRACE",
    }

并補(bǔ)入了框架自定義的"ALL", "ANY"也是合法的。

3米间、是否使用了By

功能相當(dāng)于spring中的RequestParam强品。即加入了By關(guān)鍵字,框架會從反射方法m *Method中拿輸入的形參作為RequestParam屈糊。解析http請求方法和路徑都不需要*Method的榛,而methodParser寫入了*Method字段就是為了解析輸入?yún)?shù)之用。調(diào)用如下方法解析參數(shù)逻锐。

(p *methodParser) parsePathParam(path string, w string, funcArgPos int) (string, int, error) 

parse邏輯:

對于通配符Wildcard跳過處理夫晌。

從ParamType常中檢查請求參數(shù)名的類型是否合法雕薪。支持如下類型。

func LookupParamTypeFromStd(goType string) ParamType {
    switch goType {
    case "string":
        return ParamTypeString
    case "int":
        return ParamTypeInt
    case "int64":
        return ParamTypeLong
    case "bool":
        return ParamTypeBoolean
    default:
        return ParamTypeUnExpected
    }

把參數(shù)追加到路徑中晓淀,形式/{%s:%s}蹦哼。

4、解析路徑

strings.ToLower(w)轉(zhuǎn)為小寫要糊。

如此NamingGuide提供了RequestMapping RequestParam RequestMethod的功能。PathVariable功能妆丘,直接在path中用{}锄俄,需要自定義控制器方法。

最后仍和BeforeActivation中"手動"調(diào)用Handle()一樣勺拣,調(diào)用:

c.Handle(httpMethod, httpPath, m.Name)

路由注冊邏輯Handle

Handle的邏輯功能如下:

  • 檢測上文提到的保留方法奶赠。
  • 解析路徑中的參數(shù)名稱及類型,實(shí)現(xiàn)類似@PathVariable的功能药有。
  • 參數(shù)構(gòu)建
  • handler構(gòu)建
  • 注冊路由到router.Router

先從c.routes拿已注冊路由的方法名毅戈,調(diào)用c.isReservedMethod(funcName)檢測已注冊路由。使用了保留字段的檢測方法愤惰,但功能上還是會檢測已注冊方法名的苇经。

方法調(diào)用用的參數(shù)由三部分組成:app.dependencies、controller字段和path路徑解析參數(shù)宦言。前兩個部分的整合已經(jīng)在構(gòu)建ControllerActivator時完成扇单,為深復(fù)制。這兩部分參數(shù)的特點(diǎn)是注冊時就能確定的參數(shù)奠旺,而后面的兩部分PathVaraible RequestParam是運(yùn)要運(yùn)行時才能確定的參數(shù)蜘澜。前文已經(jīng)提到RequestParam的參數(shù)名稱和類型(還未獲得值)已經(jīng)以/{%s:%s}的形式解析進(jìn)path。所以處理path中/{%s:%s}形式的字段响疚,將同時完成從@PathVariable @RequestParam獲得參數(shù)字段名鄙信,存入[]reflect.Value參數(shù)表。

由于@PathVariable @RequestParam的值需要在接到http請求之后才能獲得忿晕。并且未必提供完整的@RequestParam装诡,mvc框架中已添加是否獲得符合/{%s:%s}參數(shù)要求的校驗(yàn)。在server服務(wù)期間杏糙,接受到http請求慎王,請求path將通過已注冊路由,找到(w,*r)的handlers序列合集宏侍,具體邏輯流后面詳述赖淤。三類參數(shù)組合后,交由c.handlerOf()方法創(chuàng)建handler谅河。

上文提到自定義調(diào)用Handle方法可以注冊兩個handler咱旱,一個是從Handle()方法傳入确丢,一個就是路徑對應(yīng)的控制器方法。mvc規(guī)定傳入的handler優(yōu)先執(zhí)行吐限。

最后注冊路由鲜侥,同時注冊到ControllerActivator實(shí)例的c.routes用來檢測重復(fù)注冊。Handle邏輯的任務(wù)簡要:參數(shù)獲取诸典、handler構(gòu)建描函、route構(gòu)建。

handler構(gòu)建邏輯

調(diào)用handleOf()構(gòu)建邏輯狐粱。ControllerActivator實(shí)例會保存一個c.injector字段舀寓。每次調(diào)用handlerOf,就會將c.injector綁定為當(dāng)前值肌蜻。分別將ControllerActivator注冊為c.injector互墓,方法注冊為funcInjector。

需要說明的是蒋搜,以上所有的邏輯篡撵,對path的解析還只是一個模板,沒有填充實(shí)際的參數(shù)豆挽。這里用了閉包和反射的結(jié)合使用育谬,注冊將來才會調(diào)用的handler易于編程,結(jié)構(gòu)清晰祷杈。handler的注冊只是注冊了一個閉包斑司,而在接受請求、處理請求才調(diào)用handler時但汞,才執(zhí)行handler閉包宿刮,反射調(diào)用獲取運(yùn)行時字段信息。

給出兩個injector的數(shù)據(jù)結(jié)構(gòu):

StructInjector struct {
                initRef        reflect.Value
                elemType       reflect.Type
                fields []*targetStructField
                Has       bool
                Scope     Scope
}
FuncInjector struct {
                fn       reflect.Value
                typ      reflect.Type
                goodFunc TypeChecker

                inputs []*targetFuncInput
                Length int
                Has bool
}

其中initRef私蕾、initRefAsSlice僵缺、elemType是同一個信息,區(qū)別僅是反射值踩叭、反射值切片磕潮、反射指針的指向元素。fields用于保存ControllerActivator中的字段容贝。field數(shù)據(jù)結(jié)構(gòu):

type field struct {
    Type   reflect.Type
    Name   string 
    Index  []int 
    CanSet bool  
    AnyValue reflect.Value
}

這兩個struct的構(gòu)建過程自脯,完成了http請求參數(shù),向這兩個實(shí)體的注入斤富。完成第一步的注值之后膏潮,第二步進(jìn)行檢查,是否是BaseController满力,是否注入的控制器字段焕参,是否注入了方式的輸入?yún)?shù)轻纪。第三步根據(jù)不同的情況執(zhí)行反射調(diào)用call([]reflect.Value)。最后一步hero.DispatchFuncResult叠纷,將響應(yīng)相關(guān)的http信息寫入到ctx刻帚。

如果有ControllerActivator的綁定字段,則由ctx注值到c.injector.Acquire().Elem()涩嚣。情況1:對于BaseController的情況崇众,直接調(diào)用BeginRequest,EndRequest后返回航厚。情況2如果方法有形參校摩,則由funcInjector注值。

反射調(diào)用結(jié)果分發(fā)回context

反射調(diào)用將獲得reflect.Value類型的結(jié)果阶淘。調(diào)用hero.DispatchFuncResult分發(fā)結(jié)果。先用switch輪循[]reflect.Value處理statusCode互妓、content type溪窒、content、其它c(diǎn)untom類型冯勉。

分兩種情況澈蚌,如果反射調(diào)用的返回結(jié)果是字串,調(diào)用ctx.Write(content)灼狰。如果是Result類型宛瞄,調(diào)用Dispatch()寫入結(jié)果。其它情況:contentType為application/javascrip text/xml application/json的情況交胚。

type Result interface {
    Dispatch(ctx context.Context)
}

以上所有無論何種情況最終調(diào)用net/http標(biāo)準(zhǔn)庫方法份汗,向ctx.Write([]byte)向ResponseWriter寫入結(jié)果。

ControllerActivator與app實(shí)例

最后總結(jié)一下路由注冊類ControllerActivator與主實(shí)例app的關(guān)聯(lián)蝴簇。在調(diào)用Handle()傳入了app.Router app.dependencies兩個字段杯活。app.dependencies字段指明了從http請求中需要拿參數(shù)的類型,在反射調(diào)用之前執(zhí)行熬词。

c.router字段的功能是:

  • 提供c.router.Macros()提供http請求路徑解析的Macros旁钧。
  • 調(diào)用c.router.Handle注冊已經(jīng)構(gòu)建的handler。這也是整個邏輯流的最終目的互拾,注冊一個傳入ctx的閉包到路由歪今。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市颜矿,隨后出現(xiàn)的幾起案子寄猩,更是在濱河造成了極大的恐慌,老刑警劉巖或衡,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件焦影,死亡現(xiàn)場離奇詭異车遂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)斯辰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門舶担,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人彬呻,你說我怎么就攤上這事衣陶。” “怎么了闸氮?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵剪况,是天一觀的道長。 經(jīng)常有香客問我蒲跨,道長译断,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任或悲,我火速辦了婚禮孙咪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘巡语。我一直安慰自己翎蹈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布男公。 她就那樣靜靜地躺著荤堪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枢赔。 梳的紋絲不亂的頭發(fā)上澄阳,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天,我揣著相機(jī)與錄音踏拜,去河邊找鬼寇荧。 笑死,一個胖子當(dāng)著我的面吹牛执隧,可吹牛的內(nèi)容都是我干的揩抡。 我是一名探鬼主播,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼镀琉,長吁一口氣:“原來是場噩夢啊……” “哼峦嗤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起屋摔,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤烁设,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體装黑,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡副瀑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了恋谭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片糠睡。...
    茶點(diǎn)故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖疚颊,靈堂內(nèi)的尸體忽然破棺而出狈孔,到底是詐尸還是另有隱情,我是刑警寧澤材义,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布均抽,位于F島的核電站,受9級特大地震影響其掂,放射性物質(zhì)發(fā)生泄漏油挥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一款熬、第九天 我趴在偏房一處隱蔽的房頂上張望喘漏。 院中可真熱鬧,春花似錦华烟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至堤魁,卻和暖如春喂链,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背妥泉。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工椭微, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人盲链。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓蝇率,卻偏偏與公主長得像,于是被迫代替她去往敵國和親刽沾。 傳聞我的和親對象是個殘疾皇子本慕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評論 2 359

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