Go 框架 iris 文档

news/2024/12/30 2:24:48/
目录

文章目录

    • 安装
      • [故障 排除](https://www.iris-go.com/docs/#/?id=troubleshooting)
    • 快速入门
    • 基准
    • 接口示例
      • 使用获取、发布、放置、修补、删除和选项
      • 路径中的参数
      • 查询字符串参数
      • 多部分/乌伦编码形式
      • 再比如:查询+帖子表单
      • 查询和发布表单参数
      • 上传文件
        • 单个文件
        • 多个文件
      • 对路线进行分组
      • 默认不带中间件的空白光圈
      • 使用中间件
      • 应用程序文件记录器
      • 控制日志输出着色
      • 请求日志记录
      • 模型绑定和验证
      • 绑定查询字符串
      • 绑定任何
      • 绑定网址路径参数
      • 绑定标头
      • [“绑定 HTML ”复选框](https://www.iris-go.com/docs/#/?id=bind-html-checkboxes)
      • [JSON、JSONP、XML、Markdown、YAML 和 MsgPack 渲染](https://www.iris-go.com/docs/#/?id=json-jsonp-xml-markdown-yaml-and-msgpack-rendering)
        • 普罗托布夫
      • 提供静态文件
      • 从上下文中提供数据
      • 模板渲染
      • 多模板
        • 通过中间件
      • [重 定向](https://www.iris-go.com/docs/#/?id=redirects)
        • 从处理程序
        • 全球
      • 自定义中间件
      • 使用基本身份验证
      • [中间件中的 Goroutines](https://www.iris-go.com/docs/#/?id=goroutines-inside-a-middleware)
      • [自定义 HTTP 配置](https://www.iris-go.com/docs/#/?id=custom-http-configuration)
      • 套接字分片
      • 支持让我们加密
      • 使用鸢尾花运行多个服务
      • 正常关机或重启
      • 使用模板构建单个二进制文件
      • 尝试将身体绑定到不同的结构中
      • [HTTP2 服务器推送](https://www.iris-go.com/docs/#/?id=http2-server-push)
      • 设置并获取饼干
    • [JSON 网络令牌](https://www.iris-go.com/docs/#/?id=json-web-tokens)
      • [何时应使用 JSON Web 令牌?](https://www.iris-go.com/docs/#/?id=when-should-you-use-json-web-tokens)
      • [将 JWT 与虹膜配合使用](https://www.iris-go.com/docs/#/?id=using-jwt-with-iris)
    • 测试
      • 测试基本身份验证
      • 测试饼干
    • 地方化
      • 介绍
      • 加载嵌入式区域设置
      • 定义翻译
      • [FMT 风格](https://www.iris-go.com/docs/#/?id=fmt-style)
      • 模板
      • 多元化
      • 部分
      • 确定当前区域设置
      • 检索翻译
      • 内部视图
      • ](https://www.iris-go.com/docs/#/?id=example)[例
      • 网站地图

安装

Iris是一个跨平台的软件。

唯一的要求是 Go 编程语言,版本 1.20 及更高版本。

$ mkdir myapp
$ cd myapp
$ go mod init myapp
$ go get github.com/kataras/iris/v12@latest

将其导入到代码中:

import "github.com/kataras/iris/v12"

故障 排除

如果在安装过程中遇到网络错误,请确保设置了有效的 GOPROXY 环境变量。

go env -w GOPROXY=https://goproxy.io,direct

如果上述方法均无效,请清理 go 模块缓存:

go clean --modcache

快速入门

# 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/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
}

运行您的鸢尾花网络服务器:

$ go run main.go
> Now listening on: http://localhost:8080
> Application started. Press CTRL+C to shut down.

列表书:

$ curl --header 'Accept-Encoding:gzip' http://localhost:8080/books[{"title": "Mastering Concurrency in Go"},{"title": "Go Design Patterns"},{"title": "Black Hat Go"}
]

创建新图书:

$ 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",
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NL5y3Dps-1686647378212)(null)]

基准

Iris使用muxie的自定义版本。

查看所有基准

📖 使用 int 的动态参数触发 200000 个请求,将 JSON 作为请求正文发送,并接收 JSON 作为响应。

名字语言要求/秒延迟吞吐量完成时间
虹膜238954521.69乌秒64.15兆字节0.84秒
琴酒229665541.96乌秒62.86兆字节0.87秒
228072545.78乌秒62.61兆字节0.88秒
回波224491553.84乌秒61.70兆字节0.89秒
马提尼酒198166627.46乌秒54.47兆字节1.01秒
茶隼C#163486766.90乌秒47.42兆字节1.23秒
水牛1024781.22毫秒28.14兆字节1.95秒
科阿爪哇语484252.56毫秒15.39兆字节4.14秒
表达爪哇语236225.25毫秒9.04兆字节8.41秒

接口示例

您可以在 Iris 示例存储库中找到许多随时可以运行的示例。

使用获取、发布、放置、修补、删除和选项

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")
}

内置可用参数类型:

参数类型去类型验证检索帮助程序
:string字符串任何(单路径段)Params().Get
:uuid字符串uuidv4 或 v1(单路径段)Params().Get
:int国际-9223372036854775808 到 9223372036854775807 (x64) 或 -2147483648 到 2147483647 (x32),取决于主机架构Params().GetInt
:int8国际8-128 到 127Params().GetInt8
:int16国际16-32768 到 32767Params().GetInt16
:int32国际32-2147483648 到 2147483647Params().GetInt32
:int64国际64-9223372036854775808 到 9223372036854775807Params().GetInt64
:uint乌因特0 到 18446744073709551615 (x64) 或 0 到 4294967295 (x32),取决于主机架构Params().GetUint
:uint8uint80 到 255Params().GetUint8
:uint16uint160 到 65535Params().GetUint16
:uint32uint320 到 4294967295Params().GetUint32
:uint64uint640 到 18446744073709551615Params().GetUint64
:bool布尔“1”或“t”或“T”或“真”或“真”或“真”或“0”或“f”或“F”或“假”或“假”或“假”Params().GetBool
:alphabetical字符串小写或大写字母Params().Get
:file字符串小写或大写字母、数字、下划线 (_)、短划线 (-)、点 (.) 并且没有空格或其他对文件名无效的特殊字符Params().Get
:path字符串任何内容都可以用斜杠(路径段)分隔,但应该是路由路径的最后一部分Params().Get
:mail字符串未经域验证的电子邮件Params().Get
:email字符串具有域验证功能的电子邮件Params().Get
:date字符串年/月/日格式,例如 /博客/{参数:日期} 匹配 /博客/2022/04/21Params().GetTimeParams().SimpleDate
:weekdayuint (0-6) 或字符串时间串。工作日长名称格式(“星期日”到“星期一”或“星期日”到“星期一”)格式,例如 /schedule/{param:weekday} 匹配 /schedule/星期一Params().GetWeekday

更多示例可在以下位置找到:_examples/路由。

查询字符串参数

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")
}

多部分/乌伦编码形式

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

(adsbygoogle = window.adsbygoogle || []).push({});

查询和发布表单参数

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.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, fileHeader, err:= ctx.FormFile("file")if err != nil {ctx.StopWithError(iris.StatusBadRequest, err)return}// Upload the file to specific destination.dest := filepath.Join("./uploads", fileHeader.Filename)ctx.SaveFormFile(fileHeader, dest)ctx.Writef("File: %s uploaded!", fileHeader.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/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())// 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")
}

(adsbygoogle = window.adsbygoogle || []).push({});

应用程序文件记录器

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")
}

控制日志输出着色

默认情况下,控制台上的日志输出应根据检测到的 TTY 进行着色。

自定义关卡标题,文本,颜色和样式。

导入和 :golog``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))

嘟嘟。格式化程序界面如下所示:

// 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")

请求日志记录

我们在上面看到的应用程序记录器用于记录与应用程序相关的信息和错误。另一方面,我们在下面看到的访问记录器用于记录传入的HTTP请求和响应。

package mainimport ("os""github.com/kataras/iris/v12""github.com/kataras/iris/v12/middleware/accesslog"
)// Read the example and its comments carefully.
func makeAccessLog() *accesslog.AccessLog {// Initialize a new access log middleware.ac := accesslog.File("./access.log")// Remove this line to disable logging to console:ac.AddOutput(os.Stdout)// The default configuration:ac.Delim = '|'ac.TimeFormat = "2006-01-02 15:04:05"ac.Async = falseac.IP = trueac.BytesReceivedBody = trueac.BytesSentBody = trueac.BytesReceived = falseac.BytesSent = falseac.BodyMinify = trueac.RequestBody = trueac.ResponseBody = falseac.KeepMultiLineError = trueac.PanicLog = accesslog.LogHandler// Default line format if formatter is missing:// Time|Latency|Code|Method|Path|IP|Path Params Query Fields|Bytes Received|Bytes Sent|Request|Response|//// Set Custom Formatter:ac.SetFormatter(&accesslog.JSON{Indent:    "  ",HumanTime: true,})// ac.SetFormatter(&accesslog.CSV{})// ac.SetFormatter(&accesslog.Template{Text: "{{.Code}}"})return ac
}func main() {ac := makeAccessLog()defer ac.Close() // Close the underline file.app := iris.New()// Register the middleware (UseRouter to catch http errors too).app.UseRouter(ac.Handler)app.Get("/", indexHandler)app.Listen(":8080")
}func indexHandler(ctx iris.Context) {ctx.WriteString("OK")
}

示例:_examples/logging/request-logger。

(adsbygoogle = window.adsbygoogle || []).push({});

模型绑定和验证

若要将请求正文绑定到类型中,请使用模型绑定。我们目前支持绑定、、、、和标准表单值(foo=bar&boo=baz)。JSON``JSONProtobuf``Protobuf``MsgPack``XML``YAML

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 会尝试根据内容类型标头推断绑定程序。如果您确定要绑定的内容,则可以使用特定方法,例如 或和等ReadBody``ReadXXX``ReadJSON``ReadProtobuf

ReadBody(ptr interface{}) error

明智的是,Iris没有内置的数据验证功能。但是,它确实允许您附加一个验证器,该验证器将自动调用诸如 , …在这个例子中,我们将学习如何使用 go-playground/validator/v10 进行请求正文验证。ReadJSON``ReadXML

请注意,您需要在要绑定的所有字段上设置相应的绑定标记。例如,从 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``ReadForm

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")
}

