暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

golang源码分析(38)beego

1.http.HandleFunc将pattern及我们自定义的handler存储在DefaultServeMux的一个map中。


2.当http.ListenAndServe的handler为nil时,系统会从DefaultServeMux存储信息的map中匹配pattern获取对应的handler,进而处连接请求。


如果用更简单的话来总结以上两条,就是:路由的存储与匹配


beego作为一个基于golang之上的框架(此处只讨论核心http及路由部分的处理),必然脱离不了语言底层的支持。因此,我们可以大胆猜测,beego的路由处理主要是在HandleFunc的封装上,即如何更好的封装HandlerFunc且便于使用,这应该也是beego支持多种路由方式的特点了。


一、beego.Run

我们先从源码来验证一条简单的猜测,beego基于http.ListenAndServe来处理网络请求(本次仅针对http协议的处理进行分析,https协议类似)。


beego.Run()->BeeApp.Run()


// Run beego application.

func (app *App) Run() {

    ...

    app.Server.Handler = app.Handlers

    app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second

    app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second

    app.Server.ErrorLog = logs.GetLogger("HTTP")


    run graceful mode

    if BConfig.Listen.Graceful {

        httpsAddr := BConfig.Listen.HTTPSAddr

        app.Server.Addr = httpsAddr

        if BConfig.Listen.EnableHTTPS {

            go func() {

                ...

                if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {

                    logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))

                    time.Sleep(100 * time.Microsecond)

                    endRunning <- true

                }

            }()

        }

        if BConfig.Listen.EnableHTTP {

            go func() {

                ...

                if err := server.ListenAndServe(); err != nil {

                    logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))

                    time.Sleep(100 * time.Microsecond)

                    endRunning <- true

                }

            }()

        }

        <-endRunning

        return

    }

    ...

}

http.ListenAndServe的源码如下:


func ListenAndServe(addr string, handler Handler) error {

    server := &Server{Addr: addr, Handler: handler}

    return server.ListenAndServe()

}

可以很明显的看出beego最终和http.ListenAndServe调用的接口是一样的。(此处只对http部分进行分析,https协议也是通用的只是调用的接口是ListenAndServeTLS)


二、Router

1.支持的路由方式

根据官网的描述,beego支持以下路由方式:


基础路由

beego.Get

beego.Post

自定义的 handler 实现

s := rpc.NewServer()

s.RegisterCodec(json.NewCodec(), "application/json")

s.RegisterService(new(HelloService), "")

beego.Handler("/rpc", s)

固定路由&正则路由&自定义方法

// 固定路由

beego.Router("/", &controllers.MainController{})

beego.Router("/admin", &admin.UserController{})

// 正则路由

beego.Router("/api/?:id", &controllers.RController{})

beego.Router("/cms_:id([0-9]+).html", &controllers.CmsController{}))

// 指定方法

beego.Router("/api/list",&RestController{},"*:ListFood")

beego.Router("/api/create",&RestController{},"post:CreateFood")

自动匹配路由

beego.AutoRouter(&controllers.ObjectController{})

-注解路由

// @router all/:key [get]

func (this *CMSController) AllBlock() {


}

beego.Include(&CMSController{})

namespace

ns :=

beego.NewNamespace("/v1",

    beego.NSCond(func(ctx *context.Context) bool {

        if ctx.Input.Domain() == "api.beego.me" {

            return true

        }

        return false

    }),

    beego.NSBefore(auth),

    beego.NSGet("/notallowed", func(ctx *context.Context) {

        ctx.Output.Body([]byte("notAllowed"))

    }),

    beego.NSRouter("/version", &AdminController{}, "get:ShowAPIVersion"),

    beego.NSRouter("/changepassword", &UserController{}),

    beego.NSNamespace("/shop",

        beego.NSBefore(sentry),

        beego.NSGet("/:id", func(ctx *context.Context) {

            ctx.Output.Body([]byte("notAllowed"))

        }),

    ),

    beego.NSNamespace("/cms",

        beego.NSInclude(

            &controllers.MainController{},

            &controllers.CMSController{},

            &controllers.BlockController{},

        ),

    ),

)

//注册 namespace

beego.AddNamespace(ns)


NSNamespace/NewNamespace内部的部分存储路由时,只处理了自身的路由,总的路由还需要在NSNamespace/NewNamespace进行添加prefix,最终存储在BeeApp.Handlers.routers.

数据传输在此进行:


    app.Server.Handler = app.Handlers

    app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second

    app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second

    app.Server.ErrorLog = logs.GetLogger("HTTP")

在此,我们研究下几个主要入口作为示例来分析源码。


2.具体路由方式的分析

在初探中我们只做简单的功能分析,具体的分析我们在后续的文章中加以补充。


(1)beego.Get

beego.Get->BeeApp.Handlers.Get->p.AddMethod->p.addToRouter

其中p即BeeApp.Handlers,因此这一连串的操作主要是将router信息存入BeeApp.Handlers中。


(2)beego.Router

beego.Router->BeeApp.Handlers.Add->p.addWithMethodParams->p.addToRouter

与(1)同理


(3)beego.AutoRouter

beego.AutoRouter->BeeApp.Handlers.AddAuto->p.AddAutoPrefix->p.addToRouter


与(1)同理


(4)beego.Include

beego.Include->BeeApp.Handlers.Include->p.addWithMethodParams->p.addToRouter


与(1)同理


(5)beego.AddNamespace

beego.AddNamespace->BeeApp.Handlers.routers[k]


直接将namespace.handlers.routers中的添加前缀后存入BeeApp.Handlers.routers中。


AddNamespace、NSNamespace及其内部的一系列NSNamespace方法集最终依然调用n.handlers的方法集中,然后经过AddNamespace,保存至BeeApp.Handlers中


