- 安装
- 快速开始
- 基准测试
- Api 例子
- GET,POST,PUT,PATCH,DELETE and OPTIONS
- 路径参数
- 查询字符串参数
- 表单 Multipart/Urlencoded
- 查询和发布表单参数
- 上传文件
- 路由组
- 默认情况下没有中间件
- 使用中间件
- 如何写日志文件
- 自定义请求日志
- 控制日志输入颜色
- Model绑定和验证
- 绑定查询字符串
- 绑定url路径参数
- header头绑定
- 绑定HTML 复选框
- JSON,JSONP,XML,Markdown,YAML and MsgPack rendering
- 使用静态文件
- 上下文服务数据
- 模式渲染
- 多个模板文件
- 重定向
- 自定义中间件
- 使用基本身份信息验证
- 中间件中的Goroutines
- 自定义HTTP配置
- socket 分区
- 支持的加密
- 使用Iris运行多个服务
- 优雅关机或重启
- 使用模板构建单个二进制文件
- 尝试将body绑定到不同的结构中
- HTTP2服务器推送
- 设置和获取cookie
- 测试
- 本地化
安装
Iris是一个跨平台软件。
唯一的要求是Go编程语言,版本1.14及以上。
$ mkdir myapp$ go mod init myapp$ go get github.com/kataras/iris/v12@master
导入它在您的代码:
import "github.com/kataras/iris/v12"
疑问解答
如果您在安装期间出现网络错误,请确保您设置了一个有效的GOPROXY环境变量。
go env -w GOPROXY=https://goproxy.cn,https://gocenter.io,https://goproxy.io,direct
快速开始
# assume the following codes in main.go file$ cat main.go
package mainimport "github.com/kataras/iris/v12"func main() {app := iris.New()booksAPI := app.Party("/books"){booksAPI.Use(iris.Compression)// GET: http://localhost:8080/booksbooksAPI.Get("/", list)// POST: http://localhost:8080/booksbooksAPI.Post("/", create)}app.Listen(":8080")}// Book example.type Book struct {Title string `json:"title"`}func list(ctx iris.Context) {books := []Book{{"Mastering Concurrency in Go"},{"Go Design Patterns"},{"Black Hat Go"},}ctx.JSON(books)// TIP: negotiate the response between server's prioritizes// and client's requirements, instead of ctx.JSON:// ctx.Negotiation().JSON().MsgPack().Protobuf()// ctx.Negotiate(books)}func create(ctx iris.Context) {var b Bookerr := ctx.ReadJSON(&b)// TIP: use ctx.ReadBody(&b) to bind// any type of incoming data instead.if err != nil {ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().Title("Book creation failure").DetailErr(err))// TIP: use ctx.StopWithError(code, err) when only// plain text responses are expected on errors.return}println("Received Book: " + b.Title)ctx.StatusCode(iris.StatusCreated)}
MVC
import "github.com/kataras/iris/v12/mvc"
m := mvc.New(booksAPI)m.Handle(new(BookController))
type BookController struct {/* dependencies */}// GET: http://localhost:8080/booksfunc (c *BookController) Get() []Book {return []Book{{"Mastering Concurrency in Go"},{"Go Design Patterns"},{"Black Hat Go"},}}// POST: http://localhost:8080/booksfunc (c *BookController) Post(b Book) int {println("Received Book: " + b.Title)return iris.StatusCreated}
运行你的iris Web服务:
$ go run main.go> Now listening on: http://localhost:8080> Application started. Press CTRL+C to shut down.
Books 列表
$ curl --header 'Accept-Encoding:gzip' http://localhost:8080/books[{"title": "Mastering Concurrency in Go"},{"title": "Go Design Patterns"},{"title": "Black Hat Go"}]
创建一个新的Book
$ curl -i -X POST \--header 'Content-Encoding:gzip' \--header 'Content-Type:application/json' \--data "{\"title\":\"Writing An Interpreter In Go\"}" \http://localhost:8080/books> HTTP/1.1 201 Created
错误相应
$ curl -X POST --data "{\"title\" \"not valid one\"}" \http://localhost:8080/books> HTTP/1.1 400 Bad Request{"status": 400,"title": "Book creation failure""detail": "invalid character '\"' after object key",}
基准测试
Iris使用了一个定制版本的muxie。
See all benchmarks
动态参数的int 200000个请求,发送JSON请求主体和接收JSON响应。
| Name | Language | Reqs/sec | Latency | Throughput | Time To Complete |
|---|---|---|---|---|---|
| Iris | Go | 150430 | 826.05us | 41.25MB | 1.33s |
| Chi | Go | 146274 | 0.85ms | 39.32MB | 1.37s |
| Gin | Go | 141664 | 0.88ms | 38.74MB | 1.41s |
| Echo | Go | 138915 | 0.90ms | 38.15MB | 1.44s |
| Kestrel | C# | 136935 | 0.91ms | 39.79MB | 1.47s |
| Martini | Go | 128590 | 0.97ms | 34.57MB | 1.56s |
| Buffalo | Go | 58954 | 2.12ms | 16.18MB | 3.40s |
| Koa | Javascript | 50948 | 2.61ms | 14.15MB | 4.19s |
| Express | Javascript | 38451 | 3.24ms | 13.77MB | 5.21s |
Api 例子
GET,POST,PUT,PATCH,DELETE and OPTIONS
func main() {// Creates an iris application with default middleware:// Default with "debug" Logger Level.// Localization enabled on "./locales" directory// and HTML templates on "./views" or "./templates" directory.// It runs with the AccessLog on "./access.log",// Recovery (crash-free) and Request ID middleware already attached.app := iris.Default()app.Get("/someGet", getting)app.Post("/somePost", posting)app.Put("/somePut", putting)app.Delete("/someDelete", deleting)app.Patch("/somePatch", patching)app.Header("/someHead", head)app.Options("/someOptions", options)app.Listen(":8080")}
路径参数
func main() {app := iris.Default()// This handler will match /user/john but will not match /user/ or /userapp.Get("/user/{name}", func(ctx iris.Context) {name := ctx.Params().Get("name")ctx.Writef("Hello %s", name)})// However, this one will match /user/john/ and also /user/john/send// If no other routers match /user/john, it will redirect to /user/john/app.Get("/user/{name}/{action:path}", func(ctx iris.Context) {name := ctx.Params().Get("name")action := ctx.Params().Get("action")message := name + " is " + actionctx.WriteString(message)})// For each matched request Context will hold the route definitionapp.Post("/user/{name:string}/{action:path}", func(ctx iris.Context) {ctx.GetCurrentRoute().Tmpl().Src == "/user/{name:string}/{action:path}" // true})app.Listen(":8080")}
内置可用参数类型:
| Param Type | Go Type | Validation | Retrieve Helper |
|---|---|---|---|
:string |
string | anything (single path segment) | Params().Get |
:int |
int | -9223372036854775808 to 9223372036854775807 (x64) or -2147483648 to 2147483647 (x32), depends on the host arch | Params().GetInt |
:int8 |
int8 | -128 to 127 | Params().GetInt8 |
:int16 |
int16 | -32768 to 32767 | Params().GetInt16 |
:int32 |
int32 | -2147483648 to 2147483647 | Params().GetInt32 |
:int64 |
int64 | -9223372036854775808 to 9223372036854775807 | Params().GetInt64 |
:uint |
uint | 0 to 18446744073709551615 (x64) or 0 to 4294967295 (x32), depends on the host arch | Params().GetUint |
:uint8 |
uint8 | 0 to 255 | Params().GetUint8 |
:uint16 |
uint16 | 0 to 65535 | Params().GetUint16 |
:uint32 |
uint32 | 0 to 4294967295 | Params().GetUint32 |
:uint64 |
uint64 | 0 to 18446744073709551615 | Params().GetUint64 |
:bool |
bool | “1” or “t” or “T” or “TRUE” or “true” or “True” or “0” or “f” or “F” or “FALSE” or “false” or “False” | Params().GetBool |
:alphabetical |
string | lowercase or uppercase letters | Params().Get |
:file |
string | lowercase or uppercase letters, numbers, underscore (_), dash (-), point (.) and no spaces or other special characters that are not valid for filenames | Params().Get |
:path |
string | anything, can be separated by slashes (path segments) but should be the last part of the route path | Params().Get |
More examples can be found at: _examples/routing.
查询字符串参数
func main() {app := iris.Default()// Query string parameters are parsed using the existing underlying request object.// The request responds to a url matching: /welcome?firstname=Jane&lastname=Doeapp.Get("/welcome", func(ctx iris.Context) {firstname := ctx.URLParamDefault("firstname", "Guest")lastname := ctx.URLParam("lastname") // shortcut for ctx.Request().URL.Query().Get("lastname")ctx.Writef("Hello %s %s", firstname, lastname)})app.Listen(":8080")}
表单 Multipart/Urlencoded
func main() {app := iris.Default()app.Post("/form_post", func(ctx iris.Context) {message := ctx.PostValue("message")nick := ctx.PostValueDefault("nick", "anonymous")ctx.JSON(iris.Map{"status": "posted","message": message,"nick": nick,})})app.Listen(":8080")}
另一个例子:查询+发布表单
POST /post?id=1234&page=1 HTTP/1.1Content-Type: application/x-www-form-urlencodedname=kataras&message=this_is_great
func main() {app := iris.Default()app.Post("/post", func(ctx iris.Context) {id, err := ctx.URLParamInt("id", 0)if err != nil {ctx.StopWithError(iris.StatusBadRequest, err)return}page := ctx.URLParamIntDefault("page", 0)name := ctx.PostValue("name")message := ctx.PostValue("message")ctx.Writef("id: %d; page: %d; name: %s; message: %s", id, page, name, message)})app.Listen(":8080")}
id: 1234; page: 1; name: kataras; message: this_is_great
查询和发布表单参数
POST /post?id=a&id=b&id=c&name=john&name=doe&name=katarasContent-Type: application/x-www-form-urlencoded
func main() {app := iris.Default()app.Post("/post", func(ctx iris.Context) {ids := ctx.URLParamSlice("id")names, err := ctx.PostValues("name")if err != nil {ctx.StopWithError(iris.StatusBadRequest, err)return}ctx.Writef("ids: %v; names: %v", ids, names)})app.Listen(":8080")}
ids: [a b c], names: [john doe kataras]
上传文件
单个文件
const maxSize = 8 * iris.MBfunc main() {app := iris.Default()app.Post("/upload", func(ctx iris.Context) {// Set a lower memory limit for multipart forms (default is 32 MiB)ctx.SetMaxRequestBodySize(maxSize)// OR// app.Use(iris.LimitRequestBodySize(maxSize))// OR// OR iris.WithPostMaxMemory(maxSize)// single filefile, _, err:= ctx.FormFile("file")if err != nil {ctx.StopWithError(iris.StatusBadRequest, err)return}// Upload the file to specific destination.dest := filepath.Join("./uploads", file.Filename)ctx.SaveFormFile(file, dest)ctx.Writef("File: %s uploaded!", file.Filename)})app.Listen(":8080")}
如何curl:
curl -X POST http://localhost:8080/upload \-F "file=@/Users/kataras/test.zip" \-H "Content-Type: multipart/form-data"
多文件上传
查看示例代码细节
func main() {app := iris.Default()app.Post("/upload", func(ctx iris.Context) {files, n, err := ctx.UploadFormFiles("./uploads")if err != nil {ctx.StopWithStatus(iris.StatusInternalServerError)return}ctx.Writef("%d files of %d total size uploaded!", len(files), n))})app.Listen(":8080", iris.WithPostMaxMemory(8 * iris.MB))}
如何curl:
curl -X POST http://localhost:8080/upload \-F "upload[]=@/Users/kataras/test1.zip" \-F "upload[]=@/Users/kataras/test2.zip" \-H "Content-Type: multipart/form-data"
路由组
func main() {app := iris.Default()// Simple group: v1v1 := app.Party("/v1"){v1.Post("/login", loginEndpoint)v1.Post("/submit", submitEndpoint)v1.Post("/read", readEndpoint)}// Simple group: v2v2 := app.Party("/v2"){v2.Post("/login", loginEndpoint)v2.Post("/submit", submitEndpoint)v2.Post("/read", readEndpoint)}app.Listen(":8080")}
默认情况下没有中间件
使用
app := iris.New()
代替
// Default with "debug" Logger Level.// Localization enabled on "./locales" directory// and HTML templates on "./views" or "./templates" directory.// It runs with the AccessLog on "./access.log",// Recovery and Request ID middleware already attached.app := iris.Default()
使用中间件
package mainimport ("github.com/kataras/iris/v12""github.com/kataras/iris/v12/middleware/logger""github.com/kataras/iris/v12/middleware/recover")func main() {// Creates an iris application without any middleware by defaultapp := iris.New()// Global middleware using `UseRouter`.//// Recovery middleware recovers from any panics and writes a 500 if there was one.app.UseRouter(recover.New())// Logger middleware by-default will write the logs using app.Logger().Infof.app.UseRouter(logger.New())// Per route middleware, you can add as many as you desire.app.Get("/benchmark", MyBenchLogger(), benchEndpoint)// Authorization group// authorized := app.Party("/", AuthRequired())// exactly the same as:authorized := app.Party("/")// per group middleware! in this case we use the custom created// AuthRequired() middleware just in the "authorized" group.authorized.Use(AuthRequired()){authorized.Post("/login", loginEndpoint)authorized.Post("/submit", submitEndpoint)authorized.Post("/read", readEndpoint)// nested grouptesting := authorized.Party("testing")testing.Get("/analytics", analyticsEndpoint)}// Listen and serve on 0.0.0.0:8080app.Listen(":8080")}
如何写日志文件
func main() {app := iris.Default()// Logging to a file.// Colors are automatically disabled when writing to a file.f, _ := os.Create("iris.log")app.Logger().SetOutput(f)// Use the following code if you need to write the logs// to file and console at the same time.// app.Logger().AddOutput(os.Stdout)app.Get("/ping", func(ctx iris.Context) {ctx.WriteString("pong")})app.Listen(":8080")}
自定义请求日志
搜索access.log功能代替?详细的例子可以在这里找到。
// LogFunc field.func customLogFunc(endTime time.Time,latency time.Duration,status, ip, method, path string,message, headerMessage interface{}) {// [...]}
// LogFuncCtx field.func customLogFuncWithContext(ctx iris.Context, latency time.Duration) {// [...]}
import "github.com/kataras/iris/v12/middleware/logger"func main() {app := iris.New()reqLogger := logger.New(logger.Config{Status: true,IP: true,Method: true,Path: true,PathAfterHandler: false,Query: false,TraceRoute: true,Columns: false,LogFunc: customLogFunc,LogFuncCtx: nil,Skippers: nil,})app.UseRouter(reqLogger)app.Get("/ping", func(ctx iris.Context) {ctx.WriteString("pong")})app.Listen(":8080")}
控制日志输入颜色
默认情况下,控制台上的日志输出应该根据检测到的TTY着色。
自定义级别标题,文本,颜色和样式。
Import golog and pio:
import ("github.com/kataras/golog""github.com/kataras/pio"// [...])
自定义一个级别,例如DebugLevel:
level := golog.Levels[golog.DebugLevel]
你可以完全控制他的文本,标题和风格:
// The Name of the Level// that named (lowercased) will be used// to convert a string level on `SetLevel`// to the correct Level type.Name string// AlternativeNames are the names that can be referred to this specific log level.// i.e Name = "warn"// AlternativeNames = []string{"warning"}, it's an optional field,// therefore we keep Name as a simple string and created this new field.AlternativeNames []string// Tha Title is the prefix of the log level.// See `ColorCode` and `Style` too.// Both `ColorCode` and `Style` should be respected across writers.Title string// ColorCode a color for the `Title`.ColorCode int// Style one or more rich options for the `Title`.Style []pio.RichOption
示例代码:
level := golog.Levels[golog.DebugLevel]level.Name = "debug" // defaultlevel.Title = "[DBUG]" // defaultlevel.ColorCode = pio.Yellow // default
更改格式化输出:
app.Logger().SetFormat("json", " ")
注册自定义格式化程序:
app.Logger().RegisterFormatter(new(myFormatter))
golog.Formatter interface 看起来像这样:
// Formatter is responsible to print a log to the logger's writer.type Formatter interface {// The name of the formatter.String() string// Set any options and return a clone,// generic. See `Logger.SetFormat`.Options(opts ...interface{}) Formatter// Writes the "log" to "dest" logger.Format(dest io.Writer, log *Log) bool}
改变输出和格式每层:
app.Logger().SetLevelOutput("error", os.Stderr)app.Logger().SetLevelFormat("json")
Model绑定和验证
要将请求主体绑定到类型中,请使用模型绑定。我们目前支持JSON绑定,JSONProtobuf, Protobuf, MsgPack, XML, YAML and standard form values (foo=bar&boo=baz).
ReadJSON(outPtr interface{}) errorReadJSONProtobuf(ptr proto.Message, opts ...ProtoUnmarshalOptions) errorReadProtobuf(ptr proto.Message) errorReadMsgPack(ptr interface{}) errorReadXML(outPtr interface{}) errorReadYAML(outPtr interface{}) errorReadForm(formObject interface{}) errorReadQuery(ptr interface{}) error
在使用读取体时,Iris尝试根据内容类型标题推断绑定器。如果确定要绑定的是什么,可以使用特定的ReadXXX方法,例如ReadJSON或ReadProtobuf和e.t.c。
ReadBody(ptr interface{}) error
明智的是,Iris不提供内置数据验证。但是,它允许你附加一个验证器,自动调用方法,如ReadJSON, ReadXML…在这个示例中,我们将学习如何使用go-playground/validator/v10进行请求体验证。
注意,您需要在所有想要绑定的字段上设置相应的绑定标记。例如,当从JSON绑定时,设置JSON:“fieldname”。
您还可以指定所需的特定字段。如果一个字段用binding:”required”修饰,并且在绑定时有一个空值,那么将返回一个错误。
package mainimport ("fmt""github.com/kataras/iris/v12""github.com/go-playground/validator/v10")func main() {app := iris.New()app.Validator = validator.New()userRouter := app.Party("/user"){userRouter.Get("/validation-errors", resolveErrorsDocumentation)userRouter.Post("/", postUser)}app.Listen(":8080")}// User contains user information.type User struct {FirstName string `json:"fname" validate:"required"`LastName string `json:"lname" validate:"required"`Age uint8 `json:"age" validate:"gte=0,lte=130"`Email string `json:"email" validate:"required,email"`FavouriteColor string `json:"favColor" validate:"hexcolor|rgb|rgba"`Addresses []*Address `json:"addresses" validate:"required,dive,required"`}// Address houses a users address information.type Address struct {Street string `json:"street" validate:"required"`City string `json:"city" validate:"required"`Planet string `json:"planet" validate:"required"`Phone string `json:"phone" validate:"required"`}type validationError struct {ActualTag string `json:"tag"`Namespace string `json:"namespace"`Kind string `json:"kind"`Type string `json:"type"`Value string `json:"value"`Param string `json:"param"`}func wrapValidationErrors(errs validator.ValidationErrors) []validationError {validationErrors := make([]validationError, 0, len(errs))for _, validationErr := range errs {validationErrors = append(validationErrors, validationError{ActualTag: validationErr.ActualTag(),Namespace: validationErr.Namespace(),Kind: validationErr.Kind().String(),Type: validationErr.Type().String(),Value: fmt.Sprintf("%v", validationErr.Value()),Param: validationErr.Param(),})}return validationErrors}func postUser(ctx iris.Context) {var user Usererr := ctx.ReadJSON(&user)if err != nil {// Handle the error, below you will find the right way to do that...if errs, ok := err.(validator.ValidationErrors); ok {// Wrap the errors with JSON format, the underline library returns the errors as interface.validationErrors := wrapValidationErrors(errs)// Fire an application/json+problem response and stop the handlers chain.ctx.StopWithProblem(iris.StatusBadRequest, iris.NewProblem().Title("Validation error").Detail("One or more fields failed to be validated").Type("/user/validation-errors").Key("errors", validationErrors))return}// It's probably an internal JSON error, let's dont give more info here.ctx.StopWithStatus(iris.StatusInternalServerError)return}ctx.JSON(iris.Map{"message": "OK"})}func resolveErrorsDocumentation(ctx iris.Context) {ctx.WriteString("A page that should document to web developers or users of the API on how to resolve the validation errors")}
简单请求
{"fname": "","lname": "","age": 45,"email": "mail@example.com","favColor": "#000","addresses": [{"street": "Eavesdown Docks","planet": "Persphone","phone": "none","city": "Unknown"}]}
简单响应
{"title": "Validation error","detail": "One or more fields failed to be validated","type": "http://localhost:8080/user/validation-errors","status": 400,"fields": [{"tag": "required","namespace": "User.FirstName","kind": "string","type": "string","value": "","param": ""},{"tag": "required","namespace": "User.LastName","kind": "string","type": "string","value": "","param": ""}]}
了解更多关于模型验证的信息:https://github.com/go-playground/validator/blob/master/_examples
绑定查询字符串
ReadQuery方法只绑定查询参数而不是post数据,使用ReadForm来绑定post数据。
package mainimport "github.com/kataras/iris/v12"type Person struct {Name string `url:"name,required"`Address string `url:"address"`}func main() {app := iris.Default()app.Any("/", index)app.Listen(":8080")}func index(ctx iris.Context) {var person Personif err := ctx.ReadQuery(&person); err!=nil {ctx.StopWithError(iris.StatusBadRequest, err)return}ctx.Application().Logger().Infof("Person: %#+v", person)ctx.WriteString("Success")}
绑定任何
根据客户端发送数据的内容类型,如JSON、XML、YAML、MessagePack、Protobuf、Form和URL查询,将请求体绑定到“ptr”。
package mainimport ("time""github.com/kataras/iris/v12")type Person struct {Name string `form:"name" json:"name" url:"name" msgpack:"name"`Address string `form:"address" json:"address" url:"address" msgpack:"address"`Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1" json:"birthday" url:"birthday" msgpack:"birthday"`CreateTime time.Time `form:"createTime" time_format:"unixNano" json:"create_time" url:"create_time" msgpack:"createTime"`UnixTime time.Time `form:"unixTime" time_format:"unix" json:"unix_time" url:"unix_time" msgpack:"unixTime"`}func main() {app := iris.Default()app.Any("/", index)app.Listen(":8080")}func index(ctx iris.Context) {var person Personif err := ctx.ReadBody(&person); err!=nil {ctx.StopWithError(iris.StatusBadRequest, err)return}ctx.Application().Logger().Infof("Person: %#+v", person)ctx.WriteString("Success")}
测试:
$ curl -X GET "localhost:8085/testing?name=kataras&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
绑定url路径参数
package mainimport "github.com/kataras/iris/v12"type myParams struct {Name string `param:"name"`Age int `param:"age"`Tail []string `param:"tail"`}// All parameters are required, as we already know,// the router will fire 404 if name or int or tail are missing.func main() {app := iris.Default()app.Get("/{name}/{age:int}/{tail:path}", func(ctx iris.Context) {var p myParamsif err := ctx.ReadParams(&p); err != nil {ctx.StopWithError(iris.StatusInternalServerError, err)return}ctx.Writef("myParams: %#v", p)})app.Listen(":8088")}
请求
$ curl -v http://localhost:8080/kataras/27/iris/web/framework
header头绑定
package mainimport "github.com/kataras/iris/v12"type myHeaders struct {RequestID string `header:"X-Request-Id,required"`Authentication string `header:"Authentication,required"`}func main() {app := iris.Default()r.GET("/", func(ctx iris.Context) {var hs myHeadersif err := ctx.ReadHeaders(&hs); err != nil {ctx.StopWithError(iris.StatusInternalServerError, err)return}ctx.JSON(hs)})app.Listen(":8080")}
请求
curl -H "x-request-id:373713f0-6b4b-42ea-ab9f-e2e04bc38e73" -H "authentication: Bearer my-token" \http://localhost:8080
响应
{"RequestID": "373713f0-6b4b-42ea-ab9f-e2e04bc38e73","Authentication": "Bearer my-token"}
绑定HTML 复选框
package mainimport "github.com/kataras/iris/v12"func main() {app := iris.New()app.RegisterView(iris.HTML("./templates", ".html"))app.Get("/", showForm)app.Post("/", handleForm)app.Listen(":8080")}func showForm(ctx iris.Context) {ctx.View("form.html")}type formExample struct {Colors []string `form:"colors[]"` // or just "colors".}func handleForm(ctx iris.Context) {var form formExampleerr := ctx.ReadForm(&form)if err != nil {ctx.StopWithError(iris.StatusBadRequest, err)return}ctx.JSON(iris.Map{"Colors": form.Colors})}
templates/form.html
<form action="/" method="POST"><p>Check one or more colors</p><label for="red">Red</label><!-- name can be "colors" too --><input type="checkbox" name="colors[]" value="red" id="red"><label for="green">Green</label><input type="checkbox" name="colors[]" value="green" id="green"><label for="blue">Blue</label><input type="checkbox" name="colors[]" value="blue" id="blue"><input type="submit"></form>
响应
{"Colors": ["red","green","blue"]}
JSON,JSONP,XML,Markdown,YAML and MsgPack rendering
示例细节在这里可以找到
func main() {app := iris.New()// iris.Map is an alias of map[string]interface{}app.Get("/json", func(ctx iris.Context) {ctx.JSON(iris.Map{"message": "hello", "status": iris.StatusOK})})// Use Secure field to prevent json hijacking.// It prepends `"while(1),"` to the body when the data is array.app.Get("/json_secure", func(ctx iris.Context) {response := []string{"val1", "val2", "val3"}options := iris.JSON{Indent: "", Secure: true}ctx.JSON(response, options)// Will output: while(1);["val1","val2","val3"]})// Use ASCII field to generate ASCII-only JSON// with escaped non-ASCII characters.app.Get("/json_ascii", func(ctx iris.Context) {response := iris.Map{"lang": "GO-虹膜", "tag": "<br>"}options := iris.JSON{Indent: " ", ASCII: true}ctx.JSON(response, options)/* Will output:{"lang": "GO-\u8679\u819c","tag": "\u003cbr\u003e"}*/})// Normally, JSON replaces special HTML characters with their unicode entities.// If you want to encode such characters literally,// you SHOULD set the UnescapeHTML field to true.app.Get("/json_raw", func(ctx iris.Context) {options := iris.JSON{UnescapeHTML: true}ctx.JSON(iris.Map{"html": "<b>Hello, world!</b>",}, options)// Will output: {"html":"<b>Hello, world!</b>"}})app.Get("/json_struct", func(ctx iris.Context) {// You also can use a struct.var msg struct {Name string `json:"user"`Message stringNumber int}msg.Name = "Mariah"msg.Message = "hello"msg.Number = 42// Note that msg.Name becomes "user" in the JSON.// Will output: {"user": "Mariah", "Message": "hello", "Number": 42}ctx.JSON(msg)})app.Get("/jsonp", func(ctx iris.Context) {ctx.JSONP(iris.Map{"hello": "jsonp"}, iris.JSONP{Callback: "callbackName"})})app.Get("/xml", func(ctx iris.Context) {ctx.XML(iris.Map{"message": "hello", "status": iris.StatusOK})})app.Get("/markdown", func(ctx iris.Context) {ctx.Markdown([]byte("# Hello Dynamic Markdown -- iris"))})app.Get("/yaml", func(ctx iris.Context) {ctx.YAML(iris.Map{"message": "hello", "status": iris.StatusOK})})app.Get("/msgpack", func(ctx iris.Context) {u := User{Firstname: "John",Lastname: "Doe",City: "Neither FBI knows!!!",Age: 25,}ctx.MsgPack(u)})// Render using jsoniter instead of the encoding/json:app.Listen(":8080", iris.WithOptimizations)}
Protobuf
Iris支持原生原生的protobuf和protobuf对JSON的编码和解码。
package mainimport ("app/protos""github.com/kataras/iris/v12")func main() {app := iris.New()app.Get("/", send)app.Get("/json", sendAsJSON)app.Post("/read", read)app.Post("/read_json", readFromJSON)app.Listen(":8080")}func send(ctx iris.Context) {response := &protos.HelloReply{Message: "Hello, World!"}ctx.Protobuf(response)}func sendAsJSON(ctx iris.Context) {response := &protos.HelloReply{Message: "Hello, World!"}options := iris.JSON{Proto: iris.ProtoMarshalOptions{AllowPartial: true,Multiline: true,Indent: " ",},}ctx.JSON(response, options)}func read(ctx iris.Context) {var request protos.HelloRequesterr := ctx.ReadProtobuf(&request)if err != nil {ctx.StopWithError(iris.StatusBadRequest, err)return}ctx.Writef("HelloRequest.Name = %s", request.Name)}func readFromJSON(ctx iris.Context) {var request protos.HelloRequesterr := ctx.ReadJSONProtobuf(&request)if err != nil {ctx.StopWithError(iris.StatusBadRequest, err)return}ctx.Writef("HelloRequest.Name = %s", request.Name)}
使用静态文件
func main() {app := iris.New()app.Favicon("./resources/favicon.ico")app.HandleDir("/assets", iris.Dir("./assets"))app.Listen(":8080")}
HandleDir方法接受第三个可选参数DirOptions:
type DirOptions struct {// Defaults to "/index.html", if request path is ending with **/*/$IndexName// then it redirects to **/*(/) which another handler is handling it,// that another handler, called index handler, is auto-registered by the framework// if end developer does not managed to handle it by hand.IndexName string// PushTargets filenames (map's value) to// be served without additional client's requests (HTTP/2 Push)// when a specific request path (map's key WITHOUT prefix)// is requested and it's not a directory (it's an `IndexFile`).//// Example:// "/": {// "favicon.ico",// "js/main.js",// "css/main.css",// }PushTargets map[string][]string// PushTargetsRegexp like `PushTargets` but accepts regexp which// is compared against all files under a directory (recursively).// The `IndexName` should be set.//// Example:// "/": regexp.MustCompile("((.*).js|(.*).css|(.*).ico)$")// See `iris.MatchCommonAssets` too.PushTargetsRegexp map[string]*regexp.Regexp// Cache to enable in-memory cache and pre-compress files.Cache DirCacheOptions// When files should served under compression.Compress bool// List the files inside the current requested directory if `IndexName` not found.ShowList bool// If `ShowList` is true then this function will be used instead// of the default one to show the list of files of a current requested directory(dir).// See `DirListRich` package-level function too.DirList DirListFunc// Files downloaded and saved locally.Attachments Attachments// Optional validator that loops through each requested resource.AssetValidator func(ctx *context.Context, name string) bool}
了解更多关于文件服务器的信息。
上下文服务数据
SendFile(filename string, destinationName string) errorSendFileWithRate(src, destName string, limit float64, burst int) error
使用
强制发送文件到客户端:
func handler(ctx iris.Context) {src := "./files/first.zip"ctx.SendFile(src, "client.zip")}
限制下载速度~50Kb/s,一次100KB的下载:
func handler(ctx iris.Context) {src := "./files/big.zip"// optionally, keep it empty to resolve the filename based on the "src".dest := ""limit := 50.0 * iris.KBburst := 100 * iris.KBctx.SendFileWithRate(src, dest, limit, burst)}
ServeContent(content io.ReadSeeker, filename string, modtime time.Time)ServeContentWithRate(content io.ReadSeeker, filename string, modtime time.Time, limit float64, burst int)ServeFile(filename string) errorServeFileWithRate(filename string, limit float64, burst int) error
使用
func handler(ctx iris.Context) {ctx.ServeFile("./public/main.js")}
模式渲染
Iris支持8个开箱即用的模板引擎,开发人员仍然可以使用任何外部的golang模板引擎,因为Context.ResponseWriter()是一个io.Writer。
所有的模板引擎都共享一个通用的API,例如使用嵌入资产的解析,布局和特定于团队的布局,模板函数,部分渲染等等。
| # | Name | Parser |
|---|---|---|
| 1 | HTML | html/template |
| 2 | Blocks | kataras/blocks |
| 3 | Django | flosch/pongo2 |
| 4 | Pug | Joker/jade |
| 5 | Handlebars | aymerick/raymond |
| 6 | Amber | eknkc/amber |
| 7 | Jet | CloudyKit/jet |
| 8 | Ace | yosssi/ace |
List of Examples
List of Benchmarks
视图引擎可以为每一方注册。要注册一个视图引擎,可以使用Application/Party.RegisterView(ViewEngine)方法,如下所示。
从”./view”目录加载所有扩展名是”.html”,并使用标准的html/模板包解析它们。
// [app := iris.New...]tmpl := iris.HTML("./views", ".html")app.RegisterView(tmpl)
使用Context.View呈现或执行视图。视图方法在主路由的处理程序内。
ctx.View("hi.html")
要通过中间件或主处理程序在视图中使用键-值模式绑定Go值,请使用上下文。在Context.View之前的ViewData方法。一个视图。
绑定值为”Hello world”的变量。
ctx.ViewData("message", "Hello world!")
根绑定:
ctx.View("user-page.html", User{})// root binding as {{.Name}}
要添加模板函数,请使用首选视图引擎的AddFunc方法。
// func name, input arguments, render valuetmpl.AddFunc("greet", func(s string) string {return "Greetings " + s + "!"})
要在每个请求上重新加载,请调用视图引擎的reload方法。
tmpl.Reload(true)
要使用嵌入式模板而不依赖于本地文件系统,请使用go-bindata外部工具并将其AssetFile()生成的函数传递给首选视图引擎的第一个输入参数。
tmpl := iris.HTML(AssetFile(), ".html")
代码示例:
// file: main.gopackage mainimport "github.com/kataras/iris/v12"func main() {app := iris.New()// Parse all templates from the "./views" folder// where extension is ".html" and parse them// using the standard `html/template` package.tmpl := iris.HTML("./views", ".html")// Set custom delimeters.tmpl.Delims("{{", "}}")// Enable re-build on local template files changes.tmpl.Reload(true)// Default template funcs are://// - {{ urlpath "myNamedRoute" "pathParameter_ifNeeded" }}// - {{ render "header.html" }}// and partial relative path to current page:// - {{ render_r "header.html" }}// - {{ yield }}// - {{ current }}// Register a custom template func:tmpl.AddFunc("greet", func(s string) string {return "Greetings " + s + "!"})// Register the view engine to the views,// this will load the templates.app.RegisterView(tmpl)// Method: GET// Resource: http://localhost:8080app.Get("/", func(ctx iris.Context) {// Bind: {{.message}} with "Hello world!"ctx.ViewData("message", "Hello world!")// Render template file: ./views/hi.htmlctx.View("hi.html")})app.Listen(":8080")}
<!-- file: ./views/hi.html --><html><head><title>Hi Page</title></head><body><h1>{{.message}}</h1><strong>{{greet "to you"}}</strong></body></html>
已经渲染的效果看起来像这样:
<html><head><title>Hi Page</title></head><body><h1>Hello world!</h1><strong>Greetings to you!</strong></body></html>
多个模板文件
Iris允许每个应用程序无限制地注册视图引擎。除此之外,您还可以为每一方或通过中间件注册一个视图引擎!
// Register a view engine per group of routes.adminGroup := app.Party("/admin")adminGroup.RegisterView(iris.Blocks("./views/admin", ".html"))
通过中间件
func middleware(views iris.ViewEngine) iris.Handler {return func(ctx iris.Context) {ctx.ViewEngine(views)ctx.Next()}}
使用
// Register a view engine on-fly for the current chain of handlers.views := iris.Blocks("./views/on-fly", ".html")views.Load()app.Get("/", setViews(views), onFly)
重定向
发出HTTP重定向很容易。支持内部和外部位置。我们说的位置是指路径,子域,域等等。
处理器
app.Get("/", func(ctx iris.Context) {ctx.Redirect("https://golang.org/dl", iris.StatusMovedPermanently)})
从POST发出HTTP重定向。
app.Post("/", func(ctx iris.Context) {ctx.Redirect("/login", iris.StatusFound)})
从处理程序发出本地路由器重定向,使用应用程序。ServeHTTPC或Exec(),如下所示。
app.Get("/test", func(ctx iris.Context) {r := ctx.Request()r.URL.Path = "/test2"ctx.Application().ServeHTTPC(ctx)// OR// ctx.Exec("GET", "/test2")})app.Get("/test2", func(ctx iris.Context) {ctx.JSON(iris.Map{"hello": "world"})})
全局
使用我们都喜欢的语法。
import "github.com/kataras/iris/v12/middleware/rewrite"
func main() {app := iris.New()// [...routes]redirects := rewrite.Load("redirects.yml")app.WrapRouter(redirects)app.Listen(":80")}
redirect.yml 示例:
RedirectMatch:# Redirects /seo/* to /*- 301 /seo/(.*) /$1# Redirects /docs/v12* to /docs- 301 /docs/v12(.*) /docs# Redirects /old(.*) to /- 301 /old(.*) /# Redirects http or https://test.* to http or https://newtest.*- 301 ^(http|https)://test.(.*) $1://newtest.$2# Handles /*.json or .xml as *?format=json or xml,# without redirect. See /users route.# When Code is 0 then it does not redirect the request,# instead it changes the request URL# and leaves a route handle the request.- 0 /(.*).(json|xml) /$1?format=$2# Redirects root domain to www.# Creation of a www subdomain inside the Application is unnecessary,# all requests are handled by the root Application itself.PrimarySubdomain: www
例子链接:rewrite middleware example.
自定义中间件
func Logger() iris.Handler {return func(ctx iris.Context) {t := time.Now()// Set a shared variable between handlersctx.Values().Set("framework", "iris")// before requestctx.Next()// after requestlatency := time.Since(t)log.Print(latency)// access the status we are sendingstatus := ctx.GetStatusCode()log.Println(status)}}func main() {app := iris.New()app.Use(Logger())app.Get("/test", func(ctx iris.Context) {// retrieve a value set by the middleware.framework := ctx.Values().GetString("framework")// it would print: "iris"log.Println(framework)})app.Listen(":8080")}
使用基本身份信息验证
导入basicauth 中间件
import "github.com/kataras/iris/v12/middleware/basicauth"
func main() {app := iris.New()// Group using basicauth middleware.authConfig := basicauth.Config{Users: map[string]string{"admin": "admin_password","user" : "user_password",},Realm: "Authorization Required",Expires: time.Duration(30) * time.Minute,}authorized := app.Party("/admin", basicauth.New(authConfig))// /admin/secrets endpoint// hit "localhost:8080/admin/secretsauthorized.Get("/secrets", func(ctx iris.Context) {// get user, it was set by the BasicAuth middlewareusername, password, _ := ctx.Request().BasicAuth()ctx.Writef("Hello, %s!", username)// [...]})app.Listen(":8080")}
如果您想注册一个没有匹配路由和错误的中间件,您应该使用UseRouter方法。例如,如果你也想保护/admin 404s,那么这样做:
authorized.UseRouter(basicauth.New(authConfig))
更多例子:_examples/auth.
中间件中的Goroutines
当在中间件或处理程序中启动新的Goroutines时,您不应该在其中使用原始上下文,您必须使用只读副本。
func main() {app := iris.Default()app.Get("/long_async", func(ctx iris.Context) {// create a clone to be used inside the goroutinectxCopy := ctx.Clone()go func() {// simulate a long task with time.Sleep(). 5 secondstime.Sleep(5 * time.Second)// note that you are using the copied context "ctxCopy", IMPORTANTlog.Printf("Done! in path: %s", ctxCopy.Path())}()})app.Get("/long_sync", func(ctx iris.Context) {// simulate a long task with time.Sleep(). 5 secondstime.Sleep(5 * time.Second)// since we are NOT using a goroutine, we do not have to copy the contextlog.Printf("Done! in path: %s", ctx.Path())})app.Listen(":8080")}
自定义HTTP配置
在_examples/http-server文件夹中可以找到超过12个关于http服务器配置的示例。
直接使用http.ListenAndServe(),如下所示:
func main() {app := iris.New()// [...routes]if err := app.Build(); err!=nil{panic(err)}http.ListenAndServe(":8080", app)}
注意,在将其用作http.Handler之前,应该手动调用其构建方法来构建应用程序和路由器。
另一个例子:
func main() {app := iris.New()// [...routes]app.Build()srv := &http.Server{Addr: ":8080",Handler: app,ReadTimeout: 10 * time.Second,WriteTimeout: 10 * time.Second,MaxHeaderBytes: 1 << 20,}srv.ListenAndServe()}
但是,您很少需要外部http。服务器实例与Iris。您可以通过应用程序使用任何tcp侦听器、http服务器或自定义函数进行侦听。运行方法。
app.Run(iris.Listener(l net.Listener)) // listen using a custom net.Listenerapp.Run(iris.Server(srv *http.Server)) // listen using a custom http.Serverapp.Run(iris.Addr(addr string)) // the app.Listen is a shortcut of this method.app.Run(iris.TLS(addr string, certFileOrContents, keyFileOrContents string)) // listen TLS.app.Run(iris.AutoTLS(addr, domain, email string)) // listen using letsencrypt (see below).// and any custom function that returns an error:app.Run(iris.Raw(f func() error))
socket 分区
此选项允许在多cpu服务器上线性扩展服务器性能。详情请参阅https://www.nginx.com/blog/soct-sharing-nginx-rele-1-9-1/。iris.WithSocketSharding配置器。
示例代码:
package mainimport ("time""github.com/kataras/iris/v12")func main() {startup := time.Now()app := iris.New()app.Get("/", func(ctx iris.Context) {s := startup.Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())ctx.Writef("This server started at: %s\n", s)})app.Listen(":8080", iris.WithSocketSharding)// or app.Run(..., iris.WithSocketSharding)}
支持的加密
1行LetsEncrypt HTTPS服务器的例子。
package mainimport ("log""github.com/iris-gonic/autotls""github.com/kataras/iris/v12")func main() {app := iris.Default()// Ping handlerapp.Get("/ping", func(ctx iris.Context) {ctx.WriteString("pong")})app.Run(iris.AutoTLS(":443", "example.com example2.com", "mail@example.com"))}
自定义TLS的例子(你也可以绑定一个autocert管理器):
app.Run(iris.TLS(":443", "", "", func(su *iris.Supervisor) {su.Server.TLSConfig = &tls.Config{/* your custom fields */},}),)
所有的iris.Runner方法,如:Addr, TLS, AutoTLS, Server, Listener和e.t.c接受func(*iris.Supervisor)的可变输入参数来配置http服务器实例的构建状态。
使用Iris运行多个服务
package mainimport ("log""net/http""time""github.com/kataras/iris/v12""github.com/kataras/iris/v12/middleware/recover""golang.org/x/sync/errgroup")var g errgroup.Groupfunc startApp1() error {app := iris.New().SetName("app1")app.Use(recover.New())app.Get("/", func(ctx iris.Context) {app.Get("/", func(ctx iris.Context) {ctx.JSON(iris.Map{"code": iris.StatusOK,"message": "Welcome server 1",})})})app.Build()return app.Listen(":8080")}func startApp2() error {app := iris.New().SetName("app2")app.Use(recover.New())app.Get("/", func(ctx iris.Context) {ctx.JSON(iris.Map{"code": iris.StatusOK,"message": "Welcome server 2",})})return app.Listen(":8081")}func main() {g.Go(startApp1)g.Go(startApp2)if err := g.Wait(); err != nil {log.Fatal(err)}}
通过应用程序包管理多个Iris实例。点击这里了解更多内容。
优雅关机或重启
有一些方法可以用来执行优雅的关闭或重新启动。你可以使用专门为此而构建的第三方软件包,或者您可以使用app.Shutdown(context.Context)方法。例子可以在这里找到。
注册一个事件在CTRL/CMD+C使用iris.RegisterOnInterrupt:
idleConnsClosed := make(chan struct{})iris.RegisterOnInterrupt(func() {timeout := 10 * time.Secondctx, cancel := stdContext.WithTimeout(stdContext.Background(), timeout)defer cancel()// close all hosts.app.Shutdown(ctx)close(idleConnsClosed)})// [...]app.Listen(":8080", iris.WithoutInterruptHandler, iris.WithoutServerError(iris.ErrServerClosed))<-idleConnsClosed
使用模板构建单个二进制文件
通过使用[go-bindata][https://github.com/go-bindata/go-bindata]的AssetFile生成函数,可以将服务器构建为包含模板的单个二进制文件。
$ go get -u github.com/go-bindata/go-bindata/...$ go-bindata -fs -prefix "templates" ./templates/...$ go run .
示例代码:
func main() {app := iris.New()tmpl := iris.HTML(AssetFile(), ".html")tmpl.Layout("layouts/layout.html")tmpl.AddFunc("greet", func(s string) string {return "Greetings " + s + "!"})app.RegisterView(tmpl)// [...]}
参见_examples/view中的完整示例。
尝试将body绑定到不同的结构中
绑定请求体的常规方法使用ctx.Request()。主体和他们不能被称为多次,除非iris.WithoutBodyConsumptionOnUnmarshal配置器被传递给app.Run/Listen。
package mainimport "github.com/kataras/iris/v12"func main() {app := iris.New()app.Post("/", logAllBody, logJSON, logFormValues, func(ctx iris.Context) {// body, err := ioutil.ReadAll(ctx.Request().Body) once orbody, err := ctx.GetBody() // as many times as you need.if err != nil {ctx.StopWithError(iris.StatusInternalServerError, err)return}if len(body) == 0 {ctx.WriteString(`The body was empty.`)} else {ctx.WriteString("OK body is still:\n")ctx.Write(body)}})app.Listen(":8080", iris.WithoutBodyConsumptionOnUnmarshal)}func logAllBody(ctx iris.Context) {body, err := ctx.GetBody()if err == nil && len(body) > 0 {ctx.Application().Logger().Infof("logAllBody: %s", string(body))}ctx.Next()}func logJSON(ctx iris.Context) {var p interface{}if err := ctx.ReadJSON(&p); err == nil {ctx.Application().Logger().Infof("logJSON: %#+v", p)}ctx.Next()}func logFormValues(ctx iris.Context) {values := ctx.FormValues()if values != nil {ctx.Application().Logger().Infof("logFormValues: %v", values)}ctx.Next()}
可以使用ReadBody根据客户机的内容类型将结构绑定到请求。您还可以使用内容协商。这里有一个完整的例子:
package mainimport ("github.com/kataras/iris/v12")func main() {app := newApp()// See main_test.go for usage.app.Listen(":8080")}func newApp() *iris.Application {app := iris.New()// To automatically decompress using gzip:// app.Use(iris.GzipReader)app.Use(setAllowedResponses)app.Post("/", readBody)return app}type payload struct {Message string `json:"message" xml:"message" msgpack:"message" yaml:"Message" url:"message" form:"message"`}func readBody(ctx iris.Context) {var p payload// Bind request body to "p" depending on the content-type that client sends the data,// e.g. JSON, XML, YAML, MessagePack, Protobuf, Form and URL Query.err := ctx.ReadBody(&p)if err != nil {ctx.StopWithProblem(iris.StatusBadRequest,iris.NewProblem().Title("Parser issue").Detail(err.Error()))return}// For the sake of the example, log the received payload.ctx.Application().Logger().Infof("Received: %#+v", p)// Send back the payload depending on the accept content type and accept-encoding of the client,// e.g. JSON, XML and so on.ctx.Negotiate(p)}func setAllowedResponses(ctx iris.Context) {// Indicate that the Server can send JSON, XML, YAML and MessagePack for this request.ctx.Negotiation().JSON().XML().YAML().MsgPack()// Add more, allowed by the server format of responses, mime types here...// If client is missing an "Accept: " header then default it to JSON.ctx.Negotiation().Accept.JSON()ctx.Next()}
HTTP2服务器推送
完整的示例代码可以在_examples/response-writer/http2push中找到。
服务器推送让服务器在用户没有明确要求的情况下,先发制人地将网站资产“推送”给客户端。如果小心使用,我们可以发送我们知道用户将需要的页面,他们正在请求。
package mainimport ("net/http""github.com/kataras/iris/v12")func main() {app := iris.New()app.Get("/", pushHandler)app.Get("/main.js", simpleAssetHandler)app.Run(iris.TLS("127.0.0.1:443", "mycert.crt", "mykey.key"))// $ openssl req -new -newkey rsa:4096 -x509 -sha256 \// -days 365 -nodes -out mycert.crt -keyout mykey.key}func pushHandler(ctx iris.Context) {// The target must either be an absolute path (like "/path") or an absolute// URL that contains a valid host and the same scheme as the parent request.// If the target is a path, it will inherit the scheme and host of the// parent request.target := "/main.js"if pusher, ok := ctx.ResponseWriter().Naive().(http.Pusher); ok {err := pusher.Push(target, nil)if err != nil {if err == iris.ErrPushNotSupported {ctx.StopWithText(iris.StatusHTTPVersionNotSupported, "HTTP/2 push not supported.")} else {ctx.StopWithError(iris.StatusInternalServerError, err)}return}}ctx.HTML(`<html><body><script src="%s"></script></body></html>`, target)}func simpleAssetHandler(ctx iris.Context) {ctx.ServeFile("./public/main.js")}
设置和获取cookie
安全cookie,编码和解码,会话(和会话缩放),flash消息和更多可以在_examples/cookies和_examples/sessions目录中找到。
import "github.com/kataras/iris/v12"func main() {app := iris.Default()app.Get("/cookie", func(ctx iris.Context) {value := ctx.GetCookie("my_cookie")if value == "" {value = "NotSet"ctx.SetCookieKV("my_cookie", value)// Alternatively: ctx.SetCookie(&http.Cookie{...})ctx.SetCookie("", "test", 3600, "/", "localhost", false, true)}ctx.Writef("Cookie value: %s \n", cookie)})app.Listen(":8080")}
如果你想自定义路径:
ctx.SetCookieKV(name, value, iris.CookiePath("/custom/path/cookie/will/be/stored"))
如果你想只对当前请求路径可见:
ctx.SetCookieKV(name, value, iris.CookieCleanPath /* or iris.CookiePath("") */)
More:
- iris.CookieAllowReclaim
- iris.CookieAllowSubdomains
- iris.CookieSecure
- iris.CookieHTTPOnly
- iris.CookieSameSite
- iris.CookiePath
- iris.CookieCleanPath
- iris.CookieExpires
- iris.CookieEncoding
你也可以在一个中间件中为整个请求添加cookie选项:
func setCookieOptions(ctx iris.Context) {ctx.AddCookieOptions(iris.CookieHTTPOnly(true), iris.CookieExpires(1*time.Hour))ctx.Next()}
测试
Iris为httpexpect (web应用程序的测试框架)提供了令人难以置信的支持。iris/httptest子包为iris + httpexpect提供了帮助程序。
如果您喜欢Go的标准net/http/httptest包,您仍然可以使用它。因为每个http web框架都与任何外部测试工具兼容,所以最终它就是http。
测试基本身份验证
在第一个示例中,我们将使用iris/httptest测试基本身份验证。
1.main.go源文件看起来是这样的:
package mainimport ("github.com/kataras/iris/v12""github.com/kataras/iris/v12/middleware/basicauth")func newApp() *iris.Application {app := iris.New()authConfig := basicauth.Config{Users: map[string]string{"myusername": "mypassword"},}authentication := basicauth.New(authConfig)app.Get("/", func(ctx iris.Context) { ctx.Redirect("/admin") })needAuth := app.Party("/admin", authentication){//http://localhost:8080/adminneedAuth.Get("/", h)// http://localhost:8080/admin/profileneedAuth.Get("/profile", h)// http://localhost:8080/admin/settingsneedAuth.Get("/settings", h)}return app}func h(ctx iris.Context) {username, password, _ := ctx.Request().BasicAuth()// third parameter ^ will be always true because the middleware// makes sure for that, otherwise this handler will not be executed.ctx.Writef("%s %s:%s", ctx.Path(), username, password)}func main() {app := newApp()app.Listen(":8080")}
- 现在,创建一个main_test.go。点击“文件”并复制粘贴以下内容。 ```go package main
import ( “testing”
"github.com/kataras/iris/v12/httptest"
)
func TestNewApp(t *testing.T) { app := newApp() e := httptest.New(t, app)
// redirects to /admin without basic authe.GET("/").Expect().Status(httptest.StatusUnauthorized)// without basic authe.GET("/admin").Expect().Status(httptest.StatusUnauthorized)// with valid basic authe.GET("/admin").WithBasicAuth("myusername", "mypassword").Expect().Status(httptest.StatusOK).Body().Equal("/admin myusername:mypassword")e.GET("/admin/profile").WithBasicAuth("myusername", "mypassword").Expect().Status(httptest.StatusOK).Body().Equal("/admin/profile myusername:mypassword")e.GET("/admin/settings").WithBasicAuth("myusername", "mypassword").Expect().Status(httptest.StatusOK).Body().Equal("/admin/settings myusername:mypassword")// with invalid basic authe.GET("/admin/settings").WithBasicAuth("invalidusername", "invalidpassword").Expect().Status(httptest.StatusUnauthorized)
}
3.打开命令行并执行:```shell$ go test -v
测试cookie
package mainimport ("fmt""testing""github.com/kataras/iris/v12/httptest")func TestCookiesBasic(t *testing.T) {app := newApp()e := httptest.New(t, app, httptest.URL("http://example.com"))cookieName, cookieValue := "my_cookie_name", "my_cookie_value"// Test Set A Cookie.t1 := e.GET(fmt.Sprintf("/cookies/%s/%s", cookieName, cookieValue)).Expect().Status(httptest.StatusOK)// Validate cookie's existence, it should be available now.t1.Cookie(cookieName).Value().Equal(cookieValue)t1.Body().Contains(cookieValue)path := fmt.Sprintf("/cookies/%s", cookieName)// Test Retrieve A Cookie.t2 := e.GET(path).Expect().Status(httptest.StatusOK)t2.Body().Equal(cookieValue)// Test Remove A Cookie.t3 := e.DELETE(path).Expect().Status(httptest.StatusOK)t3.Body().Contains(cookieName)t4 := e.GET(path).Expect().Status(httptest.StatusOK)t4.Cookies().Empty()t4.Body().Empty()}
$ go test -v -run=TestCookiesBasic$
Iris web框架本身使用这个包来测试自己。在_examples存储库目录中,您还可以找到一些有用的测试。要了解更多信息,请查看和阅读httpexpect的文档。
本地化
介绍
本地化特性提供了一种方便的方法来检索各种语言中的字符串,从而使您可以轻松地在应用程序中支持多种语言。语言字符串存储在。/locale目录中的文件中。在这个目录下,应用程序支持的每种语言都有一个子目录:
│ main.go└───locales├───el-GR│ home.yml├───en-US│ home.yml└───zh-CNhome.yml
应用程序的默认语言是第一种注册语言。
app := iris.New()// First parameter: Glob filpath patern,// Second variadic parameter: Optional language tags,// the first one is the default/fallback one.app.I18n.Load("./locales/*/*", "en-US", "el-GR", "zh-CN")
或者如果你加载所有语言通过:
app.I18n.Load("./locales/*/*")// Then set the default language using:app.I18n.SetDefault("en-US")
负载嵌入式部分
您可能希望在应用程序可执行文件中使用go-bindata工具嵌入区域设置。
- 安装一个 go-bindata 工具。例如:
2.将本地文件嵌入到应用程序中$ go get -u github.com/go-bindata/go-bindata/...
3.使用LoadAssets方法初始化和加载语言$ go-bindata -o locales.go ./locales/...
^资产名称和资产函数是由go-bindata生成的ap.I18n.LoadAssets(AssetNames, Asset, "en-US", "el-GR", "zh-CN")
定义翻译
语言环境文件可以用YAML(推荐)、JSON、TOML或INI格式编写。
每个文件都应该包含密钥。键也可以有子键(我们称它们为“节”)。
每个键的值应该是由其翻译文本(或模板)或/及其复数键值包含的形式字符串或映射。
Iris i18n模块支持开箱即用的多元化,见下面。
Fmt风格
hi: "Hi %s!"
ctx.Tr("Hi", "John")// Outputs: Hi John!
多元化
Iris i18n支持复数变量。要定义按地区设置的变量,必须定义Vars键的一个新部分。
变量可接受的键值是:
- one
- “=x” where x is a number
- “<x”
- other
- format
例子:
Vars:- Minutes:one: "minute"other: "minutes"- Houses:one: "house"other: "houses"
然后,每条消息都可以使用这个变量,如下所示:
# Using variables in raw stringYouLate: "You are %[1]d ${Minutes} late."# [x] is the argument position,# variables always have priority other fmt-style arguments,# that's why we see [1] for houses and [2] for the string argument.HouseCount: "%[2]s has %[1]d ${Houses}."
ctx.Tr("YouLate", 1)// Outputs: You are 1 minute late.ctx.Tr("YouLate", 10)// Outputs: You are 10 minutes late.ctx.Tr("HouseCount", 2, "John")// Outputs: John has 2 houses.
您可以选择什么消息将显示基于给定的复数计数。
除了变量,每个消息也可以有其复数形式!
可接受的钥匙:
- zero
- one
- two
- “=x”
- “<x”
- “>x”
- other
让我们创建一个简单的多特性消息,它也可以使用我们在上面创建的Minutes变量。
FreeDay:"=3": "You have three days and %[2]d ${Minutes} off." # "FreeDay" 3, 15one: "You have a day off." # "FreeDay", 1other: "You have %[1]d free days." # "FreeDay", 5
ctx.Tr("FreeDay", 3, 15)// Outputs: You have three days and 15 minutes off.ctx.Tr("FreeDay", 1)// Outputs: You have a day off.ctx.Tr("FreeDay", 5)// Outputs: You have 5 free days.
让我们继续使用更高级的示例,使用模板文本+函数+复数+变量。
Vars:- Houses:one: "house"other: "houses"- Gender:"=1": "She""=2": "He"VarTemplatePlural:one: "${Gender} is awesome!"other: "other (${Gender}) has %[3]d ${Houses}.""=5": "{{call .InlineJoin .Names}} are awesome."
const (female = iota + 1male)ctx.Tr("VarTemplatePlural", iris.Map{"PluralCount": 5,"Names": []string{"John", "Peter"},"InlineJoin": func(arr []string) string {return strings.Join(arr, ", ")},})// Outputs: John, Peter are awesomectx.Tr("VarTemplatePlural", 1, female)// Outputs: She is awesome!ctx.Tr("VarTemplatePlural", 2, female, 5)// Outputs: other (She) has 5 houses.
分片
如果键不是预留的(例如,1,two…),那么它就充当子节。各部分由点字符(.)分隔。
Welcome:Message: "Welcome {{.Name}}"
ctx.Tr("Welcome.Message", iris.Map{"Name": "John"})// Outputs: Welcome John
确定当前区域设置
你可以使用context.GetLocale。方法来确定当前语言环境或检查语言环境是否为给定值:
func(ctx iris.Context) {locale := ctx.GetLocale()// [...]}
Locale接口如下所示。
// Locale is the interface which returns from a `Localizer.GetLocale` metod.// It serves the transltions based on "key" or format. See `GetMessage`.type Locale interface {// Index returns the current locale index from the languages list.Index() int// Tag returns the full language Tag attached tothis Locale,// it should be uniue across different Locales.Tag() *language.Tag// Language should return the exact languagecode of this `Locale`//that the user provided on `New` function.//// Same as `Tag().String()` but it's static.Language() string// GetMessage should return translated text based n the given "key".GetMessage(key string, args ...interface{}) string}
检索翻译
使用context.Tr方法作为获取该请求的翻译文本的快捷方式。
func(ctx iris.Context) {text := ctx.Tr("hi", "name")// [...]}
视图中使用
func(ctx iris.Context) {ctx.View("index.html", iris.Map{"tr": ctx.Tr,})}
例子
package mainimport ("github.com/kataras/iris/v12")func newApp() *iris.Application {app := iris.New()// Configure i18n.// First parameter: Glob filpath patern,// Second variadic parameter: Optional language tags, the first one is the default/fallback one.app.I18n.Load("./locales/*/*.ini", "en-US", "el-GR", "zh-CN")// app.I18n.LoadAssets for go-bindata.// Default values:// app.I18n.URLParameter = "lang"// app.I18n.Subdomain = true//// Set to false to disallow path (local) redirects,// see https://github.com/kataras/iris/issues/1369.// app.I18n.PathRedirect = trueapp.Get("/", func(ctx iris.Context) {hi := ctx.Tr("hi", "iris")locale := ctx.GetLocale()ctx.Writef("From the language %s translated output: %s", locale.Language(), hi)})app.Get("/some-path", func(ctx iris.Context) {ctx.Writef("%s", ctx.Tr("hi", "iris"))})app.Get("/other", func(ctx iris.Context) {language := ctx.GetLocale().Language()fromFirstFileValue := ctx.Tr("key1")fromSecondFileValue := ctx.Tr("key2")ctx.Writef("From the language: %s, translated output:\n%s=%s\n%s=%s",language, "key1", fromFirstFileValue,"key2", fromSecondFileValue)})// using in inside your views:view := iris.HTML("./views", ".html")app.RegisterView(view)app.Get("/templates", func(ctx iris.Context) {ctx.View("index.html", iris.Map{"tr": ctx.Tr, // word, arguments... {call .tr "hi" "iris"}}})// Note that,// Iris automatically adds a "tr" global template function as well,// the only difference is the way you call it inside your templates and// that it accepts a language code as its first argument.})//return app}func main() {app := newApp()// go to http://localhost:8080/el-gr/some-path// ^ (by path prefix)//// or http://el.mydomain.com8080/some-path// ^ (by subdomain - test locally with the hosts file)//// or http://localhost:8080/zh-CN/templates// ^ (by path prefix with uppercase)//// or http://localhost:8080/some-path?lang=el-GR// ^ (by url parameter)//// or http://localhost:8080 (default is en-US)// or http://localhost:8080/?lang=zh-CN//// go to http://localhost:8080/other?lang=el-GR// or http://localhost:8080/other (default is en-US)// or http://localhost:8080/other?lang=en-US//// or use cookies to set the language.app.Listen(":8080", iris.WithSitemap("http://localhost:8080"))}
站点地图
如果app.I18n,站点地图翻译会根据路径前缀自动设置每个路由。如果app.I18n,则PathRedirect为true或by子域。如果app.I18n.子域名为真或按URL查询参数。URLParameter不是空的。
更多信息请访问:https://support.google.com/webmasters/answer/189077?hl=en
GET http://localhost:8080/sitemap.xml
<?xml version="1.0" encoding="utf-8" standalone="yes"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"><url><loc>http://localhost:8080/</loc><xhtml:link rel="alternate" hreflang="en-US" href="http://localhost:8080/"></xhtml:link><xhtml:link rel="alternate" hreflang="el-GR" href="http://localhost:8080/el-GR/"></xhtml:link><xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:8080/zh-CN/"></xhtml:link></url><url><loc>http://localhost:8080/some-path</loc><xhtml:link rel="alternate" hreflang="en-US" href="http://localhost:8080/some-path"></xhtml:link><xhtml:link rel="alternate" hreflang="el-GR" href="http://localhost:8080/el-GR/some-path"></xhtml:link><xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:8080/zh-CN/some-path"></xhtml:link></url><url><loc>http://localhost:8080/other</loc><xhtml:link rel="alternate" hreflang="en-US" href="http://localhost:8080/other"></xhtml:link><xhtml:link rel="alternate" hreflang="el-GR" href="http://localhost:8080/el-GR/other"></xhtml:link><xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:8080/zh-CN/other"></xhtml:link></url><url><loc>http://localhost:8080/templates</loc><xhtml:link rel="alternate" hreflang="en-US" href="http://localhost:8080/templates"></xhtml:link><xhtml:link rel="alternate" hreflang="el-GR" href="http://localhost:8080/el-GR/templates"></xhtml:link><xhtml:link rel="alternate" hreflang="zh-CN" href="http://localhost:8080/zh-CN/templates"></xhtml:link></url></urlset>
这就是关于Iris的所有基本知识。对于初学者来说,这个文档已经涵盖了足够多的内容。想成为一个专家和认证的Iris开发者,学习MVC, i18n,依赖注入,gRPC, lambda函数,websockets,最佳实践和更多?今天索取Iris电子书,参与Iris的开发!
