Iris框架源碼閱讀和分析

iris包結構簡介

iris包含了不少包,下面這些是分析過程當中接觸到的東西。
能力有限,多多包涵,歡迎聯繫QQ:2922530320 一塊兒交流html

context包包含:

  • Context (接口)node

  • context (struct)git

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

    // 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包: 定義錯誤類型api

  • handlerconv包: handler轉換,從標準的handler轉換爲context.Handler數組

  • host包: 包含了Supervisor,用於管理http.Server對象,每一個Supervisor管理一個http.Server對象,實現了onServe,onErr,onShutdown等操做。session

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

    提供了RegisterOnError, RegisterOnServe等方法, 將一些自定義的方法添加到onServe變量中。mvc

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

    TaskHost包含了當前Supervisor的信息,也就是說,本身定義的這個方法,能夠在執行Serve方法以前隨心所欲。app

    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)
相關文章
相關標籤/搜索