go第三库Gin使用示例

package main

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/autotls"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"golang.org/x/crypto/acme/autocert"
	"io"
	"io/ioutil"
	"log"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"testing"
	"time"
)

func main()  {
	fmt.Println("go-gin测试")

	// 快速启动框架
	// router := gin.New()	// 默认不带中间件
	router := gin.Default()		// 默认带了 Logger 和 Recovery 中间件
	router.GET("/ping", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "pong",
		})
	})
	//r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")

	// 使用 GET、POST、PUT、PATCH、DELETE 和 OPTIONS
	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)

	// 获取路径中的参数
	// 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)
	})
	// For each matched request Context will hold the route definition
	router.POST("/user/:name/*action", func(c *gin.Context) {
		b := c.FullPath() == "/user/:name/*action" // true
		c.String(http.StatusOK, "%t", b)
	})
	// This handler will add a new router for /user/groups.
	// Exact routes are resolved before param routes, regardless of the order they were defined.
	// Routes starting with /user/groups are never interpreted as /user/:name/... routes
	router.GET("/user/groups", func(c *gin.Context) {
		c.String(http.StatusOK, "The available groups are [...]")
	})

	// 获取字符串中的参数
	// 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)
	})

	// Multipart/Urlencoded 表格
	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,
		})
	})
	// 查询 提交表单示例
	// POST /post?id=1234&page=1 HTTP/1.1
	// Content-Type: application/x-www-form-urlencoded
	// name=manu&message=this_is_great
	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)
	})
	// 输出的结果: 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
	router.POST("/post", func(c *gin.Context) {
		ids := c.QueryMap("ids")
		names := c.PostFormMap("names")
		fmt.Printf("ids: %v; names: %v", ids, names)
	})
	// 输出结果: ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou]

	// 上传单个文件示例
	// curl -X POST http://localhost:8080/upload \
	// -F "file=@/Users/appleboy/test.zip" \
	// -H "Content-Type: multipart/form-data"
	// 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))
	})

	// 	上传多个文件示例
	//	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"
	// 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)))
	})

	// 分组路由
	// 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)
	}


	// 使用中间件
	// 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)
	}

	// 自定义恢复
	// Creates a router without any middleware by default
	rr := 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
	rr.Use(gin.Logger())
	// Recovery middleware recovers from any panics and writes a 500 if there was one.
	rr.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
		if err, ok := recovered.(string); ok {
			c.String(http.StatusInternalServerError, fmt.Sprintf("error: %s", err))
		}
		c.AbortWithStatus(http.StatusInternalServerError)
	}))
	rr.GET("/panic", func(c *gin.Context) {
		// panic with a string -- the custom middleware could save this to a database or report it to the user
		panic("foo")
	})
	rr.GET("/", func(c *gin.Context) {
		c.String(http.StatusOK, "ohai")
	})

	// 写日志文件
	// 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)
	routerr := gin.Default()
	routerr.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})

	// 自定义日志格式
	routerrr := gin.New()
	// LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
	// By default gin.DefaultWriter = os.Stdout
	routerrr.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
		// your custom format
		return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
			param.ClientIP,
			param.TimeStamp.Format(time.RFC1123),
			param.Method,
			param.Path,
			param.Request.Proto,
			param.StatusCode,
			param.Latency,
			param.Request.UserAgent(),
			param.ErrorMessage,
		)
	}))
	routerrr.Use(gin.Recovery())
	routerrr.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})
	// 输出: ::1 - [Fri, 07 Dec 2018 17:04:38 JST] "GET /ping HTTP/1.1 200 122.767µs "Mozilla/5.0 (Macintosh;
	//Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36" "

	// 输出日志的颜色
	// Disable log's color
	//gin.DisableConsoleColor()	// 日志不着色
	// Force log's color
	gin.ForceConsoleColor()	// 日志着色
	// Creates a gin router with default middleware:
	// logger and recovery (crash-free) middleware
	routeer := gin.Default()
	routeer.GET("/ping", func(c *gin.Context) {
		c.String(200, "pong")
	})

	// 模型绑定和验证
	// 方法- Bind, BindJSON, BindXML, BindQuery, BindYAML,BindHeader
	// 方法- ShouldBind, ShouldBindJSON, ShouldBindXML, ShouldBindQuery, ShouldBindYAML,ShouldBindHeader
	type Login struct {
		User     string `form:"user" json:"user" xml:"user"  binding:"required"`
		//Password string `form:"password" json:"password" xml:"password" binding:"required"`	// 会报输出错误
		Password string `form:"password" json:"password" xml:"password" binding:"-"`
	}
	// 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 (
	//	<?xml version="1.0" encoding="UTF-8"?>
	//	<root>
	//		<user>manu</user>
	//		<password>123</password>
	//	</root>)
	router.POST("/loginXML", func(c *gin.Context) {
		var xml Login
		if err := c.ShouldBindXML(&xml); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		if xml.User != "manu" || xml.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 a HTML form (user=manu&password=123)
	router.POST("/loginForm", func(c *gin.Context) {
		var form Login
		// This will infer what binder to use depending on the content-type header.
		if err := c.ShouldBind(&form); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		if form.User != "manu" || form.Password != "123" {
			c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
			return
		}
		c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
	})
	// 请求示例
	//$ curl -v -X POST \
	//http://localhost:8080/loginJSON \
	//-H 'content-type: application/json' \
	//-d '{ "user": "manu" }'
	//> POST /loginJSON HTTP/1.1
	//> Host: localhost:8080
	//> User-Agent: curl/7.51.0
	//> Accept: */*
	//> content-type: application/json
	//> Content-Length: 18
	//>
	//* upload completely sent off: 18 out of 18 bytes
	//< HTTP/1.1 400 Bad Request
	//< Content-Type: application/json; charset=utf-8
	//< Date: Fri, 04 Aug 2017 03:51:31 GMT
	//< Content-Length: 100
	//<
	//{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}

	// 自定义验证器
	//package main
	//import (
	//	"net/http"
	//"time"
	//"github.com/gin-gonic/gin"
	//"github.com/gin-gonic/gin/binding"
	//"github.com/go-playground/validator/v10"
	//)
	//
	 Booking contains binded and validated data.
	//type Booking struct {
	//	CheckIn  time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`
	//	CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`
	//}
	//
	//var bookableDate validator.Func = func(fl validator.FieldLevel) bool {
	//	date, ok := fl.Field().Interface().(time.Time)
	//	if ok {
	//		today := time.Now()
	//		if today.After(date) {
	//			return false
	//		}
	//	}
	//	return true
	//}
	//
	//func main() {
	//	route := gin.Default()
	//
	//	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
	//		v.RegisterValidation("bookabledate", bookableDate)
	//	}
	//
	//	route.GET("/bookable", getBookable)
	//	route.Run(":8085")
	//}
	//
	//func getBookable(c *gin.Context) {
	//	var b Booking
	//	if err := c.ShouldBindWith(&b, binding.Query); err == nil {
	//		c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})
	//	} else {
	//		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	//	}
	//}
	// 测试
	//$ curl "localhost:8085/bookable?check_in=2030-04-16&check_out=2030-04-17"
	//{"message":"Booking dates are valid!"}
	//$ curl "localhost:8085/bookable?check_in=2030-03-10&check_out=2030-03-09"
	//{"error":"Key: 'Booking.CheckOut' Error:Field validation for 'CheckOut' failed on the 'gtfield' tag"}
	//$ curl "localhost:8085/bookable?check_in=2000-03-09&check_out=2000-03-10"
	//{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}%

	// 只绑定查询字符串
	//type Person struct {
	//	Name    string `form:"name"`
	//	Address string `form:"address"`
	//}
	//func main() {
	//	route := gin.Default()
	//	route.Any("/testing", startPage)
	//	route.Run(":8085")
	//}
	//func startPage(c *gin.Context) {
	//	var person Person
	//	if c.ShouldBindQuery(&person) == nil {
	//		log.Println("====== Only Bind By Query String ======")
	//		log.Println(person.Name)
	//		log.Println(person.Address)
	//	}
	//	c.String(200, "Success")
	//}

	// 绑定查询字符串或发布数据
	//type Person struct {
	//	Name       string    `form:"name"`
	//	Address    string    `form:"address"`
	//	Birthday   time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`
	//	CreateTime time.Time `form:"createTime" time_format:"unixNano"`
	//	UnixTime   time.Time `form:"unixTime" time_format:"unix"`
	//}
	//func main() {
	//	route := gin.Default()
	//	route.GET("/testing", startPage)
	//	route.Run(":8085")
	//}
	//func startPage(c *gin.Context) {
	//	var person Person
	//	// If `GET`, only `Form` binding engine (`query`) used.
	//	// If `POST`, first checks the `content-type` for `JSON` or `XML`, then uses `Form` (`form-data`).
	//	// See more at https://github.com/gin-gonic/gin/blob/master/binding/binding.go#L48
	//	if c.ShouldBind(&person) == nil {
	//		log.Println(person.Name)
	//		log.Println(person.Address)
	//		log.Println(person.Birthday)
	//		log.Println(person.CreateTime)
	//		log.Println(person.UnixTime)
	//	}
	//	c.String(200, "Success")
	//}
	//

	// 绑定Uri
	//type Person struct {
	//	ID string `uri:"id" binding:"required,uuid"`
	//	Name string `uri:"name" binding:"required"`
	//}
	//func main() {
	//	route := gin.Default()
	//	route.GET("/:name/:id", func(c *gin.Context) {
	//		var person Person
	//		if err := c.ShouldBindUri(&person); err != nil {
	//			c.JSON(400, gin.H{"msg": err.Error()})
	//			return
	//		}
	//		c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
	//	})
	//	route.Run(":8088")
	//}
	// 测试
	//$ curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3
	//$ curl -v localhost:8088/thinkerou/not-uuid

	// 绑定标题
	//type testHeader struct {
	//	Rate   int    `header:"Rate"`
	//	Domain string `header:"Domain"`
	//}
	//func main() {
	//	r := gin.Default()
	//	r.GET("/", func(c *gin.Context) {
	//		h := testHeader{}
	//
	//		if err := c.ShouldBindHeader(&h); err != nil {
	//			c.JSON(200, err)
	//		}
	//		fmt.Printf("%#v\n", h)
	//		c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain})
	//	})
	//	r.Run()
	//	// client
	//	// curl -H "rate:300" -H "domain:music" 127.0.0.1:8080/
	//	// output
	//	// {"Domain":"music","Rate":300}
	//}
	//

	// 绑定 HTML 复选框
	//...
	//type myForm struct {
	//	Colors []string `form:"colors[]"`
	//}
	//...
	//func formHandler(c *gin.Context) {
	//	var fakeForm myForm
	//	c.ShouldBind(&fakeForm)
	//	c.JSON(200, gin.H{"color": fakeForm.Colors})
	//}
	//...
	// form.html文件内容
	//<form action="/" method="POST">
	//<p>Check some colors</p>
	//<label for="red">Red</label>
	//<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>
	// 输出结果: {"color":["red","green","blue"]}

	// Multipart/Urlencoded 绑定
	//type ProfileForm struct {
	//	Name   string                `form:"name" binding:"required"`
	//	Avatar *multipart.FileHeader `form:"avatar" binding:"required"`
	//
	//	// or for multiple files
	//	// Avatars []*multipart.FileHeader `form:"avatar" binding:"required"`
	//}
	//func main() {
	//	router := gin.Default()
	//	router.POST("/profile", func(c *gin.Context) {
	//		// you can bind multipart form with explicit binding declaration:
	//		// c.ShouldBindWith(&form, binding.Form)
	//		// or you can simply use autobinding with ShouldBind method:
	//		var form ProfileForm
	//		// in this case proper binding will be automatically selected
	//		if err := c.ShouldBind(&form); err != nil {
	//			c.String(http.StatusBadRequest, "bad request")
	//			return
	//		}
	//		err := c.SaveUploadedFile(form.Avatar, form.Avatar.Filename)
	//		if err != nil {
	//			c.String(http.StatusInternalServerError, "unknown error")
	//			return
	//		}
	//		// db.Save(&form)
	//		c.String(http.StatusOK, "ok")
	//	})
	//	router.Run(":8080")
	//}
	// 测试  $ curl -X POST -v --form name=user --form "avatar=@./avatar.png" http://localhost:8080/profile

	// XML、JSON、YAML 和 ProtoBuf 渲染
	//func main() {
	//	r := gin.Default()
	//
	//	// gin.H is a shortcut for map[string]interface{}
	//	r.GET("/someJSON", func(c *gin.Context) {
	//		c.JSON(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
	//	})
	//
	//	r.GET("/moreJSON", func(c *gin.Context) {
	//		// You also can use a struct
	//		var msg struct {
	//			Name    string `json:"user"`
	//			Message string
	//			Number  int
	//		}
	//		msg.Name = "Lena"
	//		msg.Message = "hey"
	//		msg.Number = 123
	//		// Note that msg.Name becomes "user" in the JSON
	//		// Will output  :   {"user": "Lena", "Message": "hey", "Number": 123}
	//		c.JSON(http.StatusOK, msg)
	//	})
	//
	//	r.GET("/someXML", func(c *gin.Context) {
	//		c.XML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
	//	})
	//
	//	r.GET("/someYAML", func(c *gin.Context) {
	//		c.YAML(http.StatusOK, gin.H{"message": "hey", "status": http.StatusOK})
	//	})
	//
	//	r.GET("/someProtoBuf", func(c *gin.Context) {
	//		reps := []int64{int64(1), int64(2)}
	//		label := "test"
	//		// The specific definition of protobuf is written in the testdata/protoexample file.
	//		data := &protoexample.Test{
	//			Label: &label,
	//			Reps:  reps,
	//		}
	//		// Note that data becomes binary data in the response
	//		// Will output protoexample.Test protobuf serialized data
	//		c.ProtoBuf(http.StatusOK, data)
	//	})
	//
	//	// Listen and serve on 0.0.0.0:8080
	//	r.Run(":8080")
	//}
	//

	// 安全的json,防止json劫持
	//func main() {
	//	r := gin.Default()
	//
	//	// You can also use your own secure json prefix
	//	// r.SecureJsonPrefix(")]}',\n")
	//
	//	r.GET("/someJSON", func(c *gin.Context) {
	//		names := []string{"lena", "austin", "foo"}
	//
	//		// Will output  :   while(1);["lena","austin","foo"]
	//		c.SecureJSON(http.StatusOK, names)
	//	})
	//
	//	// Listen and serve on 0.0.0.0:8080
	//	r.Run(":8080")
	//}

	// JSONP
	// 使用 JSONP 从不同域中的服务器请求数据。如果存在查询参数回调,则向响应正文添加回调。
	//func main() {
	//	r := gin.Default()
	//
	//	r.GET("/JSONP", func(c *gin.Context) {
	//		data := gin.H{
	//			"foo": "bar",
	//		}
	//
	//		//callback is x
	//		// Will output  :   x({\"foo\":\"bar\"})
	//		c.JSONP(http.StatusOK, data)
	//	})
	//
	//	// Listen and serve on 0.0.0.0:8080
	//	r.Run(":8080")
	//
	//	// client
	//	// curl http://127.0.0.1:8080/JSONP?callback=x
	//}
	//

	// AsciiJSON
	// 使用 AsciiJSON 生成带有转义非 ASCII 字符的纯 ASCII JSON。
	//func main() {
	//	r := gin.Default()
	//
	//	r.GET("/someJSON", func(c *gin.Context) {
	//		data := gin.H{
	//			"lang": "GO语言",
	//			"tag":  "<br>",
	//		}
	//
	//		// will output : {"lang":"GO\u8bed\u8a00","tag":"\u003cbr\u003e"}
	//		c.AsciiJSON(http.StatusOK, data)
	//	})
	//
	//	// Listen and serve on 0.0.0.0:8080
	//	r.Run(":8080")
	//}

	// 纯JSON
	// 通常,JSON 用它们的 unicode 实体替换特殊的 HTML 字符,例如<变成 \u003c. 如果您想按字面意思对此类字符进行编码,则可以改用 PureJSON。此功能在 Go 1.6 及更低版本中不可用。
	//func main() {
	//	r := gin.Default()
	//
	//	// Serves unicode entities
	//	r.GET("/json", func(c *gin.Context) {
	//		c.JSON(200, gin.H{
	//			"html": "<b>Hello, world!</b>",
	//		})
	//	})
	//
	//	// Serves literal characters
	//	r.GET("/purejson", func(c *gin.Context) {
	//		c.PureJSON(200, gin.H{
	//			"html": "<b>Hello, world!</b>",
	//		})
	//	})
	//
	//	// listen and serve on 0.0.0.0:8080
	//	r.Run(":8080")
	//}

	// 提供静态文件
	//func main() {
	//	router := gin.Default()
	//	router.Static("/assets", "./assets")
	//	router.StaticFS("/more_static", http.Dir("my_file_system"))
	//	router.StaticFile("/favicon.ico", "./resources/favicon.ico")
	//
	//	// Listen and serve on 0.0.0.0:8080
	//	router.Run(":8080")
	//}

	// 从文件提供数据
	//func main() {
	//	router := gin.Default()
	//
	//	router.GET("/local/file", func(c *gin.Context) {
	//		c.File("local/file.go")
	//	})
	//
	//	var fs http.FileSystem = // ...
	//	router.GET("/fs/file", func(c *gin.Context) {
	//		c.FileFromFS("fs/file.go", fs)
	//	})
	//}

	// 从读取器提供数据
	//func main() {
	//	router := gin.Default()
	//	router.GET("/someDataFromReader", func(c *gin.Context) {
	//		response, err := http.Get("https://raw.githubusercontent.com/gin-gonic/logo/master/color.png")
	//		if err != nil || response.StatusCode != http.StatusOK {
	//			c.Status(http.StatusServiceUnavailable)
	//			return
	//		}
	//
	//		reader := response.Body
	//		defer reader.Close()
	//		contentLength := response.ContentLength
	//		contentType := response.Header.Get("Content-Type")
	//
	//		extraHeaders := map[string]string{
	//			"Content-Disposition": `attachment; filename="gopher.png"`,
	//		}
	//
	//		c.DataFromReader(http.StatusOK, contentLength, contentType, reader, extraHeaders)
	//	})
	//	router.Run(":8080")
	//}

	// HTML 渲染
	//func main() {
	//	router := gin.Default()
	//	router.LoadHTMLGlob("templates/*")
	//	//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
	//	router.GET("/index", func(c *gin.Context) {
	//		c.HTML(http.StatusOK, "index.tmpl", gin.H{
	//			"title": "Main website",
	//		})
	//	})
	//	router.Run(":8080")
	//}
	// templates/index.tmpl 文件内容
	//<html>
	//<h1>
	//{{ .title }}
	//</h1>
	//</html>
	// 在不同目录中使用同名模板
	//func main() {
	//	router := gin.Default()
	//	router.LoadHTMLGlob("templates/**/*")
	//	router.GET("/posts/index", func(c *gin.Context) {
	//		c.HTML(http.StatusOK, "posts/index.tmpl", gin.H{
	//			"title": "Posts",
	//		})
	//	})
	//	router.GET("/users/index", func(c *gin.Context) {
	//		c.HTML(http.StatusOK, "users/index.tmpl", gin.H{
	//			"title": "Users",
	//		})
	//	})
	//	router.Run(":8080")
	//}
	// templates/posts/index.tmpl 文件内容
	//{{ define "posts/index.tmpl" }}
	//<html><h1>
	//{{ .title }}
	//</h1>
	//<p>Using posts/index.tmpl</p>
	//</html>
	//{{ end }}
	// templates/users/index.tmpl 文件内容
	//{{ define "users/index.tmpl" }}
	//<html><h1>
	//{{ .title }}
	//</h1>
	//<p>Using users/index.tmpl</p>
	//</html>
	//{{ end }}

	// 自定义模板渲染器
	//import "html/template"
	//func main() {
	//	router := gin.Default()
	//	html := template.Must(template.ParseFiles("file1", "file2"))
	//	router.SetHTMLTemplate(html)
	//	router.Run(":8080")
	//}

	// 自定义分隔符
	// 您可以使用自定义 delims
	//r := gin.Default()
	//r.Delims("{[{", "}]}")
	//r.LoadHTMLGlob("/path/to/templates")

	// 自定义模板函数
	//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", gin.H{
	//			"now": time.Date(2017, 07, 01, 0, 0, 0, 0, time.UTC),
	//		})
	//	})
	//
	//	router.Run(":8080")
	//}
	// raw.tmpl 文件内容
	// 输出结果: Date: 2017/07/01

	// 多模板
	//Gin 默认只允许使用一个 html.Template。检查多模板渲染以使用 go 1.6 等功能block template。
	//重定向
	//发出 HTTP 重定向很容易。支持内部和外部位置。
	r.GET("/test", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "http://www.google.com/")
	})
	// 从 POST 发出 HTTP 重定向
	r.POST("/test", func(c *gin.Context) {
		c.Redirect(http.StatusFound, "/foo")
	})
	// 发出路由器重定向,使用HandleContext
	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"})
	})

	// 自定义中间件
	//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() 中间件
	// // 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
	// 在中间件或处理程序中启动新的 Goroutine 时,不应使用其中的原始上下文,而必须使用只读副本。
	//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()直接使用
	//func main() {
	//	router := gin.Default()
	//	http.ListenAndServe(":8080", router)
	//}
	// 或者
	//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()
	//}

	// 加密
	// LetsEncrypt HTTPS 服务器的示例
	//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"))
	//}

	// 自定义自动证书管理器的示例
	//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 运行多个服务
	//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 {
	//		err := server01.ListenAndServe()
	//		if err != nil && err != http.ErrServerClosed {
	//			log.Fatal(err)
	//		}
	//		return err
	//	})
	//
	//	g.Go(func() error {
	//		err := server02.ListenAndServe()
	//		if err != nil && err != http.ErrServerClosed {
	//			log.Fatal(err)
	//		}
	//		return err
	//	})
	//
	//	if err := g.Wait(); err != nil {
	//		log.Fatal(err)
	//	}
	//}

	// 执行正常关闭或重新启动,使用第三方包
	//router := gin.Default()
	//router.GET("/", handler)
	 [...]
	//endless.ListenAndServe(":4242", router)
	//manners: 优雅地关闭.
	//graceful: 正常关闭 http.Handler 服务器
	//grace: 优雅重启和零停机部署

	// 关闭,Shutdown() 方法
	 +build go1.8
	//
	//package main
	//
	//import (
	//	"context"
	//"log"
	//"net/http"
	//"os"
	//"os/signal"
	//"syscall"
	//"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,
	//	}
	//
	//	// Initializing the server in a goroutine so that
	//	// it won't block the graceful shutdown handling below
	//	go func() {
	//		if err := srv.ListenAndServe(); err != nil && errors.Is(err, http.ErrServerClosed) {
	//			log.Printf("listen: %s\n", err)
	//		}
	//	}()
	//
	//	// Wait for interrupt signal to gracefully shutdown the server with
	//	// a timeout of 5 seconds.
	//	quit := make(chan os.Signal)
	//	// kill (no param) default send syscall.SIGTERM
	//	// kill -2 is syscall.SIGINT
	//	// kill -9 is syscall.SIGKILL but can't be caught, so don't need to add it
	//	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	//	<-quit
	//	log.Println("Shutting down server...")
	//
	//	// The context is used to inform the server it has 5 seconds to finish
	//	// the request it is currently handling
	//	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	//	defer cancel()
	//
	//	if err := srv.Shutdown(ctx); err != nil {
	//		log.Fatal("Server forced to shutdown:", err)
	//	}
	//
	//	log.Println("Server exiting")
	//}
	//

	// 使用模板构建单个二进制文件
	// 您可以使用go-assets将服务器构建为包含模板的单个二进制文件
	//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 {
	//		defer file.Close()
	//		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
	//}

	// 使用自定义结构绑定表单数据请求
	//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"}}

	// 尝试将 body 绑定到不同的结构中
	// 绑定请求体的普通方法会消耗c.Request.Body,不能多次调用。
	//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
	//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.Form); 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)。

	// 使用自定义结构和自定义标记绑定表单数据请求
	//const (
	//	customerTag = "url"
	//	defaultMemory = 32 << 20
	//)
	//
	//type customerBinding struct {}
	//
	//func (customerBinding) Name() string {
	//	return "form"
	//}
	//
	//func (customerBinding) Bind(req *http.Request, obj interface{}) error {
	//if err := req.ParseForm(); err != nil {
	//return err
	//}
	//if err := req.ParseMultipartForm(defaultMemory); err != nil {
	//if err != http.ErrNotMultipart {
	//return err
	//}
	//}
	//if err := binding.MapFormWithTag(obj, req.Form, customerTag); err != nil {
	//return err
	//}
	//return validate(obj)
	//}
	//
	//func validate(obj interface{}) error {
	//if binding.Validator == nil {
	//return nil
	//}
	//return binding.Validator.ValidateStruct(obj)
	//}
	//
	 Now we can do this!!!
	 FormA is a external type that we can't modify it's tag
	//type FormA struct {
	//FieldA string `url:"field_a"`
	//}
	//
	//func ListHandler(s *Service) func(ctx *gin.Context) {
	//return func(ctx *gin.Context) {
	//var urlBinding = customerBinding{}
	//var opt FormA
	//err := ctx.MustBindWith(&opt, urlBinding)
	//if err != nil {
	//...
	//}
	//...
	//}
	//}

	// http2 服务器推送
	//package main
	//
	//import (
	//	"html/template"
	//"log"
	//
	//"github.com/gin-gonic/gin"
	//)
	//
	//var html = template.Must(template.New("https").Parse(`
	//	<html>
	//<head>
	//  <title>Https Test</title>
	//  <script src="/assets/app.js"></script>
	//</head>
	//<body>
	//  <h1 style="color:red;">Welcome, Ginner!</h1>
	//</body>
	//</html>
	//`))
	//
	//func main() {
	//	r := gin.Default()
	//	r.Static("/assets", "./assets")
	//	r.SetHTMLTemplate(html)
	//
	//	r.GET("/", func(c *gin.Context) {
	//		if pusher := c.Writer.Pusher(); pusher != nil {
	//			// use pusher.Push() to do server push
	//			if err := pusher.Push("/assets/app.js", nil); err != nil {
	//				log.Printf("Failed to push: %v", err)
	//			}
	//		}
	//		c.HTML(200, "https", gin.H{
	//			"status": "success",
	//		})
	//	})
	//
	//	// Listen and Server in https://127.0.0.1:8080
	//	r.RunTLS(":8080", "./testdata/server.pem", "./testdata/server.key")
	//}

	// 使用标准日志包记录所有路由
	//import (
	//	"log"
	//"net/http"
	//
	//"github.com/gin-gonic/gin"
	//)
	//
	//func main() {
	//	r := gin.Default()
	//	gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
	//		log.Printf("endpoint %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
	//	}
	//
	//	r.POST("/foo", func(c *gin.Context) {
	//		c.JSON(http.StatusOK, "foo")
	//	})
	//
	//	r.GET("/bar", func(c *gin.Context) {
	//		c.JSON(http.StatusOK, "bar")
	//	})
	//
	//	r.GET("/status", func(c *gin.Context) {
	//		c.JSON(http.StatusOK, "ok")
	//	})
	//
	//	// Listen and Server in http://0.0.0.0:8080
	//	r.Run()
	//}

	// 设置并获取 cookie
	//import (
	//	"fmt"
	//"github.com/gin-gonic/gin"
	//)
	//func main() {
	//	router := gin.Default()
	//	router.GET("/cookie", func(c *gin.Context) {
	//		cookie, err := c.Cookie("gin_cookie")
	//		if err != nil {
	//			cookie = "NotSet"
	//			c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
	//		}
	//		fmt.Printf("Cookie value: %s \n", cookie)
	//	})
	//	router.Run()
	//}

	// 信任代理设置
	//import (
	//	"fmt"
	//
	//"github.com/gin-gonic/gin"
	//)
	//
	//func main() {
	//
	//	router := gin.Default()
	//	router.SetTrustedProxies([]string{"192.168.1.2"})
	//
	//	router.GET("/", func(c *gin.Context) {
	//		// If the client is 192.168.1.2, use the X-Forwarded-For
	//		// header to deduce the original client IP from the trust-
	//		// worthy parts of that header.
	//		// Otherwise, simply return the direct client IP
	//		fmt.Printf("ClientIP: %s\n", c.ClientIP())
	//	})
	//	router.Run()
	//}

	// 使用的是 CDN 服务,您可以设置Engine.TrustedPlatform 跳过 TrustedProxies 检查,它的优先级高于 TrustedProxies
	//import (
	//	"fmt"
	//
	//"github.com/gin-gonic/gin"
	//)
	//
	//func main() {
	//
	//	router := gin.Default()
	//	// Use predefined header gin.PlatformXXX
	//	router.TrustedPlatform = gin.PlatformGoogleAppEngine
	//	// Or set your own trusted request header for another trusted proxy service
	//	// Don't set it to any suspect request header, it's unsafe
	//	router.TrustedPlatform = "X-CDN-IP"
	//
	//	router.GET("/", func(c *gin.Context) {
	//		// If you set TrustedPlatform, ClientIP() will resolve the
	//		// corresponding header and return IP directly
	//		fmt.Printf("ClientIP: %s\n", c.ClientIP())
	//	})
	//	router.Run()
	//}

	// 测试
	//该net/http/httptest包是 HTTP 测试的首选方式。
	//package main
	//
	//func setupRouter() *gin.Engine {
	//	r := gin.Default()
	//	r.GET("/ping", func(c *gin.Context) {
	//	c.String(200, "pong")
	//})
	//	return r
	//}
	//
	//func main() {
	//	r := setupRouter()
	//	r.Run(":8080")
	//}
	// 测试上面的代码
	//package main
	//
	//import (
	//	"net/http"
	//"net/http/httptest"
	//"testing"
	//
	//"github.com/stretchr/testify/assert"
	//)
	//
	//func TestPingRoute(t *testing.T) {
	//	router := setupRouter()
	//
	//	w := httptest.NewRecorder()
	//	req, _ := http.NewRequest("GET", "/ping", nil)
	//	router.ServeHTTP(w, req)
	//
	//	assert.Equal(t, 200, w.Code)
	//	assert.Equal(t, "pong", w.Body.String())
	//}



	router.Run(":8080")
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值