绑定任何

根据客户端发送数据的内容类型将请求正文绑定到“ptr”,例如 JSON、XML、YAML、MessagePack、Protobuf、Form 和 URL Query。

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"

绑定网址路径参数

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

(adsbygoogle = window.adsbygoogle || []).push({});

绑定标头

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) {if err := ctx.View("form.html"); err!=nil {ctx.HTML("<h3>%s</h3>", err.Error())return}
}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 和 MsgPack 渲染

详细示例可在此处找到。

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

普罗托布夫

Iris 支持原生 protobuf with 和 protobuf to JSON 编码和解码。Protobuf

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
}

详细了解文件服务器。

(adsbygoogle = window.adsbygoogle || []).push({});

从上下文中提供数据

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.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) 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,即 使用嵌入资源、布局和特定于参与方的布局、模板功能、部分渲染等进行解析。

#名字解析 器
1.HTML网页/模板
2卡塔拉斯/方块
3姜戈弗洛施/蓬戈2
4帕格小丑/翡翠
5车把艾默里克/雷蒙德
6琥珀eknkc/琥珀色
7噴氣機云套件/喷气机
8高手约西/艾斯

示例列表。

基准列表。

视图引擎可以按缔约方注册。要注册视图引擎,请使用如下所示的方法。Application/Party.RegisterView(ViewEngine)