三、ServeHTTP

既然处理请求时,经过了http.ListenAndServe,根据我们对ListenAndServe的分析,最终请求会交由server.Handler的ServeHTTP来处理。beego中的Handler即为BeeApp.Handlers,其类型是ControllerRegister,也就是说请求最终交由ControllerRegister的ServeHTTP实现来处理。此处我们也是只谈下处理过程,具体的细节我们在之后的分析文章内细谈。


三、总结

我们分别从两个入口beego.Run、router两个入口的使用,可以看到beego处理的逻辑基本上和golang底层http.ListenAndServe、http.HandleFunc的原理一致,只是有更高层次的封装,以及更便利的路由声明及处理。在后续的文章中我们会对beego的处理作进一步的分析。


最后,我们可以做下几点简单的总结:


(1)routers package主要是生成路由及处理的相关信息,并保存至BeeApp.Handler中,


(2)beego.Run则是带着BeeApp.Handler的启动http的服务,


(3)接收请求后,由BeeApp.Handler的ServeHTTP进行处理,作出对应响应。

实际上beego的入口只有beego.Run,怎么与routers具体联系起来的呢?

1

接下来我们一步步看下beego.Run背后的逻辑。


一、import & init

我们首先从程序最开始的import部分开始看。


import (


    _ "test/routers"


    "github.com/astaxie/beego"

)

我们知道golang的初始化的方向如下:



按照这个方向,我们可以知道先调用routers package init,在routers内再次import “github.com/astaxie/beego”,因此先调用beego package init,然后调用routers package init。


1.beego init

代码如下:


var (

    BeeApp is an application instance

    BeeApp *App

)


func init() {

    create beego application

    BeeApp = NewApp()

}


func NewApp() *App {

    cr := NewControllerRegister()

    app := &App{Handlers: cr, Server: &http.Server{}}

    return app

}


func NewControllerRegister() *ControllerRegister {

    cr := &ControllerRegister{

        routers:  make(map[string]*Tree),

        policies: make(map[string]*Tree),

    }

    cr.pool.New = func() interface{} {

        return beecontext.NewContext()

    }

    return cr

}

以上init完成了BeeApp的初始化,同时,初始化了Handler及Server的值(这是两个很重的参数,后面将会用到),在这里我们可以看到Handler的类型是ControllerRegister,在处理请求时将会用到。


2.routers init

routers init主要是路由的声明,例如:


ns :=

beego.NewNamespace("/v1",

    beego.NSCond(func(ctx *context.Context) bool {

        if ctx.Input.Domain() == "api.beego.me" {

            return true

        }

        return false

    }),

    beego.NSBefore(auth),

    beego.NSGet("/notallowed", func(ctx *context.Context) {

        ctx.Output.Body([]byte("notAllowed"))

    }),

    beego.NSRouter("/version", &AdminController{}, "get:ShowAPIVersion"),

    beego.NSRouter("/changepassword", &UserController{}),

    beego.NSNamespace("/shop",

        beego.NSBefore(sentry),

        beego.NSGet("/:id", func(ctx *context.Context) {

            ctx.Output.Body([]byte("notAllowed"))

        }),

    ),

    beego.NSNamespace("/cms",

        beego.NSInclude(

            &controllers.MainController{},

            &controllers.CMSController{},

            &controllers.BlockController{},

        ),

    ),

)

//注册 namespace

beego.AddNamespace(ns)


我们同样在初探中提过beego.Get、beego.Router、beego.AutoRouter、beego.Include、beego.AddNamespace等方式声明的路由最终存储在BeeApp.Handlers.routers。


二、beego.Run

beego.Run代码如下:


func Run(params ...string) {

    ...

    BeeApp.Run()

}


beego.Run最终调用了之前已经初始化了的BeeApp,


// Run beego application.

func (app *App) Run() {

    ...

    app.Server.Handler = app.Handlers

    app.Server.ReadTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second

    app.Server.WriteTimeout = time.Duration(BConfig.Listen.ServerTimeOut) * time.Second

    app.Server.ErrorLog = logs.GetLogger("HTTP")


    // run graceful mode

    if BConfig.Listen.Graceful {

        httpsAddr := BConfig.Listen.HTTPSAddr

        app.Server.Addr = httpsAddr

        if BConfig.Listen.EnableHTTPS {

            go func() {

                ...

                if err := server.ListenAndServeTLS(BConfig.Listen.HTTPSCertFile, BConfig.Listen.HTTPSKeyFile); err != nil {

                    logs.Critical("ListenAndServeTLS: ", err, fmt.Sprintf("%d", os.Getpid()))

                    time.Sleep(100 * time.Microsecond)

                    endRunning <- true

                }

            }()

        }

        if BConfig.Listen.EnableHTTP {

            go func() {

                ...

                if err := server.ListenAndServe(); err != nil {

                    logs.Critical("ListenAndServe: ", err, fmt.Sprintf("%d", os.Getpid()))

                    time.Sleep(100 * time.Microsecond)

                    endRunning <- true

                }

            }()

        }

        <-endRunning

        return

    }

    ...

}


我们知道http.ListenAndServe逻辑如下:


func ListenAndServe(addr string, handler Handler) error {

    server := &Server{Addr: addr, Handler: handler}

    return server.ListenAndServe()

}


app.Server是系统*http.Server,将Handlers传入app.Server.Handler即是构建了一个http.ListenAndServe中的server,然后在接下来调用server.ListenAndServe。至此,我们可以看到beego的启动与go中的完全一致。只是在server构建前完成了一系列的routers的存储以及响应时的匹配


文章转载自golang算法架构leetcode技术php,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论