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.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)