从扩展名为“.html”的“./views”文件夹中加载所有模板,并使用标准包解析它们。html/template

// [app := iris.New...]
tmpl := iris.HTML("./views", ".html")
app.RegisterView(tmpl)

若要呈现或执行视图,请使用主路由处理程序中的方法。Context.View

if err := ctx.View("hi.html"); err!=nil {ctx.HTML("<h3>%s</h3>", err.Error())return
}

要通过中间件或主处理程序在视图中将 Go 值与键值模式绑定,请使用 go 值之前的方法。Context.ViewData``Context.View

绑定:与 .{{.message}}``"Hello world!"

ctx.ViewData("message", "Hello world!")

根绑定:

if err := ctx.View("user-page.html", User{}); err!=nil {ctx.HTML("<h3>%s</h3>", err.Error())return
}// 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 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.htmlif err := ctx.View("hi.html"); err!=nil {ctx.HTML("<h3>%s</h3>", err.Error())return}})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>

在 http://localhost:8080 处打开浏览器选项卡。

渲染的结果将如下所示:

<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://go.dev/dl", iris.StatusMovedPermanently)
})

从开机自检发出 HTTP 重定向。

app.Post("/", func(ctx iris.Context) {ctx.Redirect("/login", iris.StatusFound)
})

从处理程序发出本地路由器重定向,使用或类似如下。Application.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")
}

