- 安装
- 快速开始
- 基准测试
- 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 main
import "github.com/kataras/iris/v12"
func main() {
app := iris.New()
booksAPI := app.Party("/books")
{
booksAPI.Use(iris.Compression)
// GET: http://localhost:8080/books
booksAPI.Get("/", list)
// POST: http://localhost:8080/books
booksAPI.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 Book
err := 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/books
func (c *BookController) Get() []Book {
return []Book{
{"Mastering Concurrency in Go"},
{"Go Design Patterns"},
{"Black Hat Go"},
}
}
// POST: http://localhost:8080/books
func (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 /user
app.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 " + action
ctx.WriteString(message)
})
// For each matched request Context will hold the route definition
app.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=Doe
app.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.1
Content-Type: application/x-www-form-urlencoded
name=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=kataras
Content-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.MB
func 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 file
file, _, 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: v1
v1 := app.Party("/v1")
{
v1.Post("/login", loginEndpoint)
v1.Post("/submit", submitEndpoint)
v1.Post("/read", readEndpoint)
}
// Simple group: v2
v2 := 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 main
import (
"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 default
app := 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 group
testing := authorized.Party("testing")
testing.Get("/analytics", analyticsEndpoint)
}
// Listen and serve on 0.0.0.0:8080
app.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" // default
level.Title = "[DBUG]" // default
level.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{}) error
ReadJSONProtobuf(ptr proto.Message, opts ...ProtoUnmarshalOptions) error
ReadProtobuf(ptr proto.Message) error
ReadMsgPack(ptr interface{}) error
ReadXML(outPtr interface{}) error
ReadYAML(outPtr interface{}) error
ReadForm(formObject interface{}) error
ReadQuery(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 main
import (
"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 User
err := 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 main
import "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 Person
if 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 main
import (
"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 Person
if 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 main
import "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 myParams
if 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 main
import "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 myHeaders
if 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 main
import "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 formExample
err := 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 string
Number 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 main
import (
"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.HelloRequest
err := 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.HelloRequest
err := 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) error
SendFileWithRate(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.KB
burst := 100 * iris.KB
ctx.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) error
ServeFileWithRate(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 value
tmpl.AddFunc("greet", func(s string) string {
return "Greetings " + s + "!"
})
要在每个请求上重新加载,请调用视图引擎的reload方法。
tmpl.Reload(true)
要使用嵌入式模板而不依赖于本地文件系统,请使用go-bindata外部工具并将其AssetFile()生成的函数传递给首选视图引擎的第一个输入参数。
tmpl := iris.HTML(AssetFile(), ".html")
代码示例:
// file: main.go
package main
import "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:8080
app.Get("/", func(ctx iris.Context) {
// Bind: {{.message}} with "Hello world!"
ctx.ViewData("message", "Hello world!")
// Render template file: ./views/hi.html
ctx.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 handlers
ctx.Values().Set("framework", "iris")
// before request
ctx.Next()
// after request
latency := time.Since(t)
log.Print(latency)
// access the status we are sending
status := 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/secrets
authorized.Get("/secrets", func(ctx iris.Context) {
// get user, it was set by the BasicAuth middleware
username, 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 goroutine
ctxCopy := ctx.Clone()
go func() {
// simulate a long task with time.Sleep(). 5 seconds
time.Sleep(5 * time.Second)
// note that you are using the copied context "ctxCopy", IMPORTANT
log.Printf("Done! in path: %s", ctxCopy.Path())
}()
})
app.Get("/long_sync", func(ctx iris.Context) {
// simulate a long task with time.Sleep(). 5 seconds
time.Sleep(5 * time.Second)
// since we are NOT using a goroutine, we do not have to copy the context
log.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.Listener
app.Run(iris.Server(srv *http.Server)) // listen using a custom http.Server
app.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 main
import (
"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 main
import (
"log"
"github.com/iris-gonic/autotls"
"github.com/kataras/iris/v12"
)
func main() {
app := iris.Default()
// Ping handler
app.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 main
import (
"log"
"net/http"
"time"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/middleware/recover"
"golang.org/x/sync/errgroup"
)
var g errgroup.Group
func 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.Second
ctx, 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 main
import "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 or
body, 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 main
import (
"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 main
import (
"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 main
import (
"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/admin
needAuth.Get("/", h)
// http://localhost:8080/admin/profile
needAuth.Get("/profile", h)
// http://localhost:8080/admin/settings
needAuth.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 auth
e.GET("/").Expect().Status(httptest.StatusUnauthorized)
// without basic auth
e.GET("/admin").Expect().Status(httptest.StatusUnauthorized)
// with valid basic auth
e.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 auth
e.GET("/admin/settings").WithBasicAuth("invalidusername", "invalidpassword").
Expect().Status(httptest.StatusUnauthorized)
}
3.打开命令行并执行:
```shell
$ go test -v
测试cookie
package main
import (
"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-CN
home.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 string
YouLate: "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, 15
one: "You have a day off." # "FreeDay", 1
other: "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 + 1
male
)
ctx.Tr("VarTemplatePlural", iris.Map{
"PluralCount": 5,
"Names": []string{"John", "Peter"},
"InlineJoin": func(arr []string) string {
return strings.Join(arr, ", ")
},
})
// Outputs: John, Peter are awesome
ctx.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 main
import (
"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 = true
app.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的开发!