Iris框架源码阅读和分析

时间:2022-03-04 20:22:00

iris包结构简介

iris包含了很多包,下面这些是分析过程中接触到的东西。
能力有限,多多包涵,欢迎联系QQ:2922530320 一起交流

context包包含:

  • Context (接口)

  • context (struct)

  • Pool (context池), Pool的newFunc返回一个Context。

    // context.New()
    func New(newFunc func() Context) *Pool {
      c := &Pool{pool: &sync.Pool{}, newFunc: newFunc}
      c.pool.New = func() interface{} { return c.newFunc() }
      return c
    }
    // app.New()中的片段
    app.ContextPool = context.New(func() context.Context {
      // 每次获取Context对象,都会把Application对象注入进去。
      return context.NewContext(app)
    })
    func NewContext(app Application) Context {
      return &context{app: app}
    }

core包包含:

  • errors包: 定义错误类型

  • handlerconv包: handler转换,从标准的handler转换为context.Handler

  • host包: 包含了Supervisor,用于管理http.Server对象,每个Supervisor管理一个http.Server对象,实现了onServe,onErr,onShutdown等操作。

    onServe     []func(TaskHost)
    onErr         []func(error)
    onShutdown    []func()

    提供了RegisterOnError, RegisterOnServe等方法, 将一些自定义的方法添加到onServe变量中。

    func (su *Supervisor) RegisterOnServe(cb func(TaskHost)) {
      su.mu.Lock()
      su.onServe = append(su.onServe, cb)
      su.mu.Unlock()
    }

    TaskHost包含了当前Supervisor的信息,也就是说,自己定义的这个方法,可以在执行Serve方法之前为所欲为。

    host包 还包含了处理命令行信号的方法 Interrupt

    还提供了反向代理的方法。NewProxy返回的是Supervisor对象,算是Supervisor的一个构造方法。

    func NewProxy(hostAddr string, target *url.URL) *Supervisor {
      proxyHandler := ProxyHandler(target)
      proxy := New(&http.Server{
          Addr:    hostAddr,
          // 这里实际上是调用了标准库中的反向代理。net/http/httputil包
          Handler: proxyHandler,
      })
    
      return proxy
    }
  • memstore 一个key/value的内存存储引擎,应该算是整个框架的小组件,先不管它。

  • netutil 工具包,先不管他。

  • router包: 管理和处理路由的包

    repository 包含Route数组,用来注册路由到repository。当然也提供了get方法。

    func (r *repository) register(route *Route) {
      for _, r := range r.routes {
          if r.String() == route.String() {
              return // do not register any duplicates, the sooner the better.
          }
      }
    
      r.routes = append(r.routes, route)
    }

    Route 包含 Name, Method, Path,FormattedPath Subdomain, template, beginHandlers, Handlers, doneHandlers等成员。用来存储路由信息,和一些基本操作。

    Router 实现了http.Handler。

    // ServeHTTPC serves the raw context, useful if we have already a context, it by-pass the wrapper.
    func (router *Router) ServeHTTPC(ctx context.Context) {
      router.requestHandler.HandleRequest(ctx)
    }
    
    func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
      router.mainHandler(w, r)
    }

    上面的ServeHTTPC函数调用了requestHandler.HandleRequest。

     type RequestHandler interface {
        // HandleRequest should handle the request based on the Context.
        HandleRequest(context.Context)
        // Build should builds the handler, it's being called on router's BuildRouter.
        Build(provider RoutesProvider) error
        // RouteExists reports whether a particular route exists.
        RouteExists(ctx context.Context, method, path string) bool
     }

    routerHandler实现了RequestHandler接口,

     type routerHandler struct {
        trees []*trie
        hosts bool // true if at least one route contains a Subdomain.
     }

    trie是一个查找树,包含 trieNode, trieNode就是整个路由树的节点。具体细节以后再分析。

     type trie struct {
        root *trieNode
    
        // if true then it will handle any path if not other parent wildcard exists,
        // so even 404 (on http services) is up to it, see trie#insert.
        hasRootWildcard bool
        hasRootSlash    bool
    
        method string
        // subdomain is empty for default-hostname routes,
        // ex: mysubdomain.
        subdomain string
     }
     type trieNode struct {
      parent *trieNode
    
      children               map[string]*trieNode
      hasDynamicChild        bool     // does one of the children contains a parameter or wildcard?
      childNamedParameter    bool     // is the child a named parameter (single segmnet)
      childWildcardParameter bool     // or it is a wildcard (can be more than one path segments) ?
      paramKeys              []string // the param keys without : or *.
      end                    bool     // it is a complete node, here we stop and we can say that the node is valid.
      key                    string   // if end == true then key is filled with the original value of the insertion's key.
      // if key != "" && its parent has childWildcardParameter == true,
      // we need it to track the static part for the closest-wildcard's parameter storage.
      staticKey string
    
      // insert data.
      Handlers  context.Handlers
      RouteName string
     }

    Party接口, 是一个路由的分组,可以把它叫做Group,作者起Party这个名字是为了有趣,一群狐朋狗友聚在一起。总之,看到Party这个单词,默认替换成Group就好理解了。

    APIBuilder实现了Party接口。使用Party添加路由是一种方式,那如果不分组,怎么添加路由呢。Party可以使用Use, UseGlobal, Done等添加middleware。

    // 这一类的方法返回一个Route对象指针。底层调用的是APIBuilder.Handle()方法。
    func (api *APIBuilder) Get(relativePath string, handlers ...context.Handler) *Route {
       return api.Handle(http.MethodGet, relativePath, handlers...)
    }