该文件如下所示:"redirects.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

完整的代码可以在重写中间件示例中找到。

自定义中间件

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")
}

(adsbygoogle = window.adsbygoogle || []).push({});

使用基本身份验证

HTTP 基本身份验证是对 Web 资源实施访问控制的最简单技术,因为它不需要 Cookie、会话标识符或登录页面;相反,HTTP 基本身份验证使用 HTTP 标头中的标准字段。

基本身份验证中间件包含在 Iris 框架中,因此无需单独安装。

1. 导入中间件

import "github.com/kataras/iris/v12/middleware/basicauth"

2. 使用其结构配置中间件:Options

opts := basicauth.Options{Allow: basicauth.AllowUsers(map[string]string{"username": "password",}),Realm:        "Authorization Required",ErrorHandler: basicauth.DefaultErrorHandler,// [...more options]
}

3. 初始化中间件:

auth := basicauth.New(opts)

3.1 以上步骤与函数相同:Default

auth := basicauth.Default(map[string]string{"username": "password",
})

3.2 使用自定义用户切片:

// The struct value MUST contain a Username and Passwords fields
// or GetUsername() string and GetPassword() string methods.
type User struct {Username stringPassword string
}// [...]
auth := basicauth.Default([]User{...})

3.3 从文件加载用户 或者,密码使用 pkg.go.dev/golang.org/x/crypto/bcrypt 包进行加密:

auth := basicauth.Load("users.yml", basicauth.BCRYPT)

3.3.1 使用(推荐)也可以实现相同的目的:Options

opts := basicauth.Options{Allow: basicauth.AllowUsersFile("users.yml", basicauth.BCRYPT),Realm: basicauth.DefaultRealm,// [...more options]
}auth := basicauth.New(opts)

其中可能看起来像这样:users.yml

- username: kataraspassword: $2a$10$Irg8k8HWkDlvL0YDBKLCYee6j6zzIFTplJcvZYKA.B8/clHPZn2Ey# encrypted of kataras_passrole: admin
- username: makispassword: $2a$10$3GXzp3J5GhHThGisbpvpZuftbmzPivDMo94XPnkTnDe7254x7sJ3O# encrypted of makis_passrole: member

4. 注册中间件:

// Register to all matched routes
// under a Party and its children.
app.Use(auth)// OR/and register to all http error routes.
app.UseError(auth)// OR register under a path prefix of a specific Party,
// including all http errors of this path prefix.
app.UseRouter(auth)// OR register to a specific Route before its main handler.
app.Post("/protected", auth, routeHandler)

5. 检索用户名和密码:

func routeHandler(ctx iris.Context) {username, password, _ := ctx.Request().BasicAuth()// [...]
}

5.1 检索 User 值(当您在以下位置注册自定义用户结构的一部分时很有用):Options.AllowUsers

func routeHandler(ctx iris.Context) {user := ctx.User().(*iris.SimpleUser)// user.Username// user.Password
}

在 _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)
}

请注意,将其用作 .Build``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()
}

但是,您很少需要具有 IRIS 的外部实例。您可以使用任何 tcp 侦听器、http 服务器或通过方法自定义函数进行侦听。http.Server``Application.Run

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

套接字分片

此选项允许在多 CPU 服务器上线性扩展服务器性能。有关详细信息,请参阅 https://www.nginx.com/blog/socket-sharding-nginx-release-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 示例(您也可以绑定自动证书管理器):

