从hello-world开始go iris

从hello-world开始go iris

iris框架是少数支持MVC的go web框架。在简单业务逻辑测试中,其性能超过了约大多数的go web框架。底层还是调用net/http包handleRequest。

iris_examples.mvc.hello-world代码剖析

基本功能实现:

  • 使用了recover中间件和logger中间件。
  • 添加了一个mvc控制器,处理"/" "/ping" "/hello",请求方法为GET

先上基本示例的代码:

package main

import (
"github.com/kataras/iris"
"github.com/kataras/iris/mvc"

"github.com/kataras/iris/middleware/logger"
"github.com/kataras/iris/middleware/recover"
)


func newApp() *iris.Application {
    app := iris.New()
    app.Use(recover.New())
    app.Use(logger.New())

    mvc.New(app).Handle(new(ExampleController))
    return app
}

func main() {
    app := newApp()

    app.Run(iris.Addr(":8080"))
}

type ExampleController struct{}

func (c *ExampleController) Get() mvc.Result {
    return mvc.Response{
        ContentType: "text/html",
        Text:        "<h1>Welcome</h1>",
    }
}

func (c *ExampleController) GetPing() string {
    return "pong"
}

func (c *ExampleController) GetHello() interface{} {
    return map[string]string{"message": "Hello Iris!"}
}

func (c *ExampleController) BeforeActivation(b mvc.BeforeActivation) {
    anyMiddlewareHere := func(ctx iris.Context) {
        ctx.Application().Logger().Warnf("Inside /custom_path")
        ctx.Next()
    }
    b.Handle("GET", "/custom_path", "CustomHandlerWithoutFollowingTheNamingGuide", anyMiddlewareHere)

}

func (c *ExampleController) CustomHandlerWithoutFollowingTheNamingGuide() string {
    return "hello from the custom handler without following the naming guide"
}

go iris处理请求的流程大致如下:

  • 先初始化一个app实例。
  • 加载中间件。
  • 由中间件构建mvc实例。
  • mvc实例Handle一个包含多个处理rest方法的controller。相当于处理java的一个Component。
  • 调用app.Run()方法,构建app实例,启动服务端,阻塞等待客户端的连接。

app实例的初始化,包括了加载配置,路由的初始化,并且完成app.ContextPool的初始化。这个池底层由同步包*sync.Pool实现。

handlers处理流

iris里所说的中间件,类似于netty中所说的handlers。最基本形式上的不同,netty中的handler是包装在SimpleChannelInboundHandler类中,通过读写通道来完成请求的处理。而由于go在语言上支持闭包,iris中间件是一个,输入是一个请求上下文ctx的闭包。

一个最基本的服务端处理,无论使用何种框架,都至少需要完成:

  • 拿到请求,解析请求。
  • 逐层地用handler处理请求。
  • 生成响应,写入到TCP的写缓存,并能过底层的tcp常连接(请求参数或是服务端设置成keepalive的)将其发送回客户端。

类似于不同的切面将拦截的请求信息,处理后转给下一层。iris每一层handler处理后转给下一个handler。作为服务端,所有的handler都保存在app实例的路由数据结构中。在接到待处理请求之后,会根据请求地址,装配成一套handlers序列,供handlerRequest()方法调用。handlers由

  • 系统必须的handlers
  • 每个路径请求对应的控制器中的控制方法。以及调用Handle(请求)时还可以再指定一个handler闭包。这里注意区别Handle(控制器),Handle(请求路径)是两个不同的方法,只是同名而已。
  • 以及中间件handler的加载。

handlers处理顺序

这里就是有一个需要解释的问题,这些handlers是如何给定执行顺序的。请求被从标准net/http的(w,*r)寺装成context.Context之后,就会调用路由(h *routerHandler) HandleRequest(ctx context.Context) {}完成解析路径,和逐层调用ctx.Do(handlers)。所以handler的执行顺序,要定位到解析请求的是路由构建,而不是app的全局路由表构建。

此处不展开,后面再解释。回到原处,解释中间件的handlers之间是如何确定顺序,以及相对控制方法的扩行顺序的。中间件通过app.Use()注册到app.*APIBuilder.middleware。是一个[]context.Handler。逻辑很单一,就是将handler闭包,加到切片中。

完成中间件及控制器添加之后,handler顺序是在调用app.Run()内部app.Build()中构建路由时,确定request_path与相应有序handlers的。保存在app.Router.requestHandler.trees。 一个控制器的根路径集合包装成一个Nodes。对比java的spingmvc框架,RequestMpping注解,添加在控制器相当于根路径,添加到方法上,才是请求的子路径。这里iris保存数据结构的实现也体现了根路径、子路径的分类存入。解析时也会取两段路径拼接。

