Merge tag 'tags/v1.11.0'
This commit is contained in:
+138
-16
@@ -63,6 +63,7 @@
|
||||
- [http2 server push](#http2-server-push)
|
||||
- [Define format for the log of routes](#define-format-for-the-log-of-routes)
|
||||
- [Set and get a cookie](#set-and-get-a-cookie)
|
||||
- [Custom json codec at runtime](#custom-json-codec-at-runtime)
|
||||
- [Don't trust all proxies](#dont-trust-all-proxies)
|
||||
- [Testing](#testing)
|
||||
|
||||
@@ -70,7 +71,7 @@
|
||||
|
||||
### Build with json replacement
|
||||
|
||||
Gin uses `encoding/json` as default json package but you can change it by build from other tags.
|
||||
Gin uses `encoding/json` as the default JSON package but you can change it by building from other tags.
|
||||
|
||||
[jsoniter](https://github.com/json-iterator/go)
|
||||
|
||||
@@ -84,10 +85,10 @@ go build -tags=jsoniter .
|
||||
go build -tags=go_json .
|
||||
```
|
||||
|
||||
[sonic](https://github.com/bytedance/sonic) (you have to ensure that your cpu support avx instruction.)
|
||||
[sonic](https://github.com/bytedance/sonic)
|
||||
|
||||
```sh
|
||||
$ go build -tags="sonic avx" .
|
||||
$ go build -tags=sonic .
|
||||
```
|
||||
|
||||
### Build without `MsgPack` rendering feature
|
||||
@@ -120,7 +121,7 @@ func main() {
|
||||
router.HEAD("/someHead", head)
|
||||
router.OPTIONS("/someOptions", options)
|
||||
|
||||
// By default it serves on :8080 unless a
|
||||
// By default, it serves on :8080 unless a
|
||||
// PORT environment variable was defined.
|
||||
router.Run()
|
||||
// router.Run(":3000") for a hard coded port
|
||||
@@ -172,7 +173,7 @@ func main() {
|
||||
router := gin.Default()
|
||||
|
||||
// Query string parameters are parsed using the existing underlying request object.
|
||||
// The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe
|
||||
// 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")
|
||||
@@ -300,7 +301,7 @@ curl -X POST http://localhost:8080/upload \
|
||||
|
||||
#### Multiple files
|
||||
|
||||
See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple).
|
||||
See the detailed [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple).
|
||||
|
||||
```go
|
||||
func main() {
|
||||
@@ -704,7 +705,7 @@ $ curl -v -X POST \
|
||||
{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
|
||||
```
|
||||
|
||||
Skip validate: when running the above example using the above the `curl` command, it returns error. Because the example use `binding:"required"` for `Password`. If use `binding:"-"` for `Password`, then it will not return error when running the above example again.
|
||||
Skip-validation: Running the example above using the `curl` command returns an error. This is because the example uses `binding:"required"` for `Password`. If instead, you use `binding:"-"` for `Password`, then it will not return an error when you run the example again.
|
||||
|
||||
### Custom Validators
|
||||
|
||||
@@ -832,6 +833,8 @@ type Person struct {
|
||||
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"`
|
||||
UnixMilliTime time.Time `form:"unixMilliTime" time_format:"unixmilli"`
|
||||
UnixMicroTime time.Time `form:"unixMicroTime" time_format:"uNiXmIcRo"` // case does not matter for "unix*" time formats
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -851,6 +854,8 @@ func startPage(c *gin.Context) {
|
||||
log.Println(person.Birthday)
|
||||
log.Println(person.CreateTime)
|
||||
log.Println(person.UnixTime)
|
||||
log.Println(person.UnixMilliTime)
|
||||
log.Println(person.UnixMicroTime)
|
||||
}
|
||||
|
||||
c.String(http.StatusOK, "Success")
|
||||
@@ -860,14 +865,14 @@ func startPage(c *gin.Context) {
|
||||
Test it with:
|
||||
|
||||
```sh
|
||||
curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033"
|
||||
curl -X GET "localhost:8085/testing?name=appleboy&address=xyz&birthday=1992-03-15&createTime=1562400033000000123&unixTime=1562400033&unixMilliTime=1562400033001&unixMicroTime=1562400033000012"
|
||||
```
|
||||
|
||||
### Bind default value if none provided
|
||||
|
||||
If the server should bind a default value to a field when the client does not provide one, specify the default value using the `default` key within the `form` tag:
|
||||
|
||||
```
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -1186,7 +1191,7 @@ func main() {
|
||||
})
|
||||
|
||||
r.GET("/moreJSON", func(c *gin.Context) {
|
||||
// You also can use a struct
|
||||
// You can also use a struct
|
||||
var msg struct {
|
||||
Name string `json:"user"`
|
||||
Message string
|
||||
@@ -1392,13 +1397,19 @@ func main() {
|
||||
|
||||
### HTML rendering
|
||||
|
||||
Using LoadHTMLGlob() or LoadHTMLFiles()
|
||||
Using LoadHTMLGlob() or LoadHTMLFiles() or LoadHTMLFS()
|
||||
|
||||
```go
|
||||
//go:embed templates/*
|
||||
var templates embed.FS
|
||||
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
router.LoadHTMLGlob("templates/*")
|
||||
//router.LoadHTMLFiles("templates/template1.html", "templates/template2.html")
|
||||
//router.LoadHTMLFS(http.Dir("templates"), "template1.html", "template2.html")
|
||||
//or
|
||||
//router.LoadHTMLFS(http.FS(templates), "templates/template1.html", "templates/template2.html")
|
||||
router.GET("/index", func(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||
"title": "Main website",
|
||||
@@ -1485,7 +1496,7 @@ You may use custom delims
|
||||
|
||||
#### Custom Template Funcs
|
||||
|
||||
See the detail [example code](https://github.com/gin-gonic/examples/tree/master/template).
|
||||
See the detailed [example code](https://github.com/gin-gonic/examples/tree/master/template).
|
||||
|
||||
main.go
|
||||
|
||||
@@ -1537,7 +1548,7 @@ Date: 2017/07/01
|
||||
|
||||
### Multitemplate
|
||||
|
||||
Gin allow by default use only one html.Template. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.
|
||||
Gin allows only one html.Template by default. Check [a multitemplate render](https://github.com/gin-contrib/multitemplate) for using features like go 1.6 `block template`.
|
||||
|
||||
### Redirects
|
||||
|
||||
@@ -2086,7 +2097,7 @@ type formB struct {
|
||||
func SomeHandler(c *gin.Context) {
|
||||
objA := formA{}
|
||||
objB := formB{}
|
||||
// This c.ShouldBind consumes c.Request.Body and it cannot be reused.
|
||||
// Calling 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.
|
||||
@@ -2293,12 +2304,64 @@ func main() {
|
||||
router := gin.Default()
|
||||
|
||||
router.GET("/cookie", func(c *gin.Context) {
|
||||
cookie, err := c.Cookie("gin_cookie")
|
||||
|
||||
if err != nil {
|
||||
cookie = "NotSet"
|
||||
// Using http.Cookie struct for more control
|
||||
c.SetCookieData(&http.Cookie{
|
||||
Name: "gin_cookie",
|
||||
Value: "test",
|
||||
Path: "/",
|
||||
Domain: "localhost",
|
||||
MaxAge: 3600,
|
||||
Secure: false,
|
||||
HttpOnly: true,
|
||||
// Additional fields available in http.Cookie
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
// Partitioned: true, // Available in newer Go versions
|
||||
})
|
||||
}
|
||||
|
||||
fmt.Printf("Cookie value: %s \n", cookie)
|
||||
})
|
||||
|
||||
router.Run()
|
||||
}
|
||||
```
|
||||
|
||||
You can also use the `SetCookieData` method, which accepts a `*http.Cookie` directly for more flexibility:
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"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)
|
||||
// Using http.Cookie struct for more control
|
||||
c.SetCookieData(&http.Cookie{
|
||||
Name: "gin_cookie",
|
||||
Value: "test",
|
||||
Path: "/",
|
||||
Domain: "localhost",
|
||||
MaxAge: 3600,
|
||||
Secure: false,
|
||||
HttpOnly: true,
|
||||
// Additional fields available in http.Cookie
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
// Partitioned: true, // Available in newer Go versions
|
||||
})
|
||||
}
|
||||
|
||||
fmt.Printf("Cookie value: %s \n", cookie)
|
||||
@@ -2308,6 +2371,65 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Custom json codec at runtime
|
||||
|
||||
Gin support custom json serialization and deserialization logic without using compile tags.
|
||||
|
||||
1. Define a custom struct implements the `json.Core` interface.
|
||||
|
||||
2. Before your engine starts, assign values to `json.API` using the custom struct.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/codec/json"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
var customConfig = jsoniter.Config{
|
||||
EscapeHTML: true,
|
||||
SortMapKeys: true,
|
||||
ValidateJsonRawMessage: true,
|
||||
}.Froze()
|
||||
|
||||
// implement api.JsonApi
|
||||
type customJsonApi struct {
|
||||
}
|
||||
|
||||
func (j customJsonApi) Marshal(v any) ([]byte, error) {
|
||||
return customConfig.Marshal(v)
|
||||
}
|
||||
|
||||
func (j customJsonApi) Unmarshal(data []byte, v any) error {
|
||||
return customConfig.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
func (j customJsonApi) MarshalIndent(v any, prefix, indent string) ([]byte, error) {
|
||||
return customConfig.MarshalIndent(v, prefix, indent)
|
||||
}
|
||||
|
||||
func (j customJsonApi) NewEncoder(writer io.Writer) json.Encoder {
|
||||
return customConfig.NewEncoder(writer)
|
||||
}
|
||||
|
||||
func (j customJsonApi) NewDecoder(reader io.Reader) json.Decoder {
|
||||
return customConfig.NewDecoder(reader)
|
||||
}
|
||||
|
||||
func main() {
|
||||
//Replace the default json api
|
||||
json.API = customJsonApi{}
|
||||
|
||||
//Start your gin engine
|
||||
router := gin.Default()
|
||||
router.Run(":8080")
|
||||
}
|
||||
```
|
||||
|
||||
## Don't trust all proxies
|
||||
|
||||
Gin lets you specify which headers to hold the real client IP (if any),
|
||||
@@ -2319,7 +2441,7 @@ or network CIDRs from where clients which their request headers related to clien
|
||||
IP can be trusted. They can be IPv4 addresses, IPv4 CIDRs, IPv6 addresses or
|
||||
IPv6 CIDRs.
|
||||
|
||||
**Attention:** Gin trust all proxies by default if you don't specify a trusted
|
||||
**Attention:** Gin trusts all proxies by default if you don't specify a trusted
|
||||
proxy using the function above, **this is NOT safe**. At the same time, if you don't
|
||||
use any proxy, you can disable this feature by using `Engine.SetTrustedProxies(nil)`,
|
||||
then `Context.ClientIP()` will return the remote address directly to avoid some
|
||||
|
||||
Reference in New Issue
Block a user