app.Run(iris.TLS(":443", "", "", func(su *iris.Supervisor) {su.Server.TLSConfig = &tls.Config{/* your custom fields */},}),
)

所有方法(如:Addr,TLS,AutoTLS,Server,Listener和e.t.c)都接受可变参数,以在构建状态上配置http服务器实例。iris.Runner``func(*iris.Supervisor)

使用鸢尾花运行多个服务

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

通过包管理多个鸢尾花实例。在此处内容。apps

(adsbygoogle = window.adsbygoogle || []).push({});

正常关机或重启

有几种方法可用于执行正常关机或重新启动。您可以使用专门为此构建的第三方包,也可以使用该方法。可以在此处找到示例。app.Shutdown(context.Context)

使用 CTRL/CMD+C 在 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/视图中查看完整示例。

尝试将身体绑定到不同的结构中

绑定请求正文的常规方法消耗,它们 不能多次调用,除非将配置器传递给 。ctx.Request().Body``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 := os.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()
}

(adsbygoogle = window.adsbygoogle || []).push({});

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、编码和解码、会话(和会话缩放)、闪存消息等可以在 _examples/cookie 和 _examples/会话目录中找到

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("") */)

更多:

  • 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()
}

JSON 网络令牌

JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。此信息可以验证和信任,因为它是经过数字签名的。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。

何时应使用 JSON Web 令牌?

以下是 JSON Web 令牌有用的一些方案:

授权:这是使用 JWT 的最常见方案。用户登录后,每个后续请求都将包含 JWT,允许用户访问使用该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够跨不同域轻松使用。

信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。由于 JWT 可以签名(例如,使用公钥/私钥对),因此您可以确定发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改。

关于智威汤逊的信息: https://jwt.io/introduction/

将 JWT 与虹膜配合使用

Iris JWT 中间件在设计时考虑了安全性、性能和简单性,它可以保护您的令牌免受您可能在其他库中发现的关键漏洞的影响。它基于kataras/jwt包。

package mainimport ("time""github.com/kataras/iris/v12""github.com/kataras/iris/v12/middleware/jwt"
)var (sigKey = []byte("signature_hmac_secret_shared_key")encKey = []byte("GCM_AES_256_secret_shared_key_32")
)type fooClaims struct {Foo string `json:"foo"`
}func main() {app := iris.New()signer := jwt.NewSigner(jwt.HS256, sigKey, 10*time.Minute)// Enable payload encryption with:// signer.WithEncryption(encKey, nil)app.Get("/", generateToken(signer))verifier := jwt.NewVerifier(jwt.HS256, sigKey)// Enable server-side token block feature (even before its expiration time):verifier.WithDefaultBlocklist()// Enable payload decryption with:// verifier.WithDecryption(encKey, nil)verifyMiddleware := verifier.Verify(func() interface{} {return new(fooClaims)})protectedAPI := app.Party("/protected")// Register the verify middleware to allow access only to authorized clients.protectedAPI.Use(verifyMiddleware)// ^ or UseRouter(verifyMiddleware) to disallow unauthorized http error handlers too.protectedAPI.Get("/", protected)// Invalidate the token through server-side, even if it's not expired yet.protectedAPI.Get("/logout", logout)// http://localhost:8080// http://localhost:8080/protected?token=$token (or Authorization: Bearer $token)// http://localhost:8080/protected/logout?token=$token// http://localhost:8080/protected?token=$token (401)app.Listen(":8080")
}func generateToken(signer *jwt.Signer) iris.Handler {return func(ctx iris.Context) {claims := fooClaims{Foo: "bar"}token, err := signer.Sign(claims)if err != nil {ctx.StopWithStatus(iris.StatusInternalServerError)return}ctx.Write(token)}
}func protected(ctx iris.Context) {// Get the verified and decoded claims.claims := jwt.Get(ctx).(*fooClaims)// Optionally, get token information if you want to work with them.// Just an example on how you can retrieve all the standard claims (set by signer's max age, "exp").standardClaims := jwt.GetVerifiedToken(ctx).StandardClaimsexpiresAtString := standardClaims.ExpiresAt().Format(ctx.Application().ConfigurationReadOnly().GetTimeFormat())timeLeft := standardClaims.Timeleft()ctx.Writef("foo=%s\nexpires at: %s\ntime left: %s\n", claims.Foo, expiresAtString, timeLeft)
}func logout(ctx iris.Context) {err := ctx.Logout()if err != nil {ctx.WriteString(err.Error())} else {ctx.Writef("token invalidated, a new token is required to access the protected API")}
}