iris包

Application继承了 router.APIBuilder 和 router.Router , 又封装了配置Configuration, 日志golog.Logger, 视图 view.View等。

iris启动过程分析

iris.Application对象创建完成之后,调用Run来启动程序,先上一下Application.Run()代码

func (app *Application) Run(serve Runner, withOrWithout ...Configurator) error {
    // first Build because it doesn't need anything from configuration,
    // this gives the user the chance to modify the router inside a configurator as well.
    if err := app.Build(); err != nil {
        return errors.PrintAndReturnErrors(err, app.logger.Errorf)
    }

    app.Configure(withOrWithout...)
    app.logger.Debugf("Application: running using %d host(s)", len(app.Hosts)+1)

    // this will block until an error(unless supervisor's DeferFlow called from a Task).
    err := serve(app)
    if err != nil {
        app.Logger().Error(err)
    }

    return err
}

Run方法会执行err := serve(app) Runner, Runner的类型是type Runner func(*Application) error

iris中构造Runner的方法有:

func Listener(l net.Listener, hostConfigs ...host.Configurator) Runner

func Server(srv *http.Server, hostConfigs ...host.Configurator) Runner,

func Addr(addr string, hostConfigs ...host.Configurator) Runner,

func TLS(addr string, certFile, keyFile string, hostConfigs ...host.Configurator) Runner,

func AutoTLS(
   addr string,
   domain string, email string,
   hostConfigs ...host.Configurator) Runner

以上方法就是提供了多种构造http.Server的方法。这些方法最后都调用Supervisor.Serve方法,来监听http服务。Run之后的过程比较简单,在执行Run之前,执行很多框架初始化的工作。

添加路由、注册路由

由于Application继承了router.APIBuilder,所以添加路由的方法是直接调用APIBuilder的方法,大致有Get,Post,Put,Delete,Any等。另外还有用于路由分组的Party方法。Party方法返回值还是Party接口类型,而APIBuilder实现了Party接口,所以Party方法返回的还是APIBuilder对象。

也就是说,Party注册的是路由分组,其中handlers参数仅仅是用户添加中间件,后续还可以再进一步添加子路由, Get,Put这类方法直接返回Route类型,并注册handlers到routes *repository,后续不能再添加路由了。

