diff --git a/README_ZH.md b/README_ZH.md
deleted file mode 100644
index 8c9f8ab..0000000
--- a/README_ZH.md
+++ /dev/null
@@ -1,1993 +0,0 @@
-# Gin Web 框架中文文档
-
-
-
-[![Build Status](https://travis-ci.org/gin-gonic/gin.svg)](https://travis-ci.org/gin-gonic/gin)
-[![codecov](https://codecov.io/gh/gin-gonic/gin/branch/master/graph/badge.svg)](https://codecov.io/gh/gin-gonic/gin)
-[![Go Report Card](https://goreportcard.com/badge/github.com/gin-gonic/gin)](https://goreportcard.com/report/github.com/gin-gonic/gin)
-[![GoDoc](https://godoc.org/github.com/gin-gonic/gin?status.svg)](https://godoc.org/github.com/gin-gonic/gin)
-[![Join the chat at https://gitter.im/gin-gonic/gin](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/gin-gonic/gin?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-[![Sourcegraph](https://sourcegraph.com/github.com/gin-gonic/gin/-/badge.svg)](https://sourcegraph.com/github.com/gin-gonic/gin?badge)
-[![Open Source Helpers](https://www.codetriage.com/gin-gonic/gin/badges/users.svg)](https://www.codetriage.com/gin-gonic/gin)
-[![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://github.com/gin-gonic/gin/releases)
-
-Gin 是一个 Go (Golang) 语言框架。 它是一个拥有更好性能的 martini-like API 框架, 比 [httprouter](https://github.com/julienschmidt/httprouter) 的速度快了40倍. 如果你是性能和高效的追求者, 那么你会爱上 Gin.
-
-![Gin console logger](https://gin-gonic.github.io/gin/other/console.png)
-
-## Contents
-
-- [安装](#安装)
-- [前提条件](#前提条件)
-- [快速开始](#快速开始)
-- [性能测试](#性能测试)
-- [Gin v1 稳定版](#gin-v1-稳定版)
-- [使用 jsoniter ](#使用-jsoniter)
-- [API 示例](#api-示例)
- - [GET,POST,PUT,PATCH,DELETE,OPTIONS 使用](#get-post-put-patch-delete-options-使用)
- - [获取路由参数](#获取路由参数)
- - [获取url查询参数](#获取url查询参数)
- - [Multipart Urlencoded 表单](#multipart-urlencoded-表单)
- - [获取post表单数据(url带查询参数)](#获取post表单数据url带查询参数)
- - [映射参数 表单参数](#映射参数-表单参数)
- - [上传文件](#上传文件)
- - [路由组](#路由组)
- - [默认初始化 Gin](#默认初始化-gin)
- - [使用中间件](#使用中间件)
- - [如何记录日志](#如何记录日志)
- - [模型绑定和验证](#模型绑定和验证)
- - [自定义验证器](#自定义验证器)
- - [只绑定url查询参数](#只绑定url查询参数)
- - [url查询参数或表单数据绑定到结构体](#url查询参数或表单数据绑定到结构体)
- - [url路径参数绑定](#url路径参数绑定)
- - [绑定 HTML 复选框](#绑定-html-复选框)
- - [Multipart Urlencoded 绑定](#multipart-urlencoded-绑定)
- - [XML JSON YAML ProtoBuf 渲染](#xml-json-yaml-protobuf-渲染)
- - [SecureJSON](#SecureJSON)
- - [静态文件服务](#静态文件服务)
- - [从reader 读取数据](#从-reader-读取数据)
- - [HTML 渲染](#html-渲染)
- - [多模板](#多模板)
- - [重定向](#重定向)
- - [自定义中间件](#自定义中间件)
- - [使用 BasicAuth() 中间件](#使用-basicauth()-中间件)
- - [在中间件中使用Goroutines](#在中间件中使用Goroutines)
- - [自定义 HTTP 配置](#自定义-http-配置)
- - [Let's Encrypt 支持](#lets-encrypt-支持)
- - [使用 Gin 运行多个服务](使用-gin-运行多个服务)
- - [优雅重启或停止](#优雅重启或停止)
- - [静态资源嵌入](#静态资源嵌入)
- - [表单数据绑定到自定义结构体](#表单数据绑定到自定义结构体)
- - [将request body绑定到不同的结构体中](#将request-body绑定到不同的结构体中)
- - [http2 server 推送](#http2-server-推送)
- - [定义路由日志的格式](#定义路由日志的格式)
- - [如何使用Cookie](#如何使用Cookie)
-- [测试](#测试)
-- [用户](#用户)
-
-## 安装
-
-要安装 Gin 软件包,需要先安装 Go 并设置 Go 工作区。
-
-1. 下载并安装 gin:
-
-```sh
-$ go get -u github.com/gin-gonic/gin
-```
-
-2. 将 gin 引入到代码中:
-
-```go
-import "github.com/gin-gonic/gin"
-```
-
-3. (可选)如果使用诸如`http.StatusOK`之类的常量,则需要引入 `net/http` 包。
-
-```go
-import "net/http"
-```
-
-### 使用 [Govendor](https://github.com/kardianos/govendor) 工具创建项目
-
-1. `go get` govendor
-
-```sh
-$ go get github.com/kardianos/govendor
-```
-2.创建项目并且 `cd` 到项目目录中
-
-```sh
-$ mkdir -p $GOPATH/src/github.com/myusername/project && cd "$_"
-```
-
-3. 使用 govendor 初始化项目,并且引入gin
-
-```sh
-$ govendor init
-$ govendor fetch github.com/gin-gonic/gin@v1.3
-```
-
-4. 复制启动文件模板到项目目录中
-
-```sh
-$ curl https://raw.githubusercontent.com/gin-gonic/gin/master/examples/basic/main.go > main.go
-```
-
-5.启动项目
-
-```sh
-$ go run main.go
-```
-
-## 前提条件
-
-新版本的 Gin 需要 Go 1.6 或者更高版本,并且很快就会要求升级到 Go 1.7.
-
-## 快速开始
-
-```sh
-# assume the following codes in example.go file
-$ cat example.go
-```
-
-```go
-package main
-
-import "github.com/gin-gonic/gin"
-
-func main() {
- r := gin.Default()
- r.GET("/ping", func(c *gin.Context) {
- c.JSON(200, gin.H{
- "message": "pong",
- })
- })
- r.Run() // listen and serve on 0.0.0.0:8080
-}
-```
-
-```
-# run example.go and visit 0.0.0.0:8080/ping on browser
-$ go run example.go
-```
-
-## 性能测试
-
-Gin 使用自定义版本的 [HttpRouter](https://github.com/julienschmidt/httprouter)
-
-[所有性能测试](/BENCHMARKS.md)
-
-Benchmark name | (1) | (2) | (3) | (4)
---------------------------------------------|-----------:|------------:|-----------:|---------:
-**BenchmarkGin_GithubAll** | **30000** | **48375** | **0** | **0**
-BenchmarkAce_GithubAll | 10000 | 134059 | 13792 | 167
-BenchmarkBear_GithubAll | 5000 | 534445 | 86448 | 943
-BenchmarkBeego_GithubAll | 3000 | 592444 | 74705 | 812
-BenchmarkBone_GithubAll | 200 | 6957308 | 698784 | 8453
-BenchmarkDenco_GithubAll | 10000 | 158819 | 20224 | 167
-BenchmarkEcho_GithubAll | 10000 | 154700 | 6496 | 203
-BenchmarkGocraftWeb_GithubAll | 3000 | 570806 | 131656 | 1686
-BenchmarkGoji_GithubAll | 2000 | 818034 | 56112 | 334
-BenchmarkGojiv2_GithubAll | 2000 | 1213973 | 274768 | 3712
-BenchmarkGoJsonRest_GithubAll | 2000 | 785796 | 134371 | 2737
-BenchmarkGoRestful_GithubAll | 300 | 5238188 | 689672 | 4519
-BenchmarkGorillaMux_GithubAll | 100 | 10257726 | 211840 | 2272
-BenchmarkHttpRouter_GithubAll | 20000 | 105414 | 13792 | 167
-BenchmarkHttpTreeMux_GithubAll | 10000 | 319934 | 65856 | 671
-BenchmarkKocha_GithubAll | 10000 | 209442 | 23304 | 843
-BenchmarkLARS_GithubAll | 20000 | 62565 | 0 | 0
-BenchmarkMacaron_GithubAll | 2000 | 1161270 | 204194 | 2000
-BenchmarkMartini_GithubAll | 200 | 9991713 | 226549 | 2325
-BenchmarkPat_GithubAll | 200 | 5590793 | 1499568 | 27435
-BenchmarkPossum_GithubAll | 10000 | 319768 | 84448 | 609
-BenchmarkR2router_GithubAll | 10000 | 305134 | 77328 | 979
-BenchmarkRivet_GithubAll | 10000 | 132134 | 16272 | 167
-BenchmarkTango_GithubAll | 3000 | 552754 | 63826 | 1618
-BenchmarkTigerTonic_GithubAll | 1000 | 1439483 | 239104 | 5374
-BenchmarkTraffic_GithubAll | 100 | 11383067 | 2659329 | 21848
-BenchmarkVulcan_GithubAll | 5000 | 394253 | 19894 | 609
-
-- (1): 在不断的时间内实现总重复,更高意味着更自信的结果
-- (2): 单次重复持续时间(ns / op),越低越好
-- (3): 堆内存(B / op),越低越好
-- (4): 每次重复的平均分配(allocs / op)越低越好
-
-## Gin v1 稳定版
-
-- [x] 零分配路由。
-- [x] 仍然是最快的http路由器和框架。
-- [x] 完整的单元测试支持
-- [x] 对战测试
-- [x] API冻结,使用新版本不需要修改原有代码。
-
-## 使用 [jsoniter](https://github.com/json-iterator/go)
-
-Gin默认使用`encoding/json`解析json数据,但您可以通过`go build -tags=`更改为使用[jsoniter](https://github.com/json-iterator/go)。
-
-```sh
-$ go build -tags=jsoniter .
-```
-
-## API 示例
-
-### 使用GET, POST, PUT, PATCH, DELETE , OPTIONS
-
-```go
-func main() {
- // Disable Console Color
- // gin.DisableConsoleColor()
-
- // Creates a gin router with default middleware:
- // logger and recovery (crash-free) middleware
- router := gin.Default()
-
- router.GET("/someGet", getting)
- router.POST("/somePost", posting)
- router.PUT("/somePut", putting)
- router.DELETE("/someDelete", deleting)
- router.PATCH("/somePatch", patching)
- router.HEAD("/someHead", head)
- router.OPTIONS("/someOptions", options)
-
- // By default it serves on :8080 unless a
- // PORT environment variable was defined.
- router.Run()
- // router.Run(":3000") for a hard coded port
-}
-```
-
-### 获取路由参数
-
-```go
-func main() {
- router := gin.Default()
-
- // This handler will match /user/john but will not match /user/ or /user
- router.GET("/user/:name", func(c *gin.Context) {
- name := c.Param("name")
- c.String(http.StatusOK, "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/
- router.GET("/user/:name/*action", func(c *gin.Context) {
- name := c.Param("name")
- action := c.Param("action")
- message := name + " is " + action
- c.String(http.StatusOK, message)
- })
-
- router.Run(":8080")
-}
-```
-
-### 获取url查询参数
-
-```go
-func main() {
- router := gin.Default()
-
- // Query string parameters are parsed using the existing underlying request object.
- // The request responds to a url matching: /welcome?firstname=Jane&lastname=Doe
- router.GET("/welcome", func(c *gin.Context) {
- firstname := c.DefaultQuery("firstname", "Guest")
- lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
-
- c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
- })
- router.Run(":8080")
-}
-```
-
-### Multipart Urlencoded 表单
-
-```go
-func main() {
- router := gin.Default()
-
- router.POST("/form_post", func(c *gin.Context) {
- message := c.PostForm("message")
- nick := c.DefaultPostForm("nick", "anonymous")
-
- c.JSON(200, gin.H{
- "status": "posted",
- "message": message,
- "nick": nick,
- })
- })
- router.Run(":8080")
-}
-```
-
-### 获取post表单数据(url带查询参数)
-
-```
-POST /post?id=1234&page=1 HTTP/1.1
-Content-Type: application/x-www-form-urlencoded
-
-name=manu&message=this_is_great
-```
-
-```go
-func main() {
- router := gin.Default()
-
- router.POST("/post", func(c *gin.Context) {
-
- id := c.Query("id")
- page := c.DefaultQuery("page", "0")
- name := c.PostForm("name")
- message := c.PostForm("message")
-
- fmt.Printf("id: %s; page: %s; name: %s; message: %s", id, page, name, message)
- })
- router.Run(":8080")
-}
-```
-
-```
-id: 1234; page: 1; name: manu; message: this_is_great
-```
-
-### 映射参数 表单参数
-
-```
-POST /post?ids[a]=1234&ids[b]=hello HTTP/1.1
-Content-Type: application/x-www-form-urlencoded
-
-names[first]=thinkerou&names[second]=tianou
-```
-
-```go
-func main() {
- router := gin.Default()
-
- router.POST("/post", func(c *gin.Context) {
-
- ids := c.QueryMap("ids")
- names := c.PostFormMap("names")
-
- fmt.Printf("ids: %v; names: %v", ids, names)
- })
- router.Run(":8080")
-}
-```
-
-```
-ids: map[b:hello a:1234], names: map[second:tianou first:thinkerou]
-```
-
-### 上传文件
-
-#### 单个文件上传
-
-参考问题[#774](https://github.com/gin-gonic/gin/issues/774)和详细[示例代码](examples/upload-file/single)。
-
-```go
-func main() {
- router := gin.Default()
- // Set a lower memory limit for multipart forms (default is 32 MiB)
- // router.MaxMultipartMemory = 8 << 20 // 8 MiB
- router.POST("/upload", func(c *gin.Context) {
- // single file
- file, _ := c.FormFile("file")
- log.Println(file.Filename)
-
- // Upload the file to specific dst.
- // c.SaveUploadedFile(file, dst)
-
- c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
- })
- router.Run(":8080")
-}
-```
-
-`curl`示例:
-
-```bash
-curl -X POST http://localhost:8080/upload \
- -F "file=@/Users/appleboy/test.zip" \
- -H "Content-Type: multipart/form-data"
-```
-
-#### 多文件上传
-
-查看详细信息[示例代码](examples/upload-file/multiple)。
-
-```go
-func main() {
- router := gin.Default()
- // Set a lower memory limit for multipart forms (default is 32 MiB)
- // router.MaxMultipartMemory = 8 << 20 // 8 MiB
- router.POST("/upload", func(c *gin.Context) {
- // Multipart form
- form, _ := c.MultipartForm()
- files := form.File["upload[]"]
-
- for _, file := range files {
- log.Println(file.Filename)
-
- // Upload the file to specific dst.
- // c.SaveUploadedFile(file, dst)
- }
- c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
- })
- router.Run(":8080")
-}
-```
-
-`curl`示例:
-
-```bash
-curl -X POST http://localhost:8080/upload \
- -F "upload[]=@/Users/appleboy/test1.zip" \
- -F "upload[]=@/Users/appleboy/test2.zip" \
- -H "Content-Type: multipart/form-data"
-```
-
-### 路由组
-
-```go
-func main() {
- router := gin.Default()
-
- // Simple group: v1
- v1 := router.Group("/v1")
- {
- v1.POST("/login", loginEndpoint)
- v1.POST("/submit", submitEndpoint)
- v1.POST("/read", readEndpoint)
- }
-
- // Simple group: v2
- v2 := router.Group("/v2")
- {
- v2.POST("/login", loginEndpoint)
- v2.POST("/submit", submitEndpoint)
- v2.POST("/read", readEndpoint)
- }
-
- router.Run(":8080")
-}
-```
-
-### 默认初始化 Gin
-
-用
-
-```go
-r := gin.New()
-```
-
-代替
-
-```go
-// Default With the Logger and Recovery middleware already attached
-r := gin.Default()
-```
-
-
-### 使用中间件
-```go
-func main() {
- // Creates a router without any middleware by default
- r := gin.New()
-
- // Global middleware
- // Logger middleware will write the logs to gin.DefaultWriter even if you set with GIN_MODE=release.
- // By default gin.DefaultWriter = os.Stdout
- r.Use(gin.Logger())
-
- // Recovery middleware recovers from any panics and writes a 500 if there was one.
- r.Use(gin.Recovery())
-
- // Per route middleware, you can add as many as you desire.
- r.GET("/benchmark", MyBenchLogger(), benchEndpoint)
-
- // Authorization group
- // authorized := r.Group("/", AuthRequired())
- // exactly the same as:
- authorized := r.Group("/")
- // 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.Group("testing")
- testing.GET("/analytics", analyticsEndpoint)
- }
-
- // Listen and serve on 0.0.0.0:8080
- r.Run(":8080")
-}
-```
-
-### 如何记录日志
-```go
-func main() {
- // Disable Console Color, you don't need console color when writing the logs to file.
- gin.DisableConsoleColor()
-
- // Logging to a file.
- f, _ := os.Create("gin.log")
- gin.DefaultWriter = io.MultiWriter(f)
-
- // Use the following code if you need to write the logs to file and console at the same time.
- // gin.DefaultWriter = io.MultiWriter(f, os.Stdout)
-
- router := gin.Default()
- router.GET("/ping", func(c *gin.Context) {
- c.String(200, "pong")
- })
-
- router.Run(":8080")
-}
-```
-
-### 模型绑定和验证
-
-要将请求主体绑定到结构体中,请使用模型绑定。Gin目前支持JSON、XML、YAML和标准表单值的绑定(foo=bar&boo=baz)。
-
-Gin使用[go-playground/validator.v8](https://github.com/go-playground/validator)进行验证。[完整文档](http://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Baked_In_Validators_and_Tags)。
-
-使用时,需要在要绑定的所有字段上,设置相应的tag。例如,使用JSON绑定时,字段tag设置为`json:"fieldname"`。
-
-Gin提供了两类绑定方法:
-- **MustBind** -
- - **方法** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`
- - **说明** - 这些方法属于`MustBindWith`的具体调用。如果发生绑定错误,则请求终止,并触发`c.AbortWithError(400,err).SetType(ErrorTypeBind)`。响应状态码被设置为400,`Content-Type`被设置为`text/plain;charset= UTF-8`。如果您在此之后尝试设置响应状态码,Gin会输出日志“ `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`。如果您希望更好地把控绑定,请考虑使用`ShouldBind`等效方法。
-- **ShouldBind** -
- - **方法** - `ShouldBind`,`ShouldBindJSON`,`ShouldBindXML`,`ShouldBindQuery`
- - **说明** - 这些方法属于`ShouldBindWith`的具体调用。如果发生绑定错误,Gin会返回错误。由您处理错误以及请求。
-
-使用Bind方法时,Gin会根据Content-Type尝试推断如何绑定,如果您明确知道,您可以使用 `MustBindWith` 或 `ShouldBindWith`。
-
-指定必须绑定的字段,在该字段Tag上加上`binding:"required"` ,如果绑定时是空值,Gin会报错。
-
-```go
-// Binding from JSON
-type Login struct {
- User string `form:"user" json:"user" xml:"user" binding:"required"`
- Password string `form:"password" json:"password" xml:"password" binding:"required"`
-}
-
-func main() {
- router := gin.Default()
-
- // Example for binding JSON ({"user": "manu", "password": "123"})
- router.POST("/loginJSON", func(c *gin.Context) {
- var json Login
- if err := c.ShouldBindJSON(&json); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
-
- if json.User != "manu" || json.Password != "123" {
- c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
- return
- }
-
- c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
- })
-
- // Example for binding XML (
- //
- //
Using posts/index.tmpl
- -{{ end }} -``` - -templates/users/index.tmpl - -```html -{{ define "users/index.tmpl" }} -Using users/index.tmpl
- -{{ end }} -``` - -#### 自定义模板渲染器 - -您还可以使用自己的 html 模板渲染 - -```go -import "html/template" - -func main() { - router := gin.Default() - html := template.Must(template.ParseFiles("file1", "file2")) - router.SetHTMLTemplate(html) - router.Run(":8080") -} -``` - -#### 自定义分隔符 - -您可以使用自定义分隔 - -```go - r := gin.Default() - r.Delims("{[{", "}]}") - r.LoadHTMLGlob("/path/to/templates") -``` - -#### 自定义模板功能 - -查看详细信息[示例代码](examples/template)。 - -main.go - -```go -import ( - "fmt" - "html/template" - "net/http" - "time" - - "github.com/gin-gonic/gin" -) - -func formatAsDate(t time.Time) string { - year, month, day := t.Date() - return fmt.Sprintf("%d%02d/%02d", year, month, day) -} - -func main() { - router := gin.Default() - router.Delims("{[{", "}]}") - router.SetFuncMap(template.FuncMap{ - "formatAsDate": formatAsDate, - }) - router.LoadHTMLFiles("./testdata/template/raw.tmpl") - - router.GET("/raw", func(c *gin.Context) { - c.HTML(http.StatusOK, "raw.tmpl", map[string]interface{}{ - "now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC), - }) - }) - - router.Run(":8080") -} - -``` - -raw.tmpl - -```html -日期: {[{.now | formatAsDate}]} -``` - -结果: -``` -Date: 2017/07/01 -``` - -### 多模板 - -Gin 允许默认只使用一个 html 模板。 检查[多模板渲染](https://github.com/gin-contrib/multitemplate)以使用 go 1.6 `block template` 等功能。 - -### 重定向 - -Issuing a HTTP redirect is easy. Both internal and external locations are supported. - -```go -r.GET("/test", func(c *gin.Context) { - c.Redirect(http.StatusMovedPermanently, "http://www.google.com/") -}) -``` - - -发出路由器重定向,使用如下的“HandleContext”。 - -``` go -r.GET("/test", func(c *gin.Context) { - c.Request.URL.Path = "/test2" - r.HandleContext(c) -}) -r.GET("/test2", func(c *gin.Context) { - c.JSON(200, gin.H{"hello": "world"}) -}) -``` - - -### 自定义中间件 - -```go -func Logger() gin.HandlerFunc { - return func(c *gin.Context) { - t := time.Now() - - // Set example variable - c.Set("example", "12345") - - // before request - - c.Next() - - // after request - latency := time.Since(t) - log.Print(latency) - - // access the status we are sending - status := c.Writer.Status() - log.Println(status) - } -} - -func main() { - r := gin.New() - r.Use(Logger()) - - r.GET("/test", func(c *gin.Context) { - example := c.MustGet("example").(string) - - // it would print: "12345" - log.Println(example) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### BasicAuth() 中间件 - -```go -// simulate some private data -var secrets = gin.H{ - "foo": gin.H{"email": "foo@bar.com", "phone": "123433"}, - "austin": gin.H{"email": "austin@example.com", "phone": "666"}, - "lena": gin.H{"email": "lena@guapa.com", "phone": "523443"}, -} - -func main() { - r := gin.Default() - - // Group using gin.BasicAuth() middleware - // gin.Accounts is a shortcut for map[string]string - authorized := r.Group("/admin", gin.BasicAuth(gin.Accounts{ - "foo": "bar", - "austin": "1234", - "lena": "hello2", - "manu": "4321", - })) - - // /admin/secrets endpoint - // hit "localhost:8080/admin/secrets - authorized.GET("/secrets", func(c *gin.Context) { - // get user, it was set by the BasicAuth middleware - user := c.MustGet(gin.AuthUserKey).(string) - if secret, ok := secrets[user]; ok { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": secret}) - } else { - c.JSON(http.StatusOK, gin.H{"user": user, "secret": "NO SECRET :("}) - } - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### 在中间件中使用Goroutines - -当在中间件或handler中启动新的Goroutines时,不能使用原始上下文,必须使用只读副本。 - -```go -func main() { - r := gin.Default() - - r.GET("/long_async", func(c *gin.Context) { - // create copy to be used inside the goroutine - cCp := c.Copy() - go func() { - // simulate a long task with time.Sleep(). 5 seconds - time.Sleep(5 * time.Second) - - // note that you are using the copied context "cCp", IMPORTANT - log.Println("Done! in path " + cCp.Request.URL.Path) - }() - }) - - r.GET("/long_sync", func(c *gin.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.Println("Done! in path " + c.Request.URL.Path) - }) - - // Listen and serve on 0.0.0.0:8080 - r.Run(":8080") -} -``` - -### 自定义 HTTP 配置 - -直接使用`http.ListenAndServe()`,如下所示: - -```go -func main() { - router := gin.Default() - http.ListenAndServe(":8080", router) -} -``` -or - -```go -func main() { - router := gin.Default() - - s := &http.Server{ - Addr: ":8080", - Handler: router, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - s.ListenAndServe() -} -``` - -### Let's Encrypt 支持 - -一行代码支持 LetsEncrypt HTTPS示例。 - -[embedmd]:# (examples/auto-tls/example1/main.go go) -```go -package main - -import ( - "log" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - log.Fatal(autotls.Run(r, "example1.com", "example2.com")) -} -``` - -autocert使用示例。 - -[embedmd]:# (examples/auto-tls/example2/main.go go) -```go -package main - -import ( - "log" - - "github.com/gin-gonic/autotls" - "github.com/gin-gonic/gin" - "golang.org/x/crypto/acme/autocert" -) - -func main() { - r := gin.Default() - - // Ping handler - r.GET("/ping", func(c *gin.Context) { - c.String(200, "pong") - }) - - m := autocert.Manager{ - Prompt: autocert.AcceptTOS, - HostPolicy: autocert.HostWhitelist("example1.com", "example2.com"), - Cache: autocert.DirCache("/var/www/.cache"), - } - - log.Fatal(autotls.RunWithManager(r, &m)) -} -``` - -### 使用 Gin 运行多个服务 - -请参阅[问题](https://github.com/gin-gonic/gin/issues/346)并尝试以下示例: - -[embedmd]:# (examples/multiple-service/main.go go) -```go -package main - -import ( - "log" - "net/http" - "time" - - "github.com/gin-gonic/gin" - "golang.org/x/sync/errgroup" -) - -var ( - g errgroup.Group -) - -func router01() http.Handler { - e := gin.New() - e.Use(gin.Recovery()) - e.GET("/", func(c *gin.Context) { - c.JSON( - http.StatusOK, - gin.H{ - "code": http.StatusOK, - "error": "Welcome server 01", - }, - ) - }) - - return e -} - -func router02() http.Handler { - e := gin.New() - e.Use(gin.Recovery()) - e.GET("/", func(c *gin.Context) { - c.JSON( - http.StatusOK, - gin.H{ - "code": http.StatusOK, - "error": "Welcome server 02", - }, - ) - }) - - return e -} - -func main() { - server01 := &http.Server{ - Addr: ":8080", - Handler: router01(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - server02 := &http.Server{ - Addr: ":8081", - Handler: router02(), - ReadTimeout: 5 * time.Second, - WriteTimeout: 10 * time.Second, - } - - g.Go(func() error { - return server01.ListenAndServe() - }) - - g.Go(func() error { - return server02.ListenAndServe() - }) - - if err := g.Wait(); err != nil { - log.Fatal(err) - } -} -``` - -### 优雅重启或停止 - -您想要优雅地重启或停止您的Web服务器吗? -有一些方法可以做到这一点。 - -我们可以使用[fvbock/endless](https://github.com/fvbock/endless)来替换默认的`ListenAndServe`。 有关更多详细信息,请参阅问题[#296](https://github.com/gin-gonic/gin/issues/296)。 - -```go -router := gin.Default() -router.GET("/", handler) -// [...] -endless.ListenAndServe(":4242", router) -``` - -替代方案: - -* [manners](https://github.com/braintree/manners):礼貌的Go HTTP服务器,可以正常关闭。 -* [graceful](https://github.com/tylerb/graceful):Graceful是一个Go包,可以正常关闭http.Handler服务器。 -* [grace](https://github.com/facebookgo/grace):Go服务器的平滑重启和零停机时间部署。 - -如果您使用的是Go 1.8,可以考虑使用http.Server的内置[Shutdown()](https://golang.org/pkg/net/http/#Server.Shutdown)方法进行正常关机。 -请参阅gin的完整[graceful-shutdown](/examples /graceful-shutdown)示例。 - -[embedmd]:# (examples/graceful-shutdown/graceful-shutdown/server.go go) -```go -// +build go1.8 - -package main - -import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "time" - - "github.com/gin-gonic/gin" -) - -func main() { - router := gin.Default() - router.GET("/", func(c *gin.Context) { - time.Sleep(5 * time.Second) - c.String(http.StatusOK, "Welcome Gin Server") - }) - - srv := &http.Server{ - Addr: ":8080", - Handler: router, - } - - go func() { - // service connections - if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("listen: %s\n", err) - } - }() - - // Wait for interrupt signal to gracefully shutdown the server with - // a timeout of 5 seconds. - quit := make(chan os.Signal) - signal.Notify(quit, os.Interrupt) - <-quit - log.Println("Shutdown Server ...") - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := srv.Shutdown(ctx); err != nil { - log.Fatal("Server Shutdown:", err) - } - log.Println("Server exiting") -} -``` - -### 静态资源嵌入 - -使用[go-assets](https://github.com/jessevdk/go-assets)将静态资源打包到可执行文件中。 - -```go -func main() { - r := gin.New() - - t, err := loadTemplate() - if err != nil { - panic(err) - } - r.SetHTMLTemplate(t) - - r.GET("/", func(c *gin.Context) { - c.HTML(http.StatusOK, "/html/index.tmpl",nil) - }) - r.Run(":8080") -} - -// loadTemplate loads templates embedded by go-assets-builder -func loadTemplate() (*template.Template, error) { - t := template.New("") - for name, file := range Assets.Files { - if file.IsDir() || !strings.HasSuffix(name, ".tmpl") { - continue - } - h, err := ioutil.ReadAll(file) - if err != nil { - return nil, err - } - t, err = t.New(name).Parse(string(h)) - if err != nil { - return nil, err - } - } - return t, nil -} -``` - -请参阅`examples/assets-in-binary`目录中的完整示例。 - -### 表单数据绑定到自定义结构体 - -以下示例使用自定义结构体: - -```go -type StructA struct { - FieldA string `form:"field_a"` -} - -type StructB struct { - NestedStruct StructA - FieldB string `form:"field_b"` -} - -type StructC struct { - NestedStructPointer *StructA - FieldC string `form:"field_c"` -} - -type StructD struct { - NestedAnonyStruct struct { - FieldX string `form:"field_x"` - } - FieldD string `form:"field_d"` -} - -func GetDataB(c *gin.Context) { - var b StructB - c.Bind(&b) - c.JSON(200, gin.H{ - "a": b.NestedStruct, - "b": b.FieldB, - }) -} - -func GetDataC(c *gin.Context) { - var b StructC - c.Bind(&b) - c.JSON(200, gin.H{ - "a": b.NestedStructPointer, - "c": b.FieldC, - }) -} - -func GetDataD(c *gin.Context) { - var b StructD - c.Bind(&b) - c.JSON(200, gin.H{ - "x": b.NestedAnonyStruct, - "d": b.FieldD, - }) -} - -func main() { - r := gin.Default() - r.GET("/getb", GetDataB) - r.GET("/getc", GetDataC) - r.GET("/getd", GetDataD) - - r.Run() -} -``` - -`curl`命令示例: - -``` -$ curl "http://localhost:8080/getb?field_a=hello&field_b=world" -{"a":{"FieldA":"hello"},"b":"world"} -$ curl "http://localhost:8080/getc?field_a=hello&field_c=world" -{"a":{"FieldA":"hello"},"c":"world"} -$ curl "http://localhost:8080/getd?field_x=hello&field_d=world" -{"d":"world","x":{"FieldX":"hello"}} -``` - -**注意**:不支持以下格式结构体: - -```go -type StructX struct { - X struct {} `form:"name_x"` // HERE have form -} - -type StructY struct { - Y StructX `form:"name_y"` // HERE have form -} - -type StructZ struct { - Z *StructZ `form:"name_z"` // HERE have form -} -``` - -一句话,现在只支持没有`form`的嵌套结构体。 - -### 将request body绑定到不同的结构体中 - -一般通过调用`c.Request.Body`方法绑定数据,但不能多次调用这个方法。 - -```go -type formA struct { - Foo string `json:"foo" xml:"foo" binding:"required"` -} - -type formB struct { - Bar string `json:"bar" xml:"bar" binding:"required"` -} - -func SomeHandler(c *gin.Context) { - objA := formA{} - objB := formB{} - // This c.ShouldBind consumes c.Request.Body and it cannot be reused. - if errA := c.ShouldBind(&objA); errA == nil { - c.String(http.StatusOK, `the body should be formA`) - // Always an error is occurred by this because c.Request.Body is EOF now. - } else if errB := c.ShouldBind(&objB); errB == nil { - c.String(http.StatusOK, `the body should be formB`) - } else { - ... - } -} -``` - -为此,要想多次绑定,需要使用`c.ShouldBindBodyWith`. - -```go -func SomeHandler(c *gin.Context) { - objA := formA{} - objB := formB{} - // This reads c.Request.Body and stores the result into the context. - if errA := c.ShouldBindBodyWith(&objA, binding.JSON); errA == nil { - c.String(http.StatusOK, `the body should be formA`) - // At this time, it reuses body stored in the context. - } else if errB := c.ShouldBindBodyWith(&objB, binding.JSON); errB == nil { - c.String(http.StatusOK, `the body should be formB JSON`) - // And it can accepts other formats - } else if errB2 := c.ShouldBindBodyWith(&objB, binding.XML); errB2 == nil { - c.String(http.StatusOK, `the body should be formB XML`) - } else { - ... - } -} -``` - - -* `c.ShouldBindBodyWith`会在绑定之前将body存储到上下文中。 这会对性能造成轻微影响,如果调用一次就能完成绑定的话,那就不要用这个方法。 -* 只有某些格式需要此功能 ,如`JSON`、`XML`、`MsgPack`、`ProtoBuf`。 对于其他格式,如`Query`、`Form`、`FormPost`、`FormMultipart`可以多次调用`c.ShouldBind()`而不会造成任任何性能损失(见[#1341](https://github.com/gin-gonic/gin/pull/1341))。 - -### http2 server 推送 - - -http.Pusher仅支持go1.8 +。 有关详细信息,请参阅[golang blog](https://blog.golang.org/h2push)。 - -[embedmd]:# (examples/http-pusher/main.go go) -```go -package main - -import ( - "html/template" - "log" - - "github.com/gin-gonic/gin" -) - -var html = template.Must(template.New("https").Parse(` - - -