有关刷新令牌、阻止列表等的信息,请访问:_examples/auth/jwt。

测试

Iris为httpexpect提供了令人难以置信的支持,httpexpect是Web应用程序的测试框架。子包为 Iris + httpexpect 提供了帮助程序。iris/httptest

如果你更喜欢Go的标准net/http/httptest包,你仍然可以使用它。Iris与每个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()opts := basicauth.Options{Allow: basicauth.AllowUsers(map[string]string{"myusername": "mypassword"}),}authentication := basicauth.New(opts) // or just: basicauth.Default(map...)app.Get("/", func(ctx iris.Context) { ctx.Redirect("/admin") })// to partyneedAuth := 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 it will be always true because the middleware// makes sure for that, otherwise this handler will not be executed.// OR:user := ctx.User().(*iris.SimpleUser)ctx.Writef("%s %s:%s", ctx.Path(), user.Username, user.Password)// ctx.Writef("%s %s:%s", ctx.Path(), username, password)
}func main() {app := newApp()app.Listen(":8080")
}

**2.**现在,创建一个文件并复制粘贴以下内容。main_test.go

package mainimport ("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. 打开命令行并执行:

$ go test -v

测试饼干

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 的文档。

地方化

介绍

本地化功能提供了一种检索各种语言字符串的便捷方法,使您可以在应用程序中轻松支持多种语言。语言字符串存储在目录中的文件中。在此目录中,应用程序支持的每种语言都应该有一个子目录:./locales

│   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")

加载嵌入式区域设置

您可能希望在应用程序可执行文件中使用新的嵌入指令嵌入区域设置。

  1. 导入嵌入包;如果您不使用此包中的任何导出标识符,则可以使用 _ “embed” 进行空白导入。
import ("embed"
)
  1. 嵌入指令接受相对于包含 Go 源文件的目录的路径。我们可以嵌入多个文件,甚至是带有通配符的文件夹。这使用嵌入的变量。FS 类型,它实现一个简单的虚拟文件系统。
//go:embed embedded/locales/*
var embeddedFS embed.FS
  1. 我们应该使用方法而不是方法。Load``LoadFS
err := app.I18n.LoadFS(embeddedFS, "./embedded/locales/*/*.ini", "en-US", "el-GR")
// OR to load all languages by filename:
// app.I18n.LoadFS(embeddedFS, "./embedded/locales/*/*.ini")
// Then set the default language using:
// app.I18n.SetDefault("en-US")

定义翻译

语言环境文件可以以 YAML(推荐)、JSON、TOML 或 INI 形式编写。

每个文件都应包含密钥。键也可以有子键(我们称之为“部分”)。

每个键的值应具有形式或包含其翻译文本(或模板)或/及其复数键值。string``map

Iris i18n模块支持开箱即用的复数,见下文。

FMT 风格

hi: "Hi %s!"
ctx.Tr("Hi", "John")
// Outputs: Hi John!

模板

hi: "Hi {{.Name}}!"
ctx.Tr("Hi", iris.Map{"Name": "John"})
// Outputs: Hi John!

多元化

Iris i18n 支持复数变量。要定义每个区域设置变量,您必须 定义键的新部分。Vars

变量可接受的键为:

  • one
  • "=x"其中 x 是一个数字
  • "<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, 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.

部分

如果密钥不是保留密钥(例如一、二…),则它充当子部分。这些部分由点字符 () 分隔。.

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 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) {err := ctx.View("index.html", iris.Map{"tr": ctx.Tr,})if err!=nil {ctx.HTML("<h3>%s</h3>", err.Error())return}
}

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) {err := ctx.View("index.html", iris.Map{"tr": ctx.Tr, // word, arguments... {call .tr "hi" "iris"}}})if err != nil {ctx.HTML("<h3>%s</h3>", err.Error())return}// 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"))
}

网站地图

站点地图翻译会自动设置为每个路由,如果为 true,则按路径前缀设置,如果为 true,则按子域自动设置,如果不为空,则按 URL 查询参数自动设置。app.I18n.PathRedirect``app.I18n.Subdomain``app.I18n.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>

这就是关于虹膜的所有基础知识。本文档涵盖了足够的初学者。想成为专家和认证鸢尾花开发人员,了解 MVC、i18n、依赖注入、gRPC、lambda 函数、websockets、最佳实践等吗?立即申请虹膜电子书,参与虹膜的开发!


http://www.ppmy.cn/news/368095.html

相关文章

「Excel表格解密神器」

“具体步骤如下&#xff1a;第一步百度搜索“密码帝官网”&#xff0c;第二步点击“立即开始”&#xff0c;在用户中心上传excel文件即可。excel不能编辑&#xff1f;不用下载软件&#xff0c;密码帝帮你一步搞定&#xff01;找回、去除excel表格密码&#xff0c;解除限制最简单…

Java后端入职第六天,Nginx搭建负载均衡+反向代理

一:需求背景 Nginx相信大家应该不陌生吧!是一个高性能web服务器,可以做负载均衡或者是反向代理,比如,把静态页面直接部署到到html,实现动静分离,一个服务器部署多个服务,缓解服务压力,等等,都可以利用Nginx实现。 1.负载均衡 这个理解比较简单,其实就是压力分摊,…

惠普电脑无故出现第三方屏保

首先先看条件&#xff0c;如果你电脑是惠普的&#xff0c;而且都是 2022 2022 2022 年四五月份或者往后发现突然出现莫名其妙的屏保那请往下看 1 1 1.直接打开的 win 的设置 2 2 2.进到应用 3 3 3.直接搜索 huiping&#xff0c;然后把这玩意儿给卸了&#xff0c;如果过几天…

将爱心代码设为电脑屏保,俘获少女芳心,还能假装黑客大佬,在酷炫的界面中保护隐私

将爱心代码设为电脑屏保&#xff0c;俘获少女芳心&#xff0c;还能假装黑客大佬&#xff0c;在酷炫的界面中保护隐私 本文介绍 Hacker Screen Saver 一款开源 Windows 屏保的使用。Hacker Screen Saver 是一款 .NET 设计的屏幕保护程序&#xff0c;可以显示 HTML 页面&#xff…

计算机显示屏怎么加密,怎样给电脑设置密码的屏幕保护

电脑屏保是在一定时间内离开&#xff0c;电脑自动进入屏保状态&#xff0c;有时候电脑可能还没有设置屏保&#xff0c;就没有这个功能实现&#xff0c;那么如何给电脑设置屏保呢?接下来是小编为大家收集的给电脑设置密码的屏幕保护方法&#xff0c;欢迎大家阅读。 给电脑设置密…

计算机屏幕保护程序怎么设置,电脑屏保怎么设置锁屏

不少朋友都喜欢给自己的电脑设置锁屏的&#xff0c;那么&#xff0c;电脑屏保怎么设置锁屏 电脑屏保设置锁屏方法一&#xff1a; 点击开始面板&#xff0c;在弹出来的窗口中选择“控制面板”。 打开控制面板&#xff0c;再点击控制面板上的“系统和安全”。 点击后&#xff0c;…

IPV6的原理和配置

第19章:IPV6 IPv6(Internet Protocol Version 6)是网络层协议的第二代标准协议,也被称为IPng(IP Next Generation)。它是Internet工程任务组IETF(Internet Engineering Task Force)设计的一套规范,是IPv4(Internet Protocol Version 4)的升级版本。 学完本章内容以…

【C++】string类的深入介绍

【C】string类的深入介绍&#xff08;1&#xff09; 目录 【C】string类的深入介绍&#xff08;1&#xff09;标准库中的string类string类&#xff08;了解即可&#xff09;string类的常用接口说明&#xff08;最常用的&#xff09;详细介绍string::operator[] 迭代器string中的…