安装
go get -u github.com/kataras/iris
若遇到下面这种情况:
可删除保存路径中的kataras目录,并执行go get github.com/kataras/iris/v12@master
快速开始
创建一个空文件,假设它的名字是example.go,然后打开它并复制粘贴下面的代码。
package main
import "github.com/kataras/iris/v12"
func main() {
app := iris.Default()
app.Use(myMiddleware) // 使用自定义的中间件,这里和gin很相似
app.Handle("GET", "/ping", func(ctx iris.Context) {
ctx.JSON(iris.Map{"message": "pong"})
})
// 监听8080端口
app.Listen(":8080")
}
func myMiddleware(ctx iris.Context) {
ctx.Application().Logger().Infof("Runs before %s", ctx.Path())
ctx.Next()
}
Show me more
package main
import "github.com/kataras/iris/v12"
func main() {
app := iris.New()
// 加载./view文件夹下所有的模板文件,如果它们的后缀是./html,则解析它们
// 使用标准的' html/template '包
app.RegisterView(iris.HTML("./views", ".html"))
// Method: GET
// Resource: http://localhost:8080
app.Get("/", func(ctx iris.Context) {
// 绑定kv
ctx.ViewData("message", "Hello world!")
// 渲染模板文件./views.hello.html
ctx.View("hello.html")
})
// Method: GET
// Resource: http://localhost:8080/user/42
// 获取GET路径上的param
app.Get("/user/{id:uint64}", func(ctx iris.Context) {
userID, _ := ctx.Params().GetUint64("id")
ctx.Writef("User ID: %d", userID)
})
// 监听端口
app.Listen(":8080")
}
<!-- file: ./views/hello.html -->
<html>
<head>
<title>Hello Page</title>
</head>
<body>
<h1>{{.message}}</h1>
</body>
</html>
路由
Handler
一个Handler,正如其名,用于处理请求。
一个处理程序用于响应 HTTP 请求。它把响应头和数据写入 Context.ResponseWriter() 然后返回请求完成的的信号。
在请求完成之后是不能使用上下文的。
取决于 HTTP 客户端软件,HTTP 协议版本,以及任何客户端和服务器之间的中间件,在写入 context.ResponseWriter() 之后可能无法从 Context.Request().Body 读取内容。严谨的处理程序应该首先读取 Context.Request().Body ,然后再响应。除了读取请求体,处理程序不应该更改修改提供的上下文。如果处理程序崩溃掉,服务器任务崩溃只是影响了当前的请求。恢复之后,记录错误日志中然后挂断连接。
type Handler func(Context)
API
所有的 HTTP 方法都支持,开发者可以为相同路径的处理程序注册不同的方法。
第一个参数是 HTTP 方法,第二个参数是路由路径,第三个可变参数应该包含一个或者多个 iris.Handler,当获取某个资源的请求从服务器到来时,处理器按照注册顺序被调用执行。
示例代码:
app := iris.New()
app.Handle("GET","/contact",func(ctx iris.Context){
ctx.HTML("<h1>Hello from /contact </h1>")
})
为了让开发者更容易开发处理程序,iris 提供了所有的 HTTP 方法。第一个参数是路由路径,第二个可变参数是一个或者多个 iris.Handler。
示例代码:
app := iris.New()
// 方法: "GET"
app.Get("/", handler)
// 方法: "POST"
app.Post("/", handler)
// 方法: "PUT"
app.Put("/", handler)
// 方法: "DELETE"
app.Delete("/", handler)
// 方法: "OPTIONS"
app.Options("/", handler)
// 方法: "TRACE"
app.Trace("/", handler)
// 方法: "CONNECT"
app.Connect("/", handler)
// 方法: "HEAD"
app.Head("/", handler)
// 方法: "PATCH"
app.Patch("/", handler)
// 用于所有 HTTP 方法
app.Any("/", handler)
func handler(ctx iris.Context){
ctx.Writef("Hello from method: %s and path: %s", ctx.Method(), ctx.Path())
}
路由分组
一组路由可以用前缀路径分组,组之间共享相同的中间件和模板布局,组内可以嵌套组。
.Party 被用于分组路由,开发者可以声明不限数量的分组。
示例代码:
func main() {
app := iris.New()
users := app.Party("/user",MyMiddleware)
users.Get("/{id:int}/profile",userProfileHandler)
users.Get("/messages/{id:int}",userMessageHandler)
app.Run(iris.Addr(":8080"))
}
// 处理器函数
func userProfileHandler(ctx iris.Context){
p,_ := ctx.Params().GetInt("id")
ctx.Writef("userProfileHandler p = %d",p)
}
// 处理器函数
func userMessageHandler(ctx iris.Context){
p, _ := ctx.Params().GetInt("id")
ctx.Writef("userMessageHandler p = %d",p)
}
// 中间件
func MyMiddleware(ctx iris.Context){
ctx.Application().Logger().Infof("Runs before %s",ctx.Path())
ctx.Next()
}
同样也可以这样写,使用一个接收子路由的函数:
app := iris.New()
app.PartyFunc("/users",func(users iris.Party){
users.Use(MyMiddleware)
users.Get("/{id:int}/profile",userProfileHandler)
users.Get("/messages/{id:int}",userMessageHandler)
})
app.Run(iris.Addr(":8080"))
Context机制
上下文是服务器用于所有客户端的中间人“对象”,对于每一个新的链接,都会从sync.Pool中获取一个新上下文对象。
上下文是iris的http流中最重要的部分。
开发者发送响应到客户端的请求通过一个上下文,开发者获取请求信息从客户端的请求上下文中。
context是context.Context子包的实现,context.Context是很好的扩展,所以开发者可以按照实际所需重写它的方法。
type Context interface{
// ResponseWriter 如期返回一个兼容 http.ResponseWriter 的 响应writer。
ResponseWriter() ResponseWriter
// ResetResponseWriter 应该改变或者升级上下文的 ResponseWriter。
ResetResponseWriter(ResponseWriter)
// Request 方法如期返回原始的 *http.Request。
Request() *http.Request
// SetCurrentRouteName 方法设置内部路由名称,为了当开发者调用
// `GetCurrentRoute()` 方法的时候能够正确返回当前 「只读」 路由。
// 它使用 Router 初始化,如果你手动更改了名称,除了当你是使用`GetCurrentRoute()`
// 的时候将获取到其他路由,其它没啥变化。
// 为了从上下文中执行一个不同的路径,你应该使用 `Exec` 函数,
// 或者通过 `SetHandlers/AddHandler` 函数改变处理方法。
SetCurrentRouteName(currentRouteName string)
// GetCurrentRoute 返回当前注册到当前请求路径的 「只读」路由。
GetCurrentRoute() RouteReadOnly
// AddHandler 可以在服务时添加处理方法到当前请求,但是这些处理方法不会持久化到路由。
//
// Router 将会调用这些添加到某个路由的处理方法。如果 AddHandler 被调用,
// 那么处理方法将被添加到已经定义的路由的处理方法的后面。
AddHandler(...Handler)
// SetHandlers 替换所有原有的处理方法。
SetHandlers(Handlers)
// Handlers 记录当前的处理方法。
Handlers() Handlers
// HandlerIndex 设置当前上下文处理方法链中当前索引。
// 如果传入 -1 ,不会当前索引。
//
// 也可以查看 Handlers(), Next() and StopExecution()。
HandlerIndex(n int) (currentIndex int)
// HandlerName 返回当前路由名称,方便调试。
HandlerName() string
// Next 调用从处理方法链中选择剩下的进行调用,他应该被用于一个中间件中。
//
// 提醒:自定义的上下文应该重写这个方法,以便能够传入他自己的 context.Context 实现。
Next()
// NextHandler 从处理链中返回下一个处理方法(但不执行)。
//
// 为了执行下一个 ,可以使用 .Skip() 跳过某个处理方法。
NextHandler() Handler
// Skip 从处理链中 忽略/跳过 下一个处理方法。
// 它应该在中间件内使用。
Skip()
// 如果调用了 StopExecution ,接下来的 .Next 调用将被局略。
StopExecution()
// IsStopped 检查当前位置的Context是否是255, 如果是, 则返回true, 意味着 StopExecution() 被调用了。
IsStopped() bool
// +------------------------------------------------------------+
// | 当前的 "user/request" 存储 |
// | 处理方法之间共享信息 - Values(). |
// | 保存并获取路径参数 - Params() |
// +------------------------------------------------------------+
// Params 返回当前URL中的命名参数。命名的路径参数是被保存在这里的。
// 这个存储对象随着整个上下文,存活于每个请求声明周期。
Params() *RequestParams
// Values 返回当前 「用户」的存储信息。
// 命名路径参数和任何可选数据可以保存在这里。
// 这个存储对象,也是存在于整个上下文,每个请求的声明周期中。
//
// 你可以用这个函数设置和获取用于在处理方法和中间件之间共享信息的局部值。
Values() *memstore.Store
// Translate 是 i18n 函数,用于本地化。它调用 Get("translate") 返回翻译值。
//
// 示例:https://github.com/kataras/iris/tree/master/_examples/miscellaneous/i18n
Translate(format string, args ...interface{}) string
// +------------------------------------------------------------+
// | 路径, 主机, 子域名, IP, HTTP 头 等 |
// +------------------------------------------------------------+
// Method 返回 request.Method, 客户端的请求方法。
Method() string
// Path 返回完整请求路径,如果 EnablePathEscape 为 True,将会转义。
Path() string
// RequestPath 返回转义过的请求完整路径。
RequestPath(escape bool) string
// Host 返回当前URL的主机部分。
Host() string
// Subdomain 返回当前请求的子域名,如果有。
// 提醒,这个方法可能在某些情况下不能正常使用。
Subdomain() (subdomain string)
// RemoteAddr 尝试解析并返回客户端正式IP。
//
// 基于允许的头名称,可以通过 Configuration.RemoteAddrHeaders 修改。
//
// 如果基于这些请求头的解析失败,将会 Request 的 `RemoteAddr` 字段,它在 Http 处理方法之前有 server 填充。
//
// 查看 `Configuration.RemoteAddrHeaders`,
// `Configuration.WithRemoteAddrHeader(...)`,
// `Configuration.WithoutRemoteAddrHeader(...)` 获取更多信息。
RemoteAddr() string
// GetHeader 返回指定的请求头值。
GetHeader(name string) string
// IsAjax 返回这个请求是否是一个 'ajax request'( XMLHttpRequest)。
//
// 不能百分之百确定一个请求是否是Ajax模式。
// 永远不要信任来自客户端的数据,他们很容易被篡改。
//
// 提醒,"X-Requested-With" 头可以被任何客户端修改,对于十分严重的情况,不要依赖于 IsAjax。
// 试试另外的鉴别方式,例如,内容类型(content-type)。
// 有很多描述这些问题的博客并且提供了很多不同的解决方案,这就是为什么说 `IsAjax`
// 太简单,只能用于一般目的。
//
// 更多请看: https://developer.mozilla.org/en-US/docs/AJAX
// 以及: https://xhr.spec.whatwg.org/
IsAjax() bool
// +------------------------------------------------------------+
// | 响应头助手 |
// +------------------------------------------------------------+
// Header 添加响应头到响应 writer。
Header(name string, value string)
// ContentType 设置响应头 "Content-Type" 为 'cType'。
ContentType(cType string)
// GetContentType 返回响应头 "Content-Type" 的值。
GetContentType() string
// StatusCode 设置响应状态码。
// 也可查看 .GetStatusCode。
StatusCode(statusCode int)
// GetStatusCode 返回当前响应的状态码。
// 也可查阅 StatusCode。
GetStatusCode() int
// Redirect 发送一个重定向响应到客户端,接受两个参数,字符串和可选的证书。
// 第一个参数是重定向的URL,第二个是重定向状态码,默认是302。
// 如果必要,你可以设置为301,代表永久转义。
Redirect(urlToRedirect string, statusHeader ...int)
// +------------------------------------------------------------+
// | 各种请求和 POST 数据 |
// +------------------------------------------------------------+
// URLParam 返回请求中的参数,如果有。
URLParam(name string) string
// URLParamInt 从请求返回 int 类型的URL参数,如果解析失败,返回错误。
URLParamInt(name string) (int, error)
// URLParamInt64 从请求返回 int64 类型的参数,如果解析失败,返回错误。
URLParamInt64(name string) (int64, error)
// URLParams 返回请求查询参数映射,如果没有,返回为空。
URLParams() map[string]string
// FormValue 返回一个表单值。
FormValue(name string) string
// FormValues 从 data,get,post 和 查询参数中返回所有的数据值以及他们的键。
//
// 提醒: 检查是否是 nil 是很有必要的。
FormValues() map[string][]string
// PostValue 仅仅根据名称返回表单的post值,类似于 Request.PostFormValue。
PostValue(name string) string
// FormFile 返回键指定的第一个文件。
// 如果有必要,FormFile 调用 ctx.Request.ParseMultipartForm 和 ParseForm。
//
// 类似于 Request.FormFile.
FormFile(key string) (multipart.File, *multipart.FileHeader, error)
// +------------------------------------------------------------+
// | 自定义 HTTP 错误 |
// +------------------------------------------------------------+
// NotFound 发送一个 404 错误到客户端,使用自定义的错误处理方法。
// 如果你不想剩下的处理方法被执行,你可能需要去调用 ctx.StopExecution()。
// 你可以将错误码改成更具体的,例如:
// users := app.Party("/users")
// users.Done(func(ctx context.Context){ if ctx.StatusCode() == 400 { /* custom error code for /users */ }})
NotFound()
// +------------------------------------------------------------+
// | Body Readers |
// +------------------------------------------------------------+
// SetMaxRequestBodySize 设置请求体大小的上限,应该在读取请求体之前调用。
SetMaxRequestBodySize(limitOverBytes int64)
// UnmarshalBody 读取请求体,并把它绑定到一个任何类型或者指针的值。
// 使用实例: context.ReadJSON, context.ReadXML。
UnmarshalBody(v interface{}, unmarshaler Unmarshaler) error
// ReadJSON 从请求体读取 JSON,并把它绑定到任何json有效类型的值。
ReadJSON(jsonObject interface{}) error
// ReadXML 从请求体读取 XML,并把它绑定到任何xml有效类型的值。
ReadXML(xmlObject interface{}) error
// ReadForm 是用表单数据绑定 formObject,支持任何类型的结构体。
ReadForm(formObject interface{}) error
// +------------------------------------------------------------+
// | Body (raw) Writers |
// +------------------------------------------------------------+
// Write 将数据作为一个HTTP响应的一部分写入连接。
//
// 如果 WriteHeader 还没有调用,Write 将在 写入数据之前调用 WriteHeader(http.StatusOK)。
// 如果 Header 没有 Content-Type,Write 添加一个 Content-Type,设置为写入数据的前
// 512 字节的类型。
//
// 取决于 HTTP 版本和客户端,调用 Write 或者 WriteHeader 可能组织以后读取 Request.Body。
// 对于 HTTP/1.x 请求,处理方法应该在写入响应之前读取所有必要的请求体数据。一旦 HTTP 头被清掉
// (显示调用 Flusher.Flush 或者写入了足够的数据触发了清空操作),请求体可能变得不可用。
// 对于 HTTP/2 请求,Go HTTP 服务器允许在写入响应的同时读取请求体。然而,这种行为可能不被所有
// HTTP/2 客户端支持。处理方法应该尽可能读取最大量的数据在写入之前。
Write(body []byte) (int, error)
// Writef 根据格式声明器格式化,然后写入响应。
//
// 返回写入的字节数量以及任何写入错误。
Writef(format string, args ...interface{}) (int, error)
// WriteString 将一个简单的字符串写入响应。
//
// 返回写入的字节数量以及任何写入错误。
WriteString(body string) (int, error)
// WriteWithExpiration 很像 Write,但是它发送了一个失效时间,它会被每个包级别的
// `StaticCacheDuration` 字段刷新。
WriteWithExpiration(body []byte, modtime time.Time) (int, error)
// StreamWriter 注册给定的流用于发布响应体。
//
// 这个函数可能被用于一下这些情况:
//
// * 如果响应体太大(超过了iris.LimitRequestBodySize)
// * 如果响应体是慢慢从外部资源流入
// * 如果响应体必须分片流向客户端(例如 `http server push`)
StreamWriter(writer func(w io.Writer) bool)
// +------------------------------------------------------------+
// | 带压缩的 Body Writers |
// +------------------------------------------------------------+
// 如果客户端支持 gzip 压缩,ClientSupportsGzip 返回 true。
ClientSupportsGzip() bool
// WriteGzip accepts bytes, which are compressed to gzip format and sent to the client.
// WriteGzip 接受压缩成 gzip 格式的字节然后发送给客户端,并返回写入的字节数量和错误(如果错误不支持 gzip 格式)
//
// 这个函数写入临时的 gzip 内容,ResponseWriter 不会改变。
WriteGzip(b []byte) (int, error)
// TryWriteGzip 接受 gzip 格式压缩的字节,然后发送给客户端。
// 如果客户端不支持 gzip,就按照他们原来未压缩的样子写入。
//
// 这个函数写入临时的 gzip 内容,ResponseWriter 不会改变。
TryWriteGzip(b []byte) (int, error)
// GzipResponseWriter converts the current response writer into a response writer
// GzipResponseWriter 将当前的响应 writer 转化为一个 gzip 响应 writer。
// 当它的 .Write 方法被调用的时候,数据被压缩成 gzip 格式然后把他们写入客户端。
//
// 也可以使用 .Disable 禁用以及使用 .ResetBody 回滚到常规的响应写入器。
GzipResponseWriter() *GzipResponseWriter
// Gzip 开启或者禁用 gzip 响应写入器,如果客户端支持 gzip 压缩,所以接下来的响应数据将被作为
// 压缩的 gzip 数据发送给客户端。
Gzip(enable bool)
// +------------------------------------------------------------+
// | 富文本内容渲染器 |
// +------------------------------------------------------------+
// ViewLayout 设置 「布局」选项,如果随后在相同的请求中 .View 被调用。
// 当需要去改变或者设置处理链中前一个方法的布局时很有用。
//
// 注意 'layoutTmplFile' 参数可以被设置为 iris.NoLayout 或者 view.NoLayout 去禁用某个试图渲染动作的布局。
// 它禁用了配置项的布局属性。
//
// 也可查看 .ViewData 和 .View。
//
// 示例:https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/
ViewLayout(layoutTmplFile string)
// ViewData 保存一个或者多个键值对为了在后续的 .View 被调用的时候使用。
// 当需要处理链中前一个处理器的模板数据的时候是很有用的。
//
// 如果 .View 的绑定参数不是 nil 也不是 map 类型,这些数据就会被忽略,绑定是有优先级的,所以住路由的处理方法仍然有效。
// 如果绑定是一个map或者context.Map,这些数据然后就被添加到视图数据然后传递给模板。
//
// .View 调用之后,这些数据不会被丢掉,为了在必要的时候重用(再次声明,同一个请求中),为了清除这些数据,开发者可以调用
// ctx.Set(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey(), nil)。
//
// 如果 'key' 是空的,然后 值被作为它的(struct 或者 map)添加,并且开发者不能添加其他值。
//
// 推荐查看 .ViewLayout 和 .View。
//
// 示例:https://github.com/kataras/iris/tree/master/_examples/view/context-view-data/
ViewData(key string, value interface{})
// GetViewData 返回 `context#ViewData` 注册的值。
// 返回值是 `map[string]interface{}` 类型,这意味着如果一个自定义的结构体被添加到 ViewData, 这个函数将把它解析成
// map,如果失败返回 nil。
// 如果不同类型的值或者没有数据通过 `ViewData` 注册,检查是否为nil总是好的编程规范。
//
// 类似于 `viewData := ctx.Values().Get("iris.viewData")` 或者
// `viewData := ctx.Values().Get(ctx.Application().ConfigurationReadOnly().GetViewDataContextKey())`。
GetViewData() map[string]interface{}
// View 基于适配的视图引擎渲染模板。第一个参数是接受相对于视图引擎目录的文件名,
// 例如如果目录是 "./templates",想要渲染: "./templates/users/index.html"
// 你应该传递 "users/index.html" 作为文件名参数。
// 也可以查看 .ViewData 和 .ViewLayout。
// 示例:https://github.com/kataras/iris/tree/master/_examples/view/
View(filename string) error
// Binary 将原生字节作为二进制数据返回。
Binary(data []byte) (int, error)
// Text 将字符串作为空白文本返回。
Text(text string) (int, error)
// HTML 将字符串作为 text/html 返回.
HTML(htmlContents string) (int, error)
// JSON 格式化给定的数据并且返回 json 数据。
JSON(v interface{}, options ...JSON) (int, error)
// JSONP 格式化给定的数据并且返回 json 数据。
JSONP(v interface{}, options ...JSONP) (int, error)
// XML 格式话给定数据,并返回 XML 数据。
XML(v interface{}, options ...XML) (int, error)
// Markdown 解析 markdown 数据为 HTML 返回给客户端。
Markdown(markdownB []byte, options ...Markdown) (int, error)
// +------------------------------------------------------------+
// | 文件响应 |
// +------------------------------------------------------------+
// ServeContent 返回的内容头是自动设置的,接受三个参数,它是一个低级的函数,你可以调用 .ServeFile(string,bool)/SendFile(string,string)。
// 这个函数调用之后,你可以定义自己的 "Content-Type" 头,不要实现 resuming,而应该使用 ctx.SendFile。
ServeContent(content io.ReadSeeker, filename string, modtime time.Time, gzipCompression bool) error
// ServeFile 渲染一个视图文件,如果要发送一个文件(例如zip文件)到客户端,你应该使用 SendFile(serverfilename,clientfilename)。
// 接受两个参数:
// filename/path (string)
// gzipCompression (bool)
// 这个函数调用之后,你可以定义自己的 "Content-Type" 头,这个函数没有实现 resuming,你应该使用 ctx.SendFile。
ServeFile(filename string, gzipCompression bool) error
// SendFile 发送强制下载的文件到客户端
//
// 使用这个而不是 ServeFile 用于大文件下载到客户端。
SendFile(filename string, destinationName string) error
// +------------------------------------------------------------+
// | Cookies |
// +------------------------------------------------------------+
// SetCookie 添加cookie
SetCookie(cookie *http.Cookie)
// SetCookieKV 添加一个 cookie,仅仅接受一个名字(字符串)和一个值(字符串)
//
// 如果你用这个方法设置cookie,它将在两小时之后失效。
// 如果你想去设置或者改变更多字段,使用 ctx.SetCookie 或者 http.SetCookie。
SetCookieKV(name, value string)
// GetCookie 通过名称返回值,如果没找到返回空字符串。
GetCookie(name string) string
// RemoveCookie 通过名字删除 cookie。
RemoveCookie(name string)
// VisitAllCookies 接受一个 visitor 循环每个cookie,visitor 接受两个参数:名称和值。
VisitAllCookies(visitor func(name string, value string))
// MaxAge 返回 "cache-control" 请求头的值,单位为:秒,类型为 int64
// 如果头没有发现或者解析失败返回 -1。
MaxAge() int64
// +------------------------------------------------------------+
// | 高级部分: 响应记录器和事务 |
// +------------------------------------------------------------+
// Record 转化上下文基本的 responseWriter 为 ResponseRecorder,它可以被用于在任何时候重置内容体,
// 重置响应头,获取内容体,获取和设置状态码。
Record()
// Recorder 返回上下文的 ResponseRecorder,如果没有 recording 然后它将开始记录并返回新的上下文的 ResponseRecorder。
Recorder() *ResponseRecorder
// IsRecording 返回响应记录器以及一个bool值
// true 表示响应记录器正在记录状态码,内容体,HTTP 头以及更多,否则就是 false
IsRecording() (*ResponseRecorder, bool)
// BeginTransaction 开启一个有界事务。
//
// 你可以搜索第三方文章或者查看事务 Transaction 如何工作(这里相当简单特别)。
//
// 记着这个是唯一的,也是新的,目前为止,我没有在这个项目中看到任何例子和代码,就大多数 iris 功能而言。
// 它没有覆盖所有路径,例如数据库,这个应该由你使用的创建数据库连接的库管理,这个事务域仅仅用于上下文响应,
//
// 阅读 https://github.com/kataras/iris/tree/master/_examples/ 查看更多。
BeginTransaction(pipe func(t *Transaction))
// 如果调用 SkipTransactions 将跳过剩余的事务,或者如果在第一个事务之前调用,将跳过所有
SkipTransactions()
// TransactionsSkipped 返回事务到底被跳过还是被取消了。
TransactionsSkipped() bool
// Exec根据这个上下文调用framewrok的ServeCtx,但是改变了方法和路径,就像用户请求的那样,但事实并非如此。
//
// 离线意味着路线已注册到 iris 并具有正常路由所具有的所有功能。
// 但是它不能通过浏览获得,它的处理程序仅在其他处理程序的上下文调用它们时执行,它可以验证路径,拥有会话,路径参数等。
//
// 你可以通过 app.GetRoute("theRouteName") 找到路由,你可以设置一个路由名称如:
// myRoute := app.Get("/mypath", handler)("theRouteName")
// 这个将给路由设置一个名称并且返回它的 RouteInfo 实例为了进一步使用。
//
// 它不会更改全局状态,如果路由处于“脱机”状态,它将保持脱机状态。
//
// app.None(...) and app.GetRoutes().Offline(route)/.Online(route, method)
//
// 实例:https://github.com/kataras/iris/tree/master/_examples/routing/route-state
//
// 用户可以通过简单调用得到响应:rec := ctx.Recorder(); rec.Body()/rec.StatusCode()/rec.Header()
// Context 的 Values 和 Session 被记住为了能够通过结果路由通信,
// 它仅仅由于特别案例,99% 的用户不会用到的。
Exec(method string, path string)
// Application 返回 属于这个上下文的 iris 实例。
// 值得留意的是这个函数返回 Application 的一个接口,它包含的方法能够安全地在运行是执行。
// 为了开发者的安全性,整个 app 的 字段和方法这里是不可用的。
Application() Application
}
动态路由参数
对于路由的路径生命语法,动态路径参数解析和计算,Iris有自己的解释器,我们简单的把它们叫作”宏”。
路由路径参数标准macor类型。
+------------------------+
| {param:string} |
+------------------------+
string type
anything
+------------------------+
| {param:int} |
+------------------------+
int type
only numbers (0-9)
+------------------------+
| {param:long} |
+------------------------+
int64 type
only numbers (0-9)
+------------------------+
| {param:boolean} |
+------------------------+
bool type
only "1" or "t" or "T" or "TRUE" or "true" or "True"
or "0" or "f" or "F" or "FALSE" or "false" or "False"
+------------------------+
| {param:alphabetical} |
+------------------------+
alphabetical/letter type
letters only (upper or lowercase)
+------------------------+
| {param:file} |
+------------------------+
file type
letters (upper or lowercase)
numbers (0-9)
underscore (_)
dash (-)
point (.)
no spaces ! or other character
+------------------------+
| {param:path} |
+------------------------+
path type
anything, should be the last part, more than one path segment,
i.e: /path1/path2/path3 , ctx.Params().Get("param") == "/path1/path2/path3"
如果没有设置参数类型,默认就是string,所以{param}=={param:string}。
如果在该类型上找不到函数,那么string宏类型的函数将被使用。
除了iris提供的基本类型和一些默认的“宏函数”,我们也可以注册自己的。
// 注册一个明明的路径参数函数。
app.Macros().Get("int").RegisterFunc("min", func(minValue int) func(string) bool {
return func(paramValue string) bool{
n, err := strconv.Atoi(paramValue)
if err != nil {
return false
}
return n >= minValue
}
})
// 第二种写法
import "github.com/kataras/iris/v12/macro"
macro.Int.RegisterFunc("min", func(minValue int) func(string) bool {
return func(paramValue string) bool {
n, err := strconv.Atoi(paramValue)
if err != nil {
return false
}
return n >= minValue
}
})
示例代码:
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/macro"
"strconv"
)
func main() {
app := iris.New()
app.Get("/username/{name}", func(ctx iris.Context) {
ctx.Writef("Hello %s",ctx.Params().Get("name"))
})
//// 注册int类型的宏函数
//// min 函数名称
//// minValue 函数参数
//// func(string) bool 宏的路径参数的求值函数,当用户请求包含 min(...)
//app.Macros().Get("int").RegisterFunc("min", func(minValue int) func(string) bool {
// return func(paramValue string) bool{
// n, err := strconv.Atoi(paramValue)
// if err != nil {
// return false
// }
// return n >= minValue
// }
//})
macro.Int.RegisterFunc("min", func(minValue int) func(string) bool {
return func(paramValue string) bool {
n, err := strconv.Atoi(paramValue)
if err != nil {
return false
}
return n >= minValue
}
})
// http://localhost:8080/profile/id>=1
// 如果路由是这样:/profile/0, /profile/blabla, /profile/-1,将抛出404错误
// 宏参数函数是可以省略的。
app.Get("/profile/{id:int min(1)}", func(ctx iris.Context) {
id, _ := ctx.Params().GetInt("id")
ctx.Writef("Hello id:%d",id)
})
// 改变路径参数错误引起的错误码
// 这将抛出504错误码,而不是404,如果所有路由宏没有通过
app.Get("/profile/{id:int min(1)}/friends/{friendid:int min(1) else 504}", func(ctx iris.Context) {
id, _ := ctx.Params().GetInt("id")
friendid, _ := ctx.Params().GetInt("friendid")
ctx.Writef("Hello id:%d looking for friend id:%d",id,friendid)
})
// http://localhostL8080/game/a-zA-Z/level/0-9
// 记着 alphabetical仅仅允许大小写字母
app.Get("/game/{name:alphabetical}/level/{level:int}", func(ctx iris.Context) {
ctx.Writef("name:%s | level:%s",ctx.Params().Get("name"),ctx.Params().Get("level"))
})
// 用一个简单的自定义正则表达式来雁阵一个路径参数仅仅是小写字母
// http://localhost:8080/lowercase/anylowercase
app.Get("/lowercase/{name:string regexp(^[a-z]+)}", func(ctx iris.Context) {
ctx.Writef("name should be only lowercase,otherwise this handler will never executed:%s",ctx.Params().Get("name"))
})
app.Run(iris.Addr(":8080"))
}
一个路径参数名称应该只包含小写字母,像_和数字这样的符号是不允许的。
路径参数值获取通过ctx.Params(),上下文中用于处理器之间通信的局部存储和中间件使用ctx.Value()。
路由中间件
中间件
当我们在 iris 中讨论中间件的时候,我们就是在讨论运行一个 HTTP 请求处理器前后的代码。例如,日志中间件会把即将到来的请求明细写到日志中,然后在写响应日志详细之前,调用处理期代码。比较酷的是这些中间件是松耦合,而且可重用。
中间件仅仅是 func(ctx iris.Context) 的处理器形式,中间件在前一个调用 ctx.Next() 时执行,这个可以用于去认证,例如,如果登录了,调用 ctx.Next() 否则将触发一个错误响应。
中间件开发
package main
import "github.com/kataras/iris/v12"
func main() {
app := iris.New()
app.Get("/",before,mainHandler,after)
app.Run(iris.Addr(":8080"))
}
func before(ctx iris.Context){
shareInformation := "this is a sharable information between handlers"
requestPath := ctx.Path()
println("Before the mainHandler:"+requestPath)
ctx.Values().Set("info",shareInformation)
ctx.Next() // 执行下一个处理器
}
func after(ctx iris.Context){
println("After the mainHandler")
}
func mainHandler(ctx iris.Context){
println("Inside mainHandler")
// 获取“before”处理器中设置的“info”值
info := ctx.Values().GetString("info")
// 响应客户端
ctx.HTML("<h1>Response</h1>")
ctx.HTML("<br/>Info:"+ info)
ctx.Next()
}
Now listening on: http://localhost:8080
Application started. Press CTRL+C to shut down.
Before the mainHandler:/
Inside mainHandler
After the mainHandler
全局
func before(ctx iris.Context){
shareInformation := "this is a sharable information between handlers"
requestPath := ctx.Path()
println("Before the mainHandler:"+requestPath)
ctx.Values().Set("info",shareInformation)
ctx.Next() // 执行下一个处理器
}
func after(ctx iris.Context){
println("After the mainHandler")
}
func indexHandler(ctx iris.Context){
println("Inside mainHandler")
// 获取“before”处理器中设置的“info”值
info := ctx.Values().GetString("info")
// 响应客户端
ctx.HTML("<h1>Index</h1>")
ctx.HTML("<br/>Info:"+ info)
ctx.Next() // 执行通过"Done"注册的"after"处理器
}
func contactHandler(ctx iris.Context){
info := ctx.Values().GetString("info")
// 响应客户端
ctx.HTML("<h1>Contact</h1>")
ctx.HTML("<br/>Info:"+info)
}
func main() {
app := iris.New()
// 注册“before”处理器作为当前域名所有路由中第一个处理函数
app.Use(before)
// 或者使用"UserGlobal"去注册一个中间件,用于在所有子域名中使用
// app.UseGlobal(before)
// 注册“after”,在所有路由处理程序之后调用
app.Done(after)
// 注册路由
app.Get("/",indexHandler)
app.Get("/contact",contactHandler)
app.Run(iris.Addr(":8080"))
}
JWT
package main
import (
"github.com/iris-contrib/middleware/jwt"
"github.com/kataras/iris/v12"
"time"
)
var mySecret = []byte("secret") // 定义秘钥
var j = jwt.New(jwt.Config{
// 设置一个函数返回秘钥,关键在于return mySecret
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return mySecret,nil
},
Expiration:true, // 是否过期
Extractor: jwt.FromParameter("token"), // 从请求参数token中获取
SigningMethod:jwt.SigningMethodHS256, // 设置加密方法
})
// 生成令牌
func getTokenHandler(ctx iris.Context){
now := time.Now()
token := jwt.NewTokenWithClaims(jwt.SigningMethodHS256,jwt.MapClaims{
"foo":"bar",
"iat":now.Unix(), // 签发时间
"exp":now.Add(15*time.Minute).Unix(), // 过期时间
})
// 生成完整的秘钥
tokenString,_ := token.SignedString(mySecret)
ctx.Writef("jwt=%s",tokenString)
}
// 验证
func myAuthenticatedHandler(ctx iris.Context){
// 验证,验证不通过直接返回
if err := j.CheckJWT(ctx);err != nil {
j.Config.ErrorHandler(ctx, err)
return
}
token := ctx.Values().Get("jwt").(*jwt.Token)
ctx.Writef("This is an authenticated request\n\n")
foobar := token.Claims.(jwt.MapClaims)
ctx.Writef("foo=%s\n",foobar["foo"])
}
func main() {
app := iris.New()
app.Get("/",getTokenHandler)
app.Get("/protected",myAuthenticatedHandler)
app.Listen(":8080")
}
错误处理
当程序产生了一个特定的 http 错误的时候,你可以定义你自己的错误处理代码。
错误代码是大于或者等于 400 的 http 状态码,像 404 not found 或者 500 服务器内部错误。
示例代码:
package main
import "github.com/kataras/iris/v12"
func main() {
app := iris.New()
app.OnErrorCode(iris.StatusNotFound,notFound)
app.OnErrorCode(iris.StatusInternalServerError,internalServerError)
app.Get("/",index)
app.Run(iris.Addr(":8080"))
}
func notFound(ctx iris.Context){
// 出现404的时候,就跳转到views/errors/404.html模板
ctx.View("./views/errors/404.html")
}
func internalServerError(ctx iris.Context){
ctx.WriteString("Oups something went wrong,try again")
}
func index(ctx iris.Context){
ctx.View("index.html")
}
Session
Overview
import "github.com/kataras/iris/v12/sessions"
sess := sessions.Start(http.ResponseWriter, *http.Request)
sess.
ID() string
Get(string) interface{}
HasFlash() bool
GetFlash(string) interface{}
GetFlashString(string) string
GetString(key string) string
GetInt(key string) (int, error)
GetInt64(key string) (int64, error)
GetFloat32(key string) (float32, error)
GetFloat64(key string) (float64, error)
GetBoolean(key string) (bool, error)
GetAll() map[string]interface{}
GetFlashes() map[string]interface{}
VisitAll(cb func(k string, v interface{}))
Set(string, interface{})
SetImmutable(key string, value interface{})
SetFlash(string, interface{})
Delete(string)
Clear()
ClearFlashes()
示例代码:
package main
import (
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/sessions"
)
var (
cookieNameForSessionId = "mycookiessionnameid"
sess = sessions.New(sessions.Config{
Cookie: cookieNameForSessionId,
})
)
func secret(ctx iris.Context){
// 检查用户是否已通过身份验证
p := sess.Start(ctx).Get("authenticated")
println(p)
if auth, _ := sess.Start(ctx).GetBoolean("authenticated");!auth{
ctx.StatusCode(iris.StatusForbidden)
return
}
// 打印
ctx.WriteString("The cake is a lie!")
}
func login(ctx iris.Context){
session := sess.Start(ctx)
// 在此进行身份验证
// ...
// 将用户设置为已验证
session.Set("authenticated",true)
ctx.WriteString("登录成功")
}
func logout(ctx iris.Context){
session := sess.Start(ctx)
// 撤销身份认证
session.Set("authenticated",false)
}
func main() {
app := iris.New()
app.Get("/secret",secret)
app.Get("/login",login)
app.Get("/logout",logout)
app.Run(iris.Addr(":8080"))
}