golang iris mvc框架的服务端加载过程

栏目: Go · 发布时间: 6年前

内容简介:整个iris框架共三层结构:iris框架包装了Server为更完善的Supervisor类,数据结构如下:调用NewHost(srv)他建Supervisor实例。先初始化以下字段:

整个iris框架共三层结构:

  • 应用的配置和注册信息,如路由、中间件、日志。
  • 中间的服务端实例,从iris实例拿配置信息进行配置。
  • 底层net/http包,负责TCP连接建立、监听接受,请求收取并解析,缓冲区管理,写入响应。

监听服务的闭包构建

iris框架包装了Server为更完善的Supervisor类,数据结构如下:

type Supervisor struct {
    Server         *http.Server
    closedManually int32 
    manuallyTLS    bool  server begin.
    shouldWait     int32 
    unblockChan    chan struct{}

    mu sync.Mutex

    onServe []func(TaskHost)
    IgnoredErrors []string
    onErr         []func(error)
    onShutdown    []func()
}

调用NewHost(srv)他建Supervisor实例。先初始化以下字段:

srv.Addr
srv.Handler //app.Router
su.unblockChan //chan struct{}

关闭ShutdownGracefully

RegisterOnServe,注册一个向日志打印闭包。按下CMD+C中断的闭包。全局中断注册地:

// onInterrupt contains a list of the functions that should be called when CTRL+C/CMD+C or a unix kill //command received.
var Interrupt = new(interruptListener)

type interruptListener struct {
    mu   sync.Mutex
    once sync.Once

    onInterrupt []func()
}

具体实现的闭包是RegisterOnInterrupt:

先设置shutdownTimeout超时
su.closedManually++ //要用atomic包原子操作

su.onShutdown[:]() //执行所有的闭包
su.Server.Shutdown(ctx)//net/http标准库方法

//RestoreFlow
su.shouldWait=0 //atomic
su.unblockChan <- //mutex

//defer
ctx.cancel()

优雅关闭流程:

  • 先关闭open listeners
  • 关闭idle connections
  • 等待连接至idle再关闭
  • 忽略hijacked connections。不主动关闭,仅通知其关闭

Deferflow和shoudlWait标志位

func (su *Supervisor) DeferFlow() {
    atomic.StoreInt32(&su.shouldWait, 1)
}
func (su *Supervisor) isWaiting() bool {
    return atomic.LoadInt32(&su.shouldWait) != 0
}

配合unblockChan实现手动关闭su实例的功能。

if su.isWaiting() {
    blockStatement:
        for {
            select {
            case <-su.unblockChan:
                break blockStatement
            }
        }
    }

使用样例:

su.DeferFlow() //不关闭直到主动调用

su.Shutdown(ctx) //关闭
su.RestoreFlow()
ctx.cancel()

su.closedManually==1表示主动调用过su.Shutdown()方法。

导入app.config.IgnoreServerErrors。

由app加载配置,iris并没有使用一个字段的struct。而是使用了闭包的写法。直接修改Supervisor实例,改变配置,减少数据传递提高效率。

[]Configurator func(su *Supervisor)

共有两次加载配置,一次是NewHost内部,调用su.Configure(app.hostConfigurators...),第二次外部调用su.Configure(hostConfigurators...)。

最后把Supervisor实例注册到app.Hosts。

ListenAndServe创建监听和服务

创建监听逻辑:

func (su *Supervisor) newListener() (net.Listener, error) {
    l, err := netutil.TCPKeepAlive(su.Server.Addr)
    if err != nil {
        return nil, err
    }

    if netutil.IsTLS(su.Server) {
        // means tls
        tlsl := tls.NewListener(l, su.Server.TLSConfig)
        return tlsl, nil
    }
    
    return l, nil
}
func TCPKeepAlive(addr string) (ln net.Listener, err error) {
    ln, err = TCP(addr)
    if err != nil {
        return nil, err
    }
    return tcpKeepAliveListener{ln.(*net.TCPListener)}, nil
}
func TCP(addr string) (net.Listener, error) {
    l, err := net.Listen("tcp", addr)
    if err != nil {
        return nil, err
    }
    return l, nil
}

iris框架把net包封装成了更易用的netutil包。方便创建keepAlive TLS letsencrypt.org 的监听。API如下:

(l tcpKeepAliveListener) Accept() (c net.Conn, err error)
TCP(addr string) (net.Listener, error)
TCPKeepAlive(addr string) (ln net.Listener, err error)
TLS(addr, certFile, keyFile string) (net.Listener, error)
CERT(addr string, cert tls.Certificate) (net.Listener, error)
UNIX(socketFile string, mode os.FileMode) (net.Listener, error)
LETSENCRYPT(addr string, serverName string, cacheDirOptional ...string) (net.Listener, error)

由netutil包建立的TCP常连接keepalive时间为3分钟。

调用su.Serve(l)方法即调用net/http标准库中的su.Server.Serve(l)。

框架将ListenAndServe及supervisor构建包装成了闭包。由于闭包内的代码需要iris实例和supervisor实例均已构建完成之后执行,所以封装成闭包,等前iris对象实例化之后再调用。

构建iris实例并运行服务

前文说明

前几篇文章已详述过,在iris服务端运行之前。iris实例已经加载了handler和middleware。建立好了ctx的对象池,这里的对象池,是iris自定义的实例,包裹了http请求、响应以及其它连接参数,不同于TCP连接时的KV参数,ctx cancelCtx timerCtx等。日志加载和配置信息加载,将在调用Runner闭包时执行。

