mirror of
https://github.com/gin-gonic/gin.git
synced 2026-06-06 20:18:19 +08:00
Compare commits
6 Commits
fc178c9fa0
...
d9690e0333
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9690e0333 | ||
|
|
92006a5512 | ||
|
|
8ca6e308c6 | ||
|
|
7ae74c5272 | ||
|
|
9a76687101 | ||
|
|
e0109c70d9 |
@ -830,7 +830,11 @@ func TestUriBinding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
var not NotSupportStruct
|
var not NotSupportStruct
|
||||||
require.Error(t, b.BindUri(m, ¬))
|
require.Error(t, b.BindUri(m, ¬))
|
||||||
assert.Equal(t, map[string]any(nil), not.Name)
|
require.Error(t, b.BindUri(m, ¬))
|
||||||
|
// Check that if the map is not nil, it is empty (json-iterator may allocate map before error)
|
||||||
|
if not.Name != nil {
|
||||||
|
assert.Empty(t, not.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUriInnerBinding(t *testing.T) {
|
func TestUriInnerBinding(t *testing.T) {
|
||||||
|
|||||||
@ -2138,7 +2138,7 @@ func TestContextBindRequestTooLarge(t *testing.T) {
|
|||||||
// https://github.com/goccy/go-json/issues/485
|
// https://github.com/goccy/go-json/issues/485
|
||||||
var expectedCode int
|
var expectedCode int
|
||||||
switch json.Package {
|
switch json.Package {
|
||||||
case "github.com/goccy/go-json":
|
case "github.com/goccy/go-json", "github.com/json-iterator/go":
|
||||||
expectedCode = http.StatusBadRequest
|
expectedCode = http.StatusBadRequest
|
||||||
default:
|
default:
|
||||||
expectedCode = http.StatusRequestEntityTooLarge
|
expectedCode = http.StatusRequestEntityTooLarge
|
||||||
|
|||||||
26
examples/json-iterator/README.md
Normal file
26
examples/json-iterator/README.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# JSON Iterator Example
|
||||||
|
|
||||||
|
This example demonstrates how to integrate [json-iterator/go](https://github.com/json-iterator/go) with Gin to replace the default encoding/json for better performance.
|
||||||
|
|
||||||
|
## How it works
|
||||||
|
|
||||||
|
Gin supports custom JSON serialization and deserialization logic via the `json.API` variable. By implementing the `json.Core` interface (which includes `Marshal`, `Unmarshal`, `NewEncoder`, `NewDecoder` etc.), we can swap out the underlying JSON engine.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Define your custom configuration using `jsoniter.Config`.
|
||||||
|
2. Implement the `json.Core` interface wrappers.
|
||||||
|
3. Assign your custom implementation to `json.API` before creating the Gin engine.
|
||||||
|
|
||||||
|
## Run the example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
Test it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:8080/ping
|
||||||
|
# Output: {"message":"pong"}
|
||||||
|
```
|
||||||
45
examples/json-iterator/json_iterator_test.go
Normal file
45
examples/json-iterator/json_iterator_test.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
ginjson "github.com/gin-gonic/gin/codec/json"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJsonIterator(t *testing.T) {
|
||||||
|
// Restore default json api after test
|
||||||
|
originalAPI := ginjson.API
|
||||||
|
defer func() {
|
||||||
|
ginjson.API = originalAPI
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Use custom json api
|
||||||
|
ginjson.API = customJsonApi{}
|
||||||
|
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
r := gin.New()
|
||||||
|
r.GET("/test", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"hello": "world",
|
||||||
|
"foo": "bar",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
req, _ := http.NewRequest("GET", "/test", nil)
|
||||||
|
r.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
// Verify JSON response
|
||||||
|
var response map[string]string
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "world", response["hello"])
|
||||||
|
assert.Equal(t, "bar", response["foo"])
|
||||||
|
}
|
||||||
55
examples/json-iterator/main.go
Normal file
55
examples/json-iterator/main.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"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()
|
||||||
|
|
||||||
|
// customJsonApi 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 with json-iterator
|
||||||
|
json.API = customJsonApi{}
|
||||||
|
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
r.GET("/ping", func(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"message": "pong",
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
r.Run(":8080")
|
||||||
|
}
|
||||||
@ -64,7 +64,20 @@ func testRequest(t *testing.T, params ...string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRunEmpty(t *testing.T) {
|
func TestRunEmpty(t *testing.T) {
|
||||||
os.Setenv("PORT", "")
|
// Listen on a random available port to avoid conflicts
|
||||||
|
l, err := net.Listen("tcp", "localhost:0")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer l.Close()
|
||||||
|
addr := l.Addr().String()
|
||||||
|
_, port, err := net.SplitHostPort(addr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Close the listener so router.Run() can bind to it (there's a small race here, but better than hardcoded 8080)
|
||||||
|
// Actually, router.Run() calls http.ListenAndServe which creates its own listener.
|
||||||
|
// If we close 'l', 'router.Run' can pick it up.
|
||||||
|
l.Close()
|
||||||
|
|
||||||
|
os.Setenv("PORT", port)
|
||||||
router := New()
|
router := New()
|
||||||
go func() {
|
go func() {
|
||||||
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||||
@ -72,11 +85,11 @@ func TestRunEmpty(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Wait for server to be ready with exponential backoff
|
// Wait for server to be ready with exponential backoff
|
||||||
err := waitForServerReady("http://localhost:8080/example", 10)
|
err = waitForServerReady("http://"+addr+"/example", 10)
|
||||||
require.NoError(t, err, "server should start successfully")
|
require.NoError(t, err, "server should start successfully")
|
||||||
|
|
||||||
require.Error(t, router.Run(":8080"))
|
require.Error(t, router.Run(":"+port))
|
||||||
testRequest(t, "http://localhost:8080/example")
|
testRequest(t, "http://"+addr+"/example")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBadTrustedCIDRs(t *testing.T) {
|
func TestBadTrustedCIDRs(t *testing.T) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user