请求ctx.Do(handlers)按顺序执行各handler,每个handler闭包中,都会调用ctx.Next()调用后续handler。在ctx.Next()之前的代码段,相较于此handler后面所有handlers组成的集合,在此之前执行。放在ctx.Next()之后的代码段、以及defer func(),在此之后执行。

如GET /的路由:

image

java try...catch...exception与panic recover区别

本例加载了两个中间件,日志打印中间件,为似地可以扩展到服务监控中间件。recover中间件,处理请求中发生的异常。go语言中更多地使用err,而较少地使用panic recover机制。而相较于java,try...catch...exception机制随处可见。形式上发生panic之后,只会调用defer func()中的recover。理由是panic调用后,方法终止,并向上抛异常。但在返回之前会先执行defer func()。所以可以利用这个特性,执行恢复,还有就是要在defer中完成资源的关闭操作,如数据库、网络。

go 的panic机制执行完recover之后,将直接返回,并不继续执行panic()之后的逻辑!!!这与java是不同的。

标准库关于panic的解释如下:

The panic built-in function stops normal execution of the current goroutine. When a function F calls panic, normal execution of F stops immediately. Any functions whose execution was deferred by F are run in the usual way, and then F returns to its caller. To the caller G, the invocation of F then behaves like a call to panic, terminating G's execution and running any deferred functions. This continues until all functions in the executing goroutine have stopped, in reverse order. At that point, the program is terminated and the error condition is reported, including the value of the argument to panic. This termination sequence is called panicking and can be controlled by the built-in function recover.

所以具体到hello-world实例中,recover()是在ctx.Next()之后执行的。

日志中间件需要在控制器方法之后收集信息,并打印日志,所以与是在ctx.Next()之后执行。但该打印哪些日志的配置加载,可以在ctx.Next()之前执行。

如果日志中间件也可能panic(),则将recover.go放在logger.go之前。

RequestMapping

控制器中的各方法最终也会被注册到app的路由表中。后面会详细说明控制器的解析过程,是如何区分标准NamingGuide,重载方法Before/AfterActivation,以及自定义控制方法,进行分辨别类地注册的。

java的spingmvc框架,接爱处理一个请求连接。需要指定RequestMapping的请求路径。指定request_method。如果需要请求路径中的参数,要用@PathVariable指定,以及body cookie等。

go iris为简化编程,支持NamingGuide的方法命名,即RequestMethod+RequestMapping的形式,不需要单独为函数起名字,不一致也会降低代码阅读效率。后面会详细说明具体实现。返回值可以是字串,或其它interface{}。

自定义控制方法及加载流程

如果不想使用NamingGuide,就需要自己调用Handle()注册,至少需要指定httpMethod、path、handlerFunc、anyMiddleware闭包。这里handlerFunc只是方法名的字串,由框架反射调用具体函数并加载。而anyMiddleware是传入的可执行闭包。

注册逻辑,以及anyMiddleware加载要写在重写的BeforeActifvation。go语言在语法上是不支持重写的,应该是实现了ControllerActivator的BeforeActivation接口方法。

先上解析的源代码:

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
}

主要逻辑就是先构建ControllerActivator控制器的执行类,如果重写了BeforeActivation,就执行BeforeActivation的逻辑。本例中注册了一个自定议的控制器。之后再执行c.activate()对其它控制器方法作解析,并注册。

c.Handle太复杂了,后面详细说明。简单斜述,如下:

  • 解析过程会调用c.Type.MethodByName(funcName)反射调用,加载方法。类似地Java中的Bean也需要通过反射加载。
  • 逻辑实现了如何区分BeforeActivation NamingGuide CustomHandler走不同的逻辑流。
  • c.handlerOf(m, funcDependencies)生成了handler闭包。
  • 两次注册c.router.Handle() c.routes[funcName] = route。第二次注册,用哈希表key的不重复特性,避免同名方法重复注册。

最后整个框架可分为上下两层,上层处理请求封装,路由,cookie,session,认证等。下层是net/http TCP实现。而中间的连接结构,就是app的字段app.(Router),为router.Router类型。包括了锁、路由集合、包裹函数、构建好的context.ContextPool,以及app.(APIBuilder)。在后文中将做继教详述。

router.go中的Route源代码:

type Router struct {
    mu sync.Mutex // for Downgrade, WrapRouter & BuildRouter,
    // not indeed but we don't to risk its usage by third-parties.
    requestHandler RequestHandler   // build-accessible, can be changed to define a custom router or proxy, used on RefreshRouter too.
    mainHandler    http.HandlerFunc // init-accessible
    wrapperFunc    func(http.ResponseWriter, *http.Request, http.HandlerFunc)

    cPool          *context.Pool // used on RefreshRouter
    routesProvider RoutesProvider
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容