func (api *APIBuilder) Party(relativePath string, handlers ...context.Handler) Party {

这些方法一般返回一个对象指针,仅仅是为了方便链式操作。因为调用Get,Put这些方法的时候,已经将路由添加进去,我们也可以忽略返回值。

这些方法统一调用api.Handle(http方法, 相对路径, handlers...) 方法

func (api *APIBuilder) Put(relativePath string, handlers ...context.Handler) *Route {
    return api.Handle(http.MethodPut, relativePath, handlers...)
}

APIBuilder保存了通过routes *repository保存了所有的路由,使用Get,Put,或者Party的时候,就已经注册好了。

Server关联Handler

从标准库中我们知道,http.Server通过net.Listener监听地址,接受到请求后,将请求交给Server.Handler处理。

type Server struct {
    Addr           string        // 监听的TCP地址,如果为空字符串会使用":http"
    Handler        Handler       // 调用的处理器,如为nil会调用http.DefaultServeMux
    ReadTimeout    time.Duration // 请求的读取操作在超时前的最大持续时间
    WriteTimeout   time.Duration // 回复的写入操作在超时前的最大持续时间
    MaxHeaderBytes int           // 请求的头域最大长度,如为0则用DefaultMaxHeaderBytes
    TLSConfig      *tls.Config   // 可选的TLS配置,用于ListenAndServeTLS方法
    // TLSNextProto(可选地)指定一个函数来在一个NPN型协议升级出现时接管TLS连接的所有权。
    // 映射的键为商谈的协议名;映射的值为函数,该函数的Handler参数应处理HTTP请求,
    // 并且初始化Handler.ServeHTTP的*Request参数的TLS和RemoteAddr字段(如果未设置)。
    // 连接在函数返回时会自动关闭。
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
    // ConnState字段指定一个可选的回调函数,该函数会在一个与客户端的连接改变状态时被调用。
    // 参见ConnState类型和相关常数获取细节。
    ConnState func(net.Conn, ConnState)
    // ErrorLog指定一个可选的日志记录器,用于记录接收连接时的错误和处理器不正常的行为。
    // 如果本字段为nil,日志会通过log包的标准日志记录器写入os.Stderr。
    ErrorLog *log.Logger
    // 内含隐藏或非导出字段
}

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

Server.Hander 类型是http.Handler接口, 实现了这个接口的struct都可以赋值给Server.Handler。

上面说Route实现了http.Handler接口,再看Application启动时的代码:

app.Run(iris.Addr(":8888"), iris.WithoutInterruptHandler)
// app.Run会调用Addr方法。
func Addr(addr string, hostConfigs ...host.Configurator) Runner {
    return func(app *Application) error {
        return app.NewHost(&http.Server{Addr: addr}).
            Configure(hostConfigs...).
            ListenAndServe()
    }
}
func (app *Application) NewHost(srv *http.Server) *host.Supervisor {
    app.mu.Lock()
    defer app.mu.Unlock()

    // set the server's handler to the framework's router
    if srv.Handler == nil {
        srv.Handler = app.Router
    }

srv.Handler = app.Router 把 Handler和 Router对象关联起来(Router是Application的父类)。

iris 接受请求并处理过程

iris是如何接受到请求,并把request和response封装成iris.context.Context的?

上面说到,所有的请求都会先经过router.Router.ServeHTTP处理。

func (router *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   router.mainHandler(w, r)
}

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
}

mainHandler是一个http.HandleFunc, 下面是mainHandler的初始化代码

    router.mainHandler = func(w http.ResponseWriter, r *http.Request) {
        ctx := cPool.Acquire(w, r)
        router.requestHandler.HandleRequest(ctx)
        cPool.Release(ctx)
    }
// 如果有wrapperFunc,会调用wrapperFunc初始化mainHandler
    if router.wrapperFunc != nil { // if wrapper used then attach that as the router service
        router.mainHandler = NewWrapper(router.wrapperFunc, router.mainHandler).ServeHTTP
    }

上面说到,ctx := cPool.Acquire(w, r)会从ContextPool中获取一个Context对象。

在获取context对象之前,调用了BeginRequest方法。

func (ctx *context) BeginRequest(w http.ResponseWriter, r *http.Request) {
    ctx.handlers = nil           // will be filled by router.Serve/HTTP
    ctx.values = ctx.values[0:0] // >>      >>     by context.Values().Set
    ctx.params.Store = ctx.params.Store[0:0]
    ctx.request = r
    ctx.currentHandlerIndex = 0
    ctx.writer = AcquireResponseWriter()
    ctx.writer.BeginResponse(w)
}

在mainHandler中执行router.requestHandler.HandleRequest(ctx)

requesthandler是从那里初始化的呢?

routerHandler := router.NewDefaultHandler()

rp.Describe("router: %v", app.Router.BuildRouter(app.ContextPool, routerHandler, app.APIBuilder, false))

// BuildRouter中会初始化requestHandler
router.requestHandler = requestHandler

requestHandler实现了RequestHandler接口。那么,requestHandler.HandlerRequest到底是个什么东西? 就是下面这一堆,主要是路由的操作。看的头晕了,之后再详细分析具体的路由实现。

type RequestHandler interface {
    // HandleRequest should handle the request based on the Context.
    HandleRequest(context.Context)
    // Build should builds the handler, it's being called on router's BuildRouter.
    Build(provider RoutesProvider) error
    // RouteExists reports whether a particular route exists.
    RouteExists(ctx context.Context, method, path string) bool
}

func (h *routerHandler) HandleRequest(ctx context.Context) {
    method := ctx.Method()
    path := ctx.Path()
    if !ctx.Application().ConfigurationReadOnly().GetDisablePathCorrection() {

        if len(path) > 1 && strings.HasSuffix(path, "/") {
            // Remove trailing slash and client-permanent rule for redirection,
            // if confgiuration allows that and path has an extra slash.

            // update the new path and redirect.
            r := ctx.Request()
            // use Trim to ensure there is no open redirect due to two leading slashes
            path = "/" + strings.Trim(path, "/")

            r.URL.Path = path
            if !ctx.Application().ConfigurationReadOnly().GetDisablePathCorrectionRedirection() {
                // do redirect, else continue with the modified path without the last "/".
                url := r.URL.String()

                // Fixes https://github.com/kataras/iris/issues/921
                // This is caused for security reasons, imagine a payment shop,
                // you can't just permantly redirect a POST request, so just 307 (RFC 7231, 6.4.7).
                if method == http.MethodPost || method == http.MethodPut {
                    ctx.Redirect(url, http.StatusTemporaryRedirect)
                    return
                }

                ctx.Redirect(url, http.StatusMovedPermanently)

                // RFC2616 recommends that a short note "SHOULD" be included in the
                // response because older user agents may not understand 301/307.
                // Shouldn't send the response for POST or HEAD; that leaves GET.
                if method == http.MethodGet {
                    note := "<a href=\"" +
                        html.EscapeString(url) +
                        "\">Moved Permanently</a>.\n"

                    ctx.ResponseWriter().WriteString(note)
                }
                return
            }

        }
    }

    for i := range h.trees {
        t := h.trees[i]
        if method != t.method {
            continue
        }

        if h.hosts && t.subdomain != "" {
            requestHost := ctx.Host()
            if netutil.IsLoopbackSubdomain(requestHost) {
                // this fixes a bug when listening on
                // 127.0.0.1:8080 for example
                // and have a wildcard subdomain and a route registered to root domain.
                continue // it's not a subdomain, it's something like 127.0.0.1 probably
            }
            // it's a dynamic wildcard subdomain, we have just to check if ctx.subdomain is not empty
            if t.subdomain == SubdomainWildcardIndicator {
                // mydomain.com -> invalid
                // localhost -> invalid
                // sub.mydomain.com -> valid
                // sub.localhost -> valid
                serverHost := ctx.Application().ConfigurationReadOnly().GetVHost()
                if serverHost == requestHost {
                    continue // it's not a subdomain, it's a full domain (with .com...)
                }

                dotIdx := strings.IndexByte(requestHost, '.')
                slashIdx := strings.IndexByte(requestHost, '/')
                if dotIdx > 0 && (slashIdx == -1 || slashIdx > dotIdx) {
                    // if "." was found anywhere but not at the first path segment (host).
                } else {
                    continue
                }
                // continue to that, any subdomain is valid.
            } else if !strings.HasPrefix(requestHost, t.subdomain) { // t.subdomain contains the dot.
                continue
            }
        }
        n := t.search(path, ctx.Params())
        if n != nil {
            ctx.SetCurrentRouteName(n.RouteName)
            ctx.Do(n.Handlers)
            // found
            return
        }
        // not found or method not allowed.
        break
    }

    if ctx.Application().ConfigurationReadOnly().GetFireMethodNotAllowed() {
        for i := range h.trees {
            t := h.trees[i]
            // if `Configuration#FireMethodNotAllowed` is kept as defaulted(false) then this function will not
            // run, therefore performance kept as before.
            if h.subdomainAndPathAndMethodExists(ctx, t, "", path) {
                // RCF rfc2616 https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
                // The response MUST include an Allow header containing a list of valid methods for the requested resource.
                ctx.Header("Allow", t.method)
                ctx.StatusCode(http.StatusMethodNotAllowed)
                return
            }
        }
    }

    ctx.StatusCode(http.StatusNotFound)
}

MVC

iris还支持MVC组件, 目测是使用reflect自动寻找相应的方法名,来进行路由注册。

mvc.Application暴漏出来的接口还是很简单的:

type Application struct {
    Dependencies di.Values
    Router       router.Party
}
// mvc.Application的配置方法
func (app *Application) Configure(configurators ...func(*Application)) *Application
// 注册以来的方法
func (app *Application) Register(values ...interface{}) *Application
// 将struct添加到路由的方法
func (app *Application) Handle(controller interface{}) *Application
// 克隆一个mvc.Application对象
func (app *Application) Clone(party router.Party) *Application
// 配置路由分组的方法
func (app *Application) Party(relativePath string, middleware ...context.Handler) *Application

mvc.Application的构造方法:

func New(party router.Party) *Application
func Configure(party router.Party, configurators ...func(*Application)) *Application

看一个栗子

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

func main() {
    app := iris.New()
    mvc.Configure(app.Party("/root"), myMVC)
    app.Run(iris.Addr(":8080"))
}

func myMVC(app *mvc.Application) {
    // app.Register(...)
    // app.Router.Use/UseGlobal/Done(...)
    app.Handle(new(MyController))
}

type MyController struct {}

func (m *MyController) BeforeActivation(b mvc.BeforeActivation) {
    // b.Dependencies().Add/Remove
    // b.Router().Use/UseGlobal/Done // and any standard API call you already know

    // 1-> Method
    // 2-> Path
    // 3-> The controller's function name to be parsed as handler
    // 4-> Any handlers that should run before the MyCustomHandler
    b.Handle("GET", "/something/{id:long}", "MyCustomHandler", anyMiddleware...)
}

// GET: http://localhost:8080/root
func (m *MyController) Get() string { return "Hey" }

// GET: http://localhost:8080/root/something/{id:long}
func (m *MyController) MyCustomHandler(id int64) string { return "MyCustomHandler says Hey" }

使用方法

先把使用方法搞上吧,下面是网上照抄的,对不住了。

func(c *MyController) BeforeActivation(b mvc.BeforeActivation) {
    b.Dependencies().Add/Remove(...)
}

访问控制器的字段 Context (不需要手动绑定)如 Ctx iris.Context 或者当作方法参数输入,如 func(ctx iris.Context, otherArguments...)

控制器内部结构模型(在方法函数中设置并由视图呈现)。你可以从控制器的方法中返回模型,或者在请求生命周期中设置字段,并在相同的请求生命周期中将该字段返回到另一个方法。

正如你以前用过的一样,MVC 应用程序有自己的 Router ,它是一个 iris/router.Party 类型的标准 iris api。当 iris/router.Party 如期望的那样开始执行处理程序的时候 , Controllers 可以注册到任意的 Party,包括子域。

BeginRequest(ctx) 是一个可选函数,经常用在方法执行前,执行一些初始化操作,当调用中间件或者有很多方法使用相同的数据集合的时候非常有用。

EndRequest(ctx) 是一个可选函数,经常用在方法执行后,执行一些初始化操作。

继承,递归,例如我们的 mvc.SessionController,它有 Session *sessions.SessionManager *sessions.Sessions 作为嵌入式字段,由 BeginRequest 加载, 这里. 这只是一个例子,你可以用使用管理器的 Start 返回的 sessions.Session, 作为对 MVC 应用程序的动态依赖项。 如mvcApp.Register(sessions.New(sessions.Config{Cookie: "iris_session_id"}).Start).

通过控制器方法的输入参数访问动态路径参数,不需要绑定。当你使用 iris 的默认语法来解析控制器处理程序时,你需要在方法后加上 "."字符,大写字母是一个新的子路径。 例子:

如果 mvc.New(app.Party("/user")).Handle(new(user.Controller))

  • func(*Controller) Get() - GET:/user.
  • func(*Controller) Post() - POST:/user.
  • func(*Controller) GetLogin() - GET:/user/login
  • func(*Controller) PostLogin() - POST:/user/login
  • func(*Controller) GetProfileFollowers() - GET:/user/profile/followers
  • func(*Controller) PostProfileFollowers() - POST:/user/profile/followers
  • func(*Controller) GetBy(id int64) - GET:/user/{param:long}
  • func(*Controller) PostBy(id int64) - POST:/user/{param:long}

如果 mvc.New(app.Party("/profile")).Handle(new(profile.Controller))

  • func(*Controller) GetBy(username string) - GET:/profile/{param:string}

如果 mvc.New(app.Party("/assets")).Handle(new(file.Controller))

  • func(*Controller) GetByWildard(path string) - GET:/assets/{param:path}

    方法函数接收器支持的类型: int,int64, bool 和 string。

可选的响应输出参数,如下:

func(c *ExampleController) Get() string |
                                (string, string) |
                                (string, int) |
                                int |
                                (int, string) |
                                (string, error) |
                                error |
                                (int, error) |
                                (any, bool) |
                                (customStruct, error) |
                                customStruct |
                                (customStruct, int) |
                                (customStruct, string) |
                                mvc.Result or (mvc.Result, error)