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)