iris包含了不少包,下面這些是分析過程當中接觸到的東西。
能力有限,多多包涵,歡迎聯繫QQ:2922530320 一塊兒交流html
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} }
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...) }
Application繼承了 router.APIBuilder 和 router.Router , 又封裝了配置Configuration, 日誌golog.Logger, 視圖 view.View等。
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的時候,就已經註冊好了。
從標準庫中咱們知道,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的父類)。
上面說到,全部的請求都會先通過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) }
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.Session
和 Manager *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)