mirror of
https://github.com/gin-gonic/gin.git
synced 2026-07-06 10:21:17 +08:00
Compare commits
5 Commits
6281a1b6ed
...
0a2e4c0d08
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a2e4c0d08 | ||
|
|
58135f06cf | ||
|
|
a85ef5ce4d | ||
|
|
fb27ef26c2 | ||
|
|
ce6939ce40 |
2
.github/workflows/gin.yml
vendored
2
.github/workflows/gin.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
- name: Setup golangci-lint
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
version: v2.1.6
|
||||
version: v2.6
|
||||
args: --verbose
|
||||
test:
|
||||
needs: lint
|
||||
|
||||
@ -68,7 +68,6 @@ linters:
|
||||
- examples$
|
||||
formatters:
|
||||
enable:
|
||||
- gci
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
@ -80,7 +79,4 @@ formatters:
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
- gin.go
|
||||
|
||||
@ -18,9 +18,8 @@ func BenchmarkSliceValidationError(b *testing.B) {
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
if len(e.Error()) == 0 {
|
||||
b.Errorf("error")
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ type structFull struct {
|
||||
|
||||
func BenchmarkMapFormFull(b *testing.B) {
|
||||
var s structFull
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
err := mapForm(&s, form)
|
||||
if err != nil {
|
||||
b.Fatalf("Error on a form mapping")
|
||||
@ -54,7 +54,7 @@ type structName struct {
|
||||
|
||||
func BenchmarkMapFormName(b *testing.B) {
|
||||
var s structName
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
err := mapForm(&s, form)
|
||||
if err != nil {
|
||||
b.Fatalf("Error on a form mapping")
|
||||
|
||||
30
context.go
30
context.go
@ -830,41 +830,71 @@ func (c *Context) ShouldBind(obj any) error {
|
||||
}
|
||||
|
||||
// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// POST /user
|
||||
// Content-Type: application/json
|
||||
//
|
||||
// Request Body:
|
||||
// {
|
||||
// "name": "Manu",
|
||||
// "age": 20
|
||||
// }
|
||||
//
|
||||
// type User struct {
|
||||
// Name string `json:"name"`
|
||||
// Age int `json:"age"`
|
||||
// }
|
||||
//
|
||||
// var user User
|
||||
// if err := c.ShouldBindJSON(&user); err != nil {
|
||||
// c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
// return
|
||||
// }
|
||||
// c.JSON(http.StatusOK, user)
|
||||
func (c *Context) ShouldBindJSON(obj any) error {
|
||||
return c.ShouldBindWith(obj, binding.JSON)
|
||||
}
|
||||
|
||||
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
|
||||
// It works like ShouldBindJSON but binds the request body as XML data.
|
||||
func (c *Context) ShouldBindXML(obj any) error {
|
||||
return c.ShouldBindWith(obj, binding.XML)
|
||||
}
|
||||
|
||||
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
|
||||
// It works like ShouldBindJSON but binds query parameters from the URL.
|
||||
func (c *Context) ShouldBindQuery(obj any) error {
|
||||
return c.ShouldBindWith(obj, binding.Query)
|
||||
}
|
||||
|
||||
// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
|
||||
// It works like ShouldBindJSON but binds the request body as YAML data.
|
||||
func (c *Context) ShouldBindYAML(obj any) error {
|
||||
return c.ShouldBindWith(obj, binding.YAML)
|
||||
}
|
||||
|
||||
// ShouldBindTOML is a shortcut for c.ShouldBindWith(obj, binding.TOML).
|
||||
// It works like ShouldBindJSON but binds the request body as TOML data.
|
||||
func (c *Context) ShouldBindTOML(obj any) error {
|
||||
return c.ShouldBindWith(obj, binding.TOML)
|
||||
}
|
||||
|
||||
// ShouldBindPlain is a shortcut for c.ShouldBindWith(obj, binding.Plain).
|
||||
// It works like ShouldBindJSON but binds plain text data from the request body.
|
||||
func (c *Context) ShouldBindPlain(obj any) error {
|
||||
return c.ShouldBindWith(obj, binding.Plain)
|
||||
}
|
||||
|
||||
// ShouldBindHeader is a shortcut for c.ShouldBindWith(obj, binding.Header).
|
||||
// It works like ShouldBindJSON but binds values from HTTP headers.
|
||||
func (c *Context) ShouldBindHeader(obj any) error {
|
||||
return c.ShouldBindWith(obj, binding.Header)
|
||||
}
|
||||
|
||||
// ShouldBindUri binds the passed struct pointer using the specified binding engine.
|
||||
// It works like ShouldBindJSON but binds parameters from the URI.
|
||||
func (c *Context) ShouldBindUri(obj any) error {
|
||||
m := make(map[string][]string, len(c.Params))
|
||||
for _, v := range c.Params {
|
||||
|
||||
@ -292,7 +292,7 @@ func TestContextReset(t *testing.T) {
|
||||
assert.Empty(t, c.Errors.Errors())
|
||||
assert.Empty(t, c.Errors.ByType(ErrorTypeAny))
|
||||
assert.Empty(t, c.Params)
|
||||
assert.EqualValues(t, c.index, -1)
|
||||
assert.EqualValues(t, -1, c.index)
|
||||
assert.Equal(t, c.Writer.(*responseWriter), &c.writermem)
|
||||
}
|
||||
|
||||
@ -384,7 +384,7 @@ func TestContextSetGetValues(t *testing.T) {
|
||||
c.Set("intInterface", a)
|
||||
|
||||
assert.Exactly(t, "this is a string", c.MustGet("string").(string))
|
||||
assert.Exactly(t, c.MustGet("int32").(int32), int32(-42))
|
||||
assert.Exactly(t, int32(-42), c.MustGet("int32").(int32))
|
||||
assert.Exactly(t, int64(42424242424242), c.MustGet("int64").(int64))
|
||||
assert.Exactly(t, uint64(42), c.MustGet("uint64").(uint64))
|
||||
assert.InDelta(t, float32(4.2), c.MustGet("float32").(float32), 0.01)
|
||||
|
||||
24
docs/doc.md
24
docs/doc.md
@ -9,6 +9,7 @@
|
||||
- [Using GET, POST, PUT, PATCH, DELETE and OPTIONS](#using-get-post-put-patch-delete-and-options)
|
||||
- [Parameters in path](#parameters-in-path)
|
||||
- [Querystring parameters](#querystring-parameters)
|
||||
- [Retrieving the path of a registered handler](#retrieving-the-path-of-a-registered-handler)
|
||||
- [Multipart/Urlencoded Form](#multiparturlencoded-form)
|
||||
- [Another example: query + post form](#another-example-query--post-form)
|
||||
- [Map as querystring or postform parameters](#map-as-querystring-or-postform-parameters)
|
||||
@ -184,6 +185,29 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
### Retrieving the path of a registered handler
|
||||
|
||||
Once a handler is registered with the router, you can retrieve its path with the `PathFor` method:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// Creates a gin router with default middleware:
|
||||
// logger and recovery (crash-free) middleware
|
||||
router := gin.Default()
|
||||
|
||||
getUser := func(c *Context) {
|
||||
// Handle the GET request.
|
||||
}
|
||||
router.GET("/users/:name", getUser)
|
||||
path := router.PathFor(getUser, ":name", "gopher")
|
||||
|
||||
print(path) // Prints /users/gopher
|
||||
|
||||
router.Run()
|
||||
}
|
||||
router := gin.Default()
|
||||
```
|
||||
|
||||
### Multipart/Urlencoded Form
|
||||
|
||||
```go
|
||||
|
||||
66
gin.go
66
gin.go
@ -384,6 +384,24 @@ func (engine *Engine) Routes() (routes RoutesInfo) {
|
||||
return routes
|
||||
}
|
||||
|
||||
// Routes returns a slice of registered routes, including some useful information, such as:
|
||||
// the http method, path and the handler name.
|
||||
func (engine *Engine) Route(handler HandlerFunc) (route RouteInfo, ok bool) {
|
||||
handlerName := nameOfFunction(handler)
|
||||
routes := RoutesInfo{}
|
||||
for _, tree := range engine.trees {
|
||||
routes = iterate("", tree.method, routes, tree.root)
|
||||
|
||||
for _, route := range routes {
|
||||
if route.Handler == handlerName {
|
||||
return route, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RouteInfo{}, false
|
||||
}
|
||||
|
||||
func iterate(path, method string, routes RoutesInfo, root *node) RoutesInfo {
|
||||
path += root.path
|
||||
if len(root.handlers) > 0 {
|
||||
@ -659,6 +677,43 @@ func (engine *Engine) HandleContext(c *Context) {
|
||||
c.handlers = oldHandlers
|
||||
}
|
||||
|
||||
// PathFor returns the path registered for the specified handler function.
|
||||
// Route values are passed pair-wise as key value.
|
||||
func (engine *Engine) PathFor(handler HandlerFunc, values ...interface{}) string {
|
||||
route, ok := engine.Route(handler)
|
||||
|
||||
if !ok || len(route.Path) == 0 || len(values)%2 != 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
url := route.Path
|
||||
params := make(map[string]string)
|
||||
if len(values) > 0 {
|
||||
key := ""
|
||||
for k, v := range values {
|
||||
if k%2 == 0 {
|
||||
key = fmt.Sprint(v)
|
||||
} else {
|
||||
params[key] = fmt.Sprint(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
urls := strings.Split(url, "/")
|
||||
for _, v := range urls {
|
||||
if v == "" {
|
||||
continue
|
||||
}
|
||||
if v[0:1] == ":" {
|
||||
if u, ok := params[v]; ok {
|
||||
delete(params, v)
|
||||
url = strings.Replace(url, v, u, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return url + toQuerystring(params)
|
||||
}
|
||||
|
||||
func (engine *Engine) handleHTTPRequest(c *Context) {
|
||||
httpMethod := c.Request.Method
|
||||
rPath := c.Request.URL.Path
|
||||
@ -787,3 +842,14 @@ func redirectRequest(c *Context) {
|
||||
http.Redirect(c.Writer, req, rURL, code)
|
||||
c.writermem.WriteHeaderNow()
|
||||
}
|
||||
|
||||
func toQuerystring(params map[string]string) string {
|
||||
if len(params) == 0 {
|
||||
return ""
|
||||
}
|
||||
u := "?"
|
||||
for k, v := range params {
|
||||
u += k + "=" + v + "&"
|
||||
}
|
||||
return strings.TrimRight(u, "&")
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@ -261,10 +262,11 @@ func TestUnixSocket(t *testing.T) {
|
||||
|
||||
fmt.Fprint(c, "GET /example HTTP/1.0\r\n\r\n")
|
||||
scanner := bufio.NewScanner(c)
|
||||
var response string
|
||||
var responseBuilder strings.Builder
|
||||
for scanner.Scan() {
|
||||
response += scanner.Text()
|
||||
responseBuilder.WriteString(scanner.Text())
|
||||
}
|
||||
response := responseBuilder.String()
|
||||
assert.Contains(t, response, "HTTP/1.0 200", "should get a 200")
|
||||
assert.Contains(t, response, "it worked", "resp body should match")
|
||||
}
|
||||
@ -322,10 +324,11 @@ func TestFileDescriptor(t *testing.T) {
|
||||
|
||||
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
|
||||
scanner := bufio.NewScanner(c)
|
||||
var response string
|
||||
var responseBuilder strings.Builder
|
||||
for scanner.Scan() {
|
||||
response += scanner.Text()
|
||||
responseBuilder.WriteString(scanner.Text())
|
||||
}
|
||||
response := responseBuilder.String()
|
||||
assert.Contains(t, response, "HTTP/1.0 200", "should get a 200")
|
||||
assert.Contains(t, response, "it worked", "resp body should match")
|
||||
}
|
||||
@ -354,10 +357,11 @@ func TestListener(t *testing.T) {
|
||||
|
||||
fmt.Fprintf(c, "GET /example HTTP/1.0\r\n\r\n")
|
||||
scanner := bufio.NewScanner(c)
|
||||
var response string
|
||||
var responseBuilder strings.Builder
|
||||
for scanner.Scan() {
|
||||
response += scanner.Text()
|
||||
responseBuilder.WriteString(scanner.Text())
|
||||
}
|
||||
response := responseBuilder.String()
|
||||
assert.Contains(t, response, "HTTP/1.0 200", "should get a 200")
|
||||
assert.Contains(t, response, "it worked", "resp body should match")
|
||||
}
|
||||
|
||||
19
gin_test.go
19
gin_test.go
@ -825,6 +825,25 @@ func TestPrepareTrustedCIRDsWith(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathFor(t *testing.T) {
|
||||
r := New()
|
||||
|
||||
getIndex := func(c *Context) {}
|
||||
r.GET("/", getIndex)
|
||||
|
||||
users := r.Group("/users")
|
||||
|
||||
postUser := func(c *Context) {}
|
||||
users.POST("", postUser)
|
||||
|
||||
getUser := func(c *Context) {}
|
||||
users.GET("/:name", getUser)
|
||||
|
||||
assert.Equal(t, "/", r.PathFor(getIndex))
|
||||
assert.Equal(t, "/users", r.PathFor(postUser))
|
||||
assert.Equal(t, "/users/gopher", r.PathFor(getUser, ":name", "gopher"))
|
||||
}
|
||||
|
||||
func parseCIDR(cidr string) *net.IPNet {
|
||||
_, parsedCIDR, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
|
||||
@ -94,7 +94,7 @@ func TestPathCleanMallocs(t *testing.T) {
|
||||
func BenchmarkPathClean(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
for _, test := range cleanTests {
|
||||
cleanPath(test.path)
|
||||
}
|
||||
@ -134,10 +134,10 @@ func TestPathCleanLong(t *testing.T) {
|
||||
|
||||
func BenchmarkPathCleanLong(b *testing.B) {
|
||||
cleanTests := genLongPaths()
|
||||
b.ResetTimer()
|
||||
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
for _, test := range cleanTests {
|
||||
cleanPath(test.path)
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ func init() {
|
||||
}
|
||||
|
||||
func BenchmarkParseAccept(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for b.Loop() {
|
||||
parseAccept("text/html , application/xhtml+xml,application/xml;q=0.9, */* ;q=0.8")
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user