实际iris框架,在Runner闭包调用前后,共对iris服务端实例进行了三次配置:

  • 注册阶段的配置
  • Run()阶段调用app.Build,功能:builds the default router and the template function。
  • Runner闭包内加载func Configurator,功能是配置*Server实例及其包裹实例。仍由少量信息反馈给iris实例。如包裹*Server的Supervisor实例本身,就会被注册到app.Host字段。(可能多实例,要考虑到并发)

在调用app.Build()之前,app.APIBuilder字段已构建完成,包括了路由信息。ApiBuider是一个路由构建器,提供了路由构建的接口:

type RoutesProvider interface {
    GetRoutes() []*Route
    GetRoute(routeName string) *Route
}

路由按路径及其子路径。路由的存储结构如下:

type routerHandler struct {
    trees []*tree
    hosts bool // true if at least one route contains a Subdomain.
}
type tree struct {
    Method string
    Subdomain string
    Nodes     *node.Nodes
}
type node struct {
    s                 string
    routeName         string
    wildcardParamName string   
    paramNames        []string 
    childrenNodes     Nodes
    handlers          context.Handlers
    root              bool
    rootWildcard      bool 
}

每一段路由路径,都包含了一系列context.Handlers。路由数组,只要执行第一向就会向后链式执行,并包裹子节点的路由。

经过以上复杂的准备,可以执行构建路由了。

最后处理app.view。routeProvider.Path()方法功能是由路由名解析出路由路径,其中参数用%v代替。

构建routeHandler

先对路由排序,排序规则是:subDomain长的在前。如果子域长度相等且,则第一段路径在前。如果仍相等,则请求路径中参数较多的在前。

路由中handler的 排序 为:beginHandlers Handlers doneHandlers。

接下来完成将路由route添加到routerHandler。路由是用树的数组保存的,每个树是同一类请求方法,同一个子域,节点是树状图。每个节点包含了:一段路径 参数名字列表 handlers 子节点。

再看路由的数据结构:

type Route struct {
    Name       string       
    Method     string        
    methodBckp string        
    Subdomain  string   
    
    tmpl       *macro.Template 
    beginHandlers context.Handlers
    Handlers        context.Handlers 
    MainHandlerName string          
    doneHandlers context.Handlers
    Path         string 
    FormattedPath string `json:"formattedPath"`
}

先由method, subdomain找到所在tree,再根据path, handler添加路由树中。

为什么要三次构建路由

这里区别APIBuilder和routerHandler保存路的方式。以及为什么要三次构建路由。

在mvc controller中:http请求方式 http请求路径 subdomain(就是controller类的RequestMapping) 一组handlers,共同构成了一条路由路径。在注册controller时,以列表的方式加入到APIBuilder,是工作量最小的保存方式。

但这样有问题,在服务端拿到httpRequest时,查找路由的效率极低,需要遍历整个个数组。因而需要将路由表按subdomain+path构成树状结构,加快查找效率。

整个过程,由控制器添加线性的路由表,并转成树状路由表是在服务端接受http请求之前完成,不会影响http处理效率。接受请求之后,遍历树进行查找,命中则重新构建一条路径,处理http请求。

树状路由添加节点

执行方法:

t.Nodes.Add(routeName, path, handlers)

收尾工作

路由自带一个context的sync.pool,复用对象加快httpRequest的构建。

保留路由构建工具APIBuilder作为一个字段。

最后作为Server.Handler类型,要实现ServeHTTP接口。

router.mainHandler = func(w http.ResponseWriter, r *http.Request) {
        ctx := cPool.Acquire(w, r)
        router.requestHandler.HandleRequest(ctx)
        cPool.Release(ctx)
    }

    if router.wrapperFunc != nil { 
        router.mainHandler = NewWrapper(router.wrapperFunc, router.mainHandler).ServeHTTP
    }

需要说明的是router本身也实现了Hanler接口,可以调用router.ServeHTTP()。但是这样使用不会复用contex对象, 会带来性能损耗

将符合net/http包Server.Handler接口的实现,内部交由requestHandler.HandleRequest实现。Router是对requestHandler的包装:

type Router struct {
    mu sync.Mutex 
    requestHandler RequestHandler   
    mainHandler    http.HandlerFunc 
    wrapperFunc    func(http.ResponseWriter, *http.Request, http.HandlerFunc)

    cPool          *context.Pool 
    routesProvider RoutesProvider
}

如果使用了包裹,先执行包裹代码。包裹哪些代码,被包裹的代码段,可以自由配置,利益于闭包的使用。将两个闭包打包成一个struct{},再实现一个类方法,造知这两个闭包的代码以何种顺序执行。如果没有闭包,函数是写死在包裹中的,不能更换,进而会增加许多重复的代码。

type wrapper struct {
    router      http.HandlerFunc 
    wrapperFunc func(http.ResponseWriter, *http.Request, http.HandlerFunc)
}
func (wr *wrapper) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    wr.wrapperFunc(w, r, wr.router)
}

更进一步,源代码中的WrapRouter方法实现了两层包裹。


以上所述就是小编给大家介绍的《golang iris mvc框架的服务端加载过程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

函数响应式领域建模

函数响应式领域建模

【美】Debasish Ghosh / 李源 / 电子工业出版社 / 2018-1 / 79

传统的分布式应用不会切入微服务、快速数据及传感器网络的响应式世界。为了捕获这些应用的动态联系及依赖,我们需要使用另外一种方式来进行领域建模。由纯函数构成的领域模型是以一种更加自然的方式来反映一个响应式系统内的处理流程,同时它也直接映射到了相应的技术和模式,比如Akka、CQRS 以及事件溯源。《函数响应式领域建模》讲述了响应式系统中建立领域模型所需要的通用且可重用的技巧——首先介绍了函数式编程和响......一起来看看 《函数响应式领域建模》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换