forked from mirror/gin
Compare commits
14 Commits
a0744ba446
...
68582537f5
Author | SHA1 | Date |
---|---|---|
re | 68582537f5 | |
dependabot[bot] | 3010cbd7f4 | |
dependabot[bot] | 47ae6ee386 | |
Kristian Svalland | 8eb5f832ba | |
apriil15 | c58e0d59ca | |
dependabot[bot] | 79a61b9032 | |
dependabot[bot] | 7626361587 | |
thinkerou | c9b27249fb | |
youngxhui | 7d8fc1563b | |
Alireza (Pure) | 41f2669ebc | |
thinkerou | 82e1c53cc0 | |
dependabot[bot] | 8659ab573c | |
mstmdev | e868fd1d3d | |
lgbgbl | 297b664cf8 |
475
README.md
475
README.md
|
@ -12,106 +12,58 @@
|
||||||
[![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://git.internal/re/gin/releases)
|
[![Release](https://img.shields.io/github/release/gin-gonic/gin.svg?style=flat-square)](https://git.internal/re/gin/releases)
|
||||||
[![TODOs](https://badgen.net/https/api.tickgit.com/badgen/git.internal/re/gin)](https://www.tickgit.com/browse?repo=git.internal/re/gin)
|
[![TODOs](https://badgen.net/https/api.tickgit.com/badgen/git.internal/re/gin)](https://www.tickgit.com/browse?repo=git.internal/re/gin)
|
||||||
|
|
||||||
Gin is a web framework written in Go (Golang). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
Gin is a web framework written in [Go](https://go.dev/). It features a martini-like API with performance that is up to 40 times faster thanks to [httprouter](https://github.com/julienschmidt/httprouter). If you need performance and good productivity, you will love Gin.
|
||||||
|
|
||||||
## Contents
|
**The key features of Gin are:**
|
||||||
|
|
||||||
- [Gin Web Framework](#gin-web-framework)
|
- Zero allocation router
|
||||||
- [Contents](#contents)
|
- Fast
|
||||||
- [Installation](#installation)
|
- Middleware support
|
||||||
- [Quick start](#quick-start)
|
- Crash-free
|
||||||
- [Benchmarks](#benchmarks)
|
- JSON validation
|
||||||
- [Gin v1. stable](#gin-v1-stable)
|
- Routes grouping
|
||||||
- [Build with json replacement](#build-with-json-replacement)
|
- Error management
|
||||||
- [Build without `MsgPack` rendering feature](#build-without-msgpack-rendering-feature)
|
- Rendering built-in
|
||||||
- [API Examples](#api-examples)
|
- Extendable
|
||||||
- [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)
|
|
||||||
- [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)
|
|
||||||
- [Upload files](#upload-files)
|
|
||||||
- [Single file](#single-file)
|
|
||||||
- [Multiple files](#multiple-files)
|
|
||||||
- [Grouping routes](#grouping-routes)
|
|
||||||
- [Blank Gin without middleware by default](#blank-gin-without-middleware-by-default)
|
|
||||||
- [Using middleware](#using-middleware)
|
|
||||||
- [Custom Recovery behavior](#custom-recovery-behavior)
|
|
||||||
- [How to write log file](#how-to-write-log-file)
|
|
||||||
- [Custom Log Format](#custom-log-format)
|
|
||||||
- [Controlling Log output coloring](#controlling-log-output-coloring)
|
|
||||||
- [Model binding and validation](#model-binding-and-validation)
|
|
||||||
- [Custom Validators](#custom-validators)
|
|
||||||
- [Only Bind Query String](#only-bind-query-string)
|
|
||||||
- [Bind Query String or Post Data](#bind-query-string-or-post-data)
|
|
||||||
- [Bind Uri](#bind-uri)
|
|
||||||
- [Bind Header](#bind-header)
|
|
||||||
- [Bind HTML checkboxes](#bind-html-checkboxes)
|
|
||||||
- [Multipart/Urlencoded binding](#multiparturlencoded-binding)
|
|
||||||
- [XML, JSON, YAML, TOML and ProtoBuf rendering](#xml-json-yaml-toml-and-protobuf-rendering)
|
|
||||||
- [SecureJSON](#securejson)
|
|
||||||
- [JSONP](#jsonp)
|
|
||||||
- [AsciiJSON](#asciijson)
|
|
||||||
- [PureJSON](#purejson)
|
|
||||||
- [Serving static files](#serving-static-files)
|
|
||||||
- [Serving data from file](#serving-data-from-file)
|
|
||||||
- [Serving data from reader](#serving-data-from-reader)
|
|
||||||
- [HTML rendering](#html-rendering)
|
|
||||||
- [Custom Template renderer](#custom-template-renderer)
|
|
||||||
- [Custom Delimiters](#custom-delimiters)
|
|
||||||
- [Custom Template Funcs](#custom-template-funcs)
|
|
||||||
- [Multitemplate](#multitemplate)
|
|
||||||
- [Redirects](#redirects)
|
|
||||||
- [Custom Middleware](#custom-middleware)
|
|
||||||
- [Using BasicAuth() middleware](#using-basicauth-middleware)
|
|
||||||
- [Goroutines inside a middleware](#goroutines-inside-a-middleware)
|
|
||||||
- [Custom HTTP configuration](#custom-http-configuration)
|
|
||||||
- [Support Let's Encrypt](#support-lets-encrypt)
|
|
||||||
- [Run multiple service using Gin](#run-multiple-service-using-gin)
|
|
||||||
- [Graceful shutdown or restart](#graceful-shutdown-or-restart)
|
|
||||||
- [Third-party packages](#third-party-packages)
|
|
||||||
- [Manually](#manually)
|
|
||||||
- [Build a single binary with templates](#build-a-single-binary-with-templates)
|
|
||||||
- [Bind form-data request with custom struct](#bind-form-data-request-with-custom-struct)
|
|
||||||
- [Try to bind body into different structs](#try-to-bind-body-into-different-structs)
|
|
||||||
- [Bind form-data request with custom struct and custom tag](#bind-form-data-request-with-custom-struct-and-custom-tag)
|
|
||||||
- [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)
|
|
||||||
- [Don't trust all proxies](#dont-trust-all-proxies)
|
|
||||||
- [Testing](#testing)
|
|
||||||
- [Users](#users)
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
To install Gin package, you need to install Go and set your Go workspace first.
|
## Getting started
|
||||||
|
|
||||||
1. You first need [Go](https://go.dev/) installed (**version 1.16+ is required**), then you can use the below Go command to install Gin.
|
### Prerequisites
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
```sh
|
```sh
|
||||||
go get -u git.internal/re/gin
|
go get -u git.internal/re/gin
|
||||||
```
|
```
|
||||||
|
=======
|
||||||
|
- **[Go](https://go.dev/)**: ~~any one of the **three latest major** [releases](https://go.dev/doc/devel/release)~~ (now version **1.16+** is required).
|
||||||
|
>>>>>>> upstream/master
|
||||||
|
|
||||||
2. Import it in your code:
|
### Getting Gin
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
```go
|
```go
|
||||||
import "git.internal/re/gin"
|
import "git.internal/re/gin"
|
||||||
|
=======
|
||||||
|
With [Go module](https://github.com/golang/go/wiki/Modules) support, simply add the following import
|
||||||
|
|
||||||
|
```
|
||||||
|
import "github.com/gin-gonic/gin"
|
||||||
|
>>>>>>> upstream/master
|
||||||
```
|
```
|
||||||
|
|
||||||
3. (Optional) Import `net/http`. This is required for example if using constants such as `http.StatusOK`.
|
to your code, and then `go [build|run|test]` will automatically fetch the necessary dependencies.
|
||||||
|
|
||||||
```go
|
Otherwise, run the following Go command to install the `gin` package:
|
||||||
import "net/http"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick start
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# assume the following codes in example.go file
|
$ go get -u github.com/gin-gonic/gin
|
||||||
$ cat example.go
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Running Gin
|
||||||
|
|
||||||
|
First you need to import Gin package for using Gin, one simplest example likes the follow `example.go`:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
@ -132,16 +84,48 @@ func main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
And use the Go command to run the demo:
|
||||||
|
|
||||||
```
|
```
|
||||||
# run example.go and visit 0.0.0.0:8080/ping (for windows "localhost:8080/ping") on browser
|
# run example.go and visit 0.0.0.0:8080/ping on browser
|
||||||
$ go run example.go
|
$ go run example.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Learn more examples
|
||||||
|
|
||||||
|
#### Quick Start
|
||||||
|
|
||||||
|
Learn and practice more examples, please read the [Gin Quick Start](docs/doc.md) which includes API examples and builds tag.
|
||||||
|
|
||||||
|
#### Examples
|
||||||
|
|
||||||
|
A number of ready-to-run examples demonstrating various use cases of Gin on the [Gin examples](https://github.com/gin-gonic/examples) repository.
|
||||||
|
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
See [API documentation and descriptions](https://godoc.org/github.com/gin-gonic/gin) for package.
|
||||||
|
|
||||||
|
All documentation is available on the Gin website.
|
||||||
|
|
||||||
|
- [English](https://gin-gonic.com/docs/)
|
||||||
|
- [简体中文](https://gin-gonic.com/zh-cn/docs/)
|
||||||
|
- [繁體中文](https://gin-gonic.com/zh-tw/docs/)
|
||||||
|
- [日本語](https://gin-gonic.com/ja/docs/)
|
||||||
|
- [Español](https://gin-gonic.com/es/docs/)
|
||||||
|
- [한국어](https://gin-gonic.com/ko-kr/docs/)
|
||||||
|
- [Turkish](https://gin-gonic.com/tr/docs/)
|
||||||
|
- [Persian](https://gin-gonic.com/fa/docs/)
|
||||||
|
|
||||||
|
### Articles about Gin
|
||||||
|
|
||||||
|
A curated list of awesome Gin framework.
|
||||||
|
|
||||||
|
- [Tutorial: Developing a RESTful API with Go and Gin](https://go.dev/doc/tutorial/web-service-gin)
|
||||||
|
|
||||||
## Benchmarks
|
## Benchmarks
|
||||||
|
|
||||||
Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter)
|
Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httprouter), [see all benchmarks details](/BENCHMARKS.md).
|
||||||
|
|
||||||
[See all benchmarks](/BENCHMARKS.md)
|
|
||||||
|
|
||||||
| Benchmark name | (1) | (2) | (3) | (4) |
|
| Benchmark name | (1) | (2) | (3) | (4) |
|
||||||
| ------------------------------ | ---------:| ---------------:| ------------:| ---------------:|
|
| ------------------------------ | ---------:| ---------------:| ------------:| ---------------:|
|
||||||
|
@ -181,6 +165,7 @@ Gin uses a custom version of [HttpRouter](https://github.com/julienschmidt/httpr
|
||||||
- (3): Heap Memory (B/op), lower is better
|
- (3): Heap Memory (B/op), lower is better
|
||||||
- (4): Average Allocations per Repetition (allocs/op), lower is better
|
- (4): Average Allocations per Repetition (allocs/op), lower is better
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
## Gin v1. stable
|
## Gin v1. stable
|
||||||
|
|
||||||
- [x] Zero allocation router.
|
- [x] Zero allocation router.
|
||||||
|
@ -389,319 +374,32 @@ ids: map[b:hello a:1234]; names: map[second:tianou first:thinkerou]
|
||||||
References issue [#774](https://git.internal/re/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single).
|
References issue [#774](https://git.internal/re/gin/issues/774) and detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/single).
|
||||||
|
|
||||||
`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://git.internal/re/gin/issues/1693)
|
`file.Filename` **SHOULD NOT** be trusted. See [`Content-Disposition` on MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition#Directives) and [#1693](https://git.internal/re/gin/issues/1693)
|
||||||
|
=======
|
||||||
|
|
||||||
> The filename is always optional and must not be used blindly by the application: path information should be stripped, and conversion to the server file system rules should be done.
|
## Middlewares
|
||||||
|
>>>>>>> upstream/master
|
||||||
|
|
||||||
```go
|
You can find many useful Gin middlewares at [gin-contrib](https://github.com/gin-contrib).
|
||||||
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))
|
## Users
|
||||||
})
|
|
||||||
router.Run(":8080")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
How to `curl`:
|
Awesome project lists using [Gin](https://github.com/gin-gonic/gin) web framework.
|
||||||
|
|
||||||
```bash
|
* [gorush](https://github.com/appleboy/gorush): A push notification server written in Go.
|
||||||
curl -X POST http://localhost:8080/upload \
|
* [fnproject](https://github.com/fnproject/fn): The container native, cloud agnostic serverless platform.
|
||||||
-F "file=@/Users/appleboy/test.zip" \
|
* [photoprism](https://github.com/photoprism/photoprism): Personal photo management powered by Go and Google TensorFlow.
|
||||||
-H "Content-Type: multipart/form-data"
|
* [lura](https://github.com/luraproject/lura): Ultra performant API Gateway with middlewares.
|
||||||
```
|
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
||||||
|
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
|
||||||
|
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
|
||||||
|
|
||||||
#### Multiple files
|
|
||||||
|
|
||||||
See the detail [example code](https://github.com/gin-gonic/examples/tree/master/upload-file/multiple).
|
## Contributing
|
||||||
|
|
||||||
```go
|
Gin is the work of hundreds of contributors. We appreciate your help!
|
||||||
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")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
How to `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"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Grouping routes
|
|
||||||
|
|
||||||
```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")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Blank Gin without middleware by default
|
|
||||||
|
|
||||||
Use
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := gin.New()
|
|
||||||
```
|
|
||||||
|
|
||||||
instead of
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Default With the Logger and Recovery middleware already attached
|
|
||||||
r := gin.Default()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using middleware
|
|
||||||
|
|
||||||
```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")
|
|
||||||
// visit 0.0.0.0:8080/testing/analytics
|
|
||||||
testing.GET("/analytics", analyticsEndpoint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen and serve on 0.0.0.0:8080
|
|
||||||
r.Run(":8080")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Recovery behavior
|
|
||||||
|
|
||||||
```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.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)
|
|
||||||
}))
|
|
||||||
|
|
||||||
r.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")
|
|
||||||
})
|
|
||||||
|
|
||||||
r.GET("/", func(c *gin.Context) {
|
|
||||||
c.String(http.StatusOK, "ohai")
|
|
||||||
})
|
|
||||||
|
|
||||||
// Listen and serve on 0.0.0.0:8080
|
|
||||||
r.Run(":8080")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### How to write log file
|
|
||||||
|
|
||||||
```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(http.StatusOK, "pong")
|
|
||||||
})
|
|
||||||
|
|
||||||
router.Run(":8080")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Custom Log Format
|
|
||||||
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
router := gin.New()
|
|
||||||
|
|
||||||
// LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
|
|
||||||
// By default gin.DefaultWriter = os.Stdout
|
|
||||||
router.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,
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
router.Use(gin.Recovery())
|
|
||||||
|
|
||||||
router.GET("/ping", func(c *gin.Context) {
|
|
||||||
c.String(http.StatusOK, "pong")
|
|
||||||
})
|
|
||||||
|
|
||||||
router.Run(":8080")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Sample Output
|
|
||||||
|
|
||||||
```sh
|
|
||||||
::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" "
|
|
||||||
```
|
|
||||||
|
|
||||||
### Controlling Log output coloring
|
|
||||||
|
|
||||||
By default, logs output on console should be colorized depending on the detected TTY.
|
|
||||||
|
|
||||||
Never colorize logs:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
// Disable log's color
|
|
||||||
gin.DisableConsoleColor()
|
|
||||||
|
|
||||||
// Creates a gin router with default middleware:
|
|
||||||
// logger and recovery (crash-free) middleware
|
|
||||||
router := gin.Default()
|
|
||||||
|
|
||||||
router.GET("/ping", func(c *gin.Context) {
|
|
||||||
c.String(http.StatusOK, "pong")
|
|
||||||
})
|
|
||||||
|
|
||||||
router.Run(":8080")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Always colorize logs:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
// Force log's color
|
|
||||||
gin.ForceConsoleColor()
|
|
||||||
|
|
||||||
// Creates a gin router with default middleware:
|
|
||||||
// logger and recovery (crash-free) middleware
|
|
||||||
router := gin.Default()
|
|
||||||
|
|
||||||
router.GET("/ping", func(c *gin.Context) {
|
|
||||||
c.String(http.StatusOK, "pong")
|
|
||||||
})
|
|
||||||
|
|
||||||
router.Run(":8080")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Model binding and validation
|
|
||||||
|
|
||||||
To bind a request body into a type, use model binding. We currently support binding of JSON, XML, YAML, TOML and standard form values (foo=bar&boo=baz).
|
|
||||||
|
|
||||||
Gin uses [**go-playground/validator/v10**](https://github.com/go-playground/validator) for validation. Check the full docs on tags usage [here](https://pkg.go.dev/github.com/go-playground/validator#hdr-Baked_In_Validators_and_Tags).
|
|
||||||
|
|
||||||
Note that you need to set the corresponding binding tag on all fields you want to bind. For example, when binding from JSON, set `json:"fieldname"`.
|
|
||||||
|
|
||||||
Also, Gin provides two sets of methods for binding:
|
|
||||||
|
|
||||||
- **Type** - Must bind
|
|
||||||
- **Methods** - `Bind`, `BindJSON`, `BindXML`, `BindQuery`, `BindYAML`, `BindHeader`, `BindTOML`
|
|
||||||
- **Behavior** - These methods use `MustBindWith` under the hood. If there is a binding error, the request is aborted with `c.AbortWithError(400, err).SetType(ErrorTypeBind)`. This sets the response status code to 400 and the `Content-Type` header is set to `text/plain; charset=utf-8`. Note that if you try to set the response code after this, it will result in a warning `[GIN-debug] [WARNING] Headers were already written. Wanted to override status code 400 with 422`. If you wish to have greater control over the behavior, consider using the `ShouldBind` equivalent method.
|
|
||||||
- **Type** - Should bind
|
|
||||||
- **Methods** - `ShouldBind`, `ShouldBindJSON`, `ShouldBindXML`, `ShouldBindQuery`, `ShouldBindYAML`, `ShouldBindHeader`, `ShouldBindTOML`,
|
|
||||||
- **Behavior** - These methods use `ShouldBindWith` under the hood. If there is a binding error, the error is returned and it is the developer's responsibility to handle the request and error appropriately.
|
|
||||||
|
|
||||||
When using the Bind-method, Gin tries to infer the binder depending on the Content-Type header. If you are sure what you are binding, you can use `MustBindWith` or `ShouldBindWith`.
|
|
||||||
|
|
||||||
You can also specify that specific fields are required. If a field is decorated with `binding:"required"` and has an empty value when binding, an error will be returned.
|
|
||||||
|
|
||||||
```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"`
|
|
||||||
}
|
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
func main() {
|
func main() {
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
|
@ -2380,3 +2078,6 @@ Awesome project lists using [Gin](https://git.internal/re/gin) web framework.
|
||||||
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
* [picfit](https://github.com/thoas/picfit): An image resizing server written in Go.
|
||||||
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
|
* [brigade](https://github.com/brigadecore/brigade): Event-based Scripting for Kubernetes.
|
||||||
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
|
* [dkron](https://github.com/distribworks/dkron): Distributed, fault tolerant job scheduling system.
|
||||||
|
=======
|
||||||
|
Please see [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the contribution workflow.
|
||||||
|
>>>>>>> upstream/master
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type yamlBinding struct{}
|
type yamlBinding struct{}
|
||||||
|
|
|
@ -248,20 +248,20 @@ func (c *Context) Error(err error) *Error {
|
||||||
// It also lazy initializes c.Keys if it was not used previously.
|
// It also lazy initializes c.Keys if it was not used previously.
|
||||||
func (c *Context) Set(key string, value any) {
|
func (c *Context) Set(key string, value any) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
if c.Keys == nil {
|
if c.Keys == nil {
|
||||||
c.Keys = make(map[string]any)
|
c.Keys = make(map[string]any)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Keys[key] = value
|
c.Keys[key] = value
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns the value for the given key, ie: (value, true).
|
// Get returns the value for the given key, ie: (value, true).
|
||||||
// If the value does not exist it returns (nil, false)
|
// If the value does not exist it returns (nil, false)
|
||||||
func (c *Context) Get(key string) (value any, exists bool) {
|
func (c *Context) Get(key string) (value any, exists bool) {
|
||||||
c.mu.RLock()
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
value, exists = c.Keys[key]
|
value, exists = c.Keys[key]
|
||||||
c.mu.RUnlock()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
17
go.mod
17
go.mod
|
@ -3,18 +3,18 @@ module git.internal/re/gin
|
||||||
go 1.18
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.6.0
|
github.com/bytedance/sonic v1.6.1
|
||||||
github.com/gin-contrib/sse v0.1.0
|
github.com/gin-contrib/sse v0.1.0
|
||||||
github.com/go-playground/validator/v10 v10.11.1
|
github.com/go-playground/validator/v10 v10.11.1
|
||||||
github.com/goccy/go-json v0.9.11
|
github.com/goccy/go-json v0.10.0
|
||||||
github.com/json-iterator/go v1.1.12
|
github.com/json-iterator/go v1.1.12
|
||||||
github.com/mattn/go-isatty v0.0.16
|
github.com/mattn/go-isatty v0.0.17
|
||||||
github.com/pelletier/go-toml/v2 v2.0.6
|
github.com/pelletier/go-toml/v2 v2.0.6
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
github.com/ugorji/go/codec v1.2.7
|
github.com/ugorji/go/codec v1.2.8
|
||||||
golang.org/x/net v0.4.0
|
golang.org/x/net v0.5.0
|
||||||
google.golang.org/protobuf v1.28.1
|
google.golang.org/protobuf v1.28.1
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -30,7 +30,6 @@ require (
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect
|
golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 // indirect
|
||||||
golang.org/x/sys v0.3.0 // indirect
|
golang.org/x/sys v0.4.0 // indirect
|
||||||
golang.org/x/text v0.5.0 // indirect
|
golang.org/x/text v0.6.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
31
go.sum
31
go.sum
|
@ -1,6 +1,6 @@
|
||||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
github.com/bytedance/sonic v1.6.0 h1:j90DM/Ss1bmySEQYL2U4jRsUjJ+chASzCCZYxohJR60=
|
github.com/bytedance/sonic v1.6.1 h1:HEyWqlvEh95R/rMg5Mh6jDx5Zt35MG24QWzpHMVuan0=
|
||||||
github.com/bytedance/sonic v1.6.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
github.com/bytedance/sonic v1.6.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
@ -18,8 +18,8 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j
|
||||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||||
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
@ -39,8 +39,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
@ -65,29 +65,28 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
github.com/ugorji/go/codec v1.2.8 h1:sgBJS6COt0b/P40VouWKdseidkDgHxYGm0SAglUHfP0=
|
||||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
|
||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU=
|
golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15 h1:GVfVkciLYxn5mY5EncwAe0SXUn9Rm81rRkZ0TTmn/cU=
|
||||||
golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
golang.org/x/arch v0.0.0-20220412001346-fc48f9fe4c15/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
@ -99,8 +98,6 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|
|
@ -62,7 +62,9 @@ func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
|
||||||
if ne, ok := err.(*net.OpError); ok {
|
if ne, ok := err.(*net.OpError); ok {
|
||||||
var se *os.SyscallError
|
var se *os.SyscallError
|
||||||
if errors.As(ne, &se) {
|
if errors.As(ne, &se) {
|
||||||
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
|
seStr := strings.ToLower(se.Error())
|
||||||
|
if strings.Contains(seStr, "broken pipe") ||
|
||||||
|
strings.Contains(seStr, "connection reset by peer") {
|
||||||
brokenPipe = true
|
brokenPipe = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
"errors"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -237,7 +238,7 @@ b:
|
||||||
|
|
||||||
err := (YAML{data}).Render(w)
|
err := (YAML{data}).Render(w)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String())
|
assert.Equal(t, "|4-\n a : Easy!\n b:\n \tc: 2\n \td: [3, 4]\n \t\n", w.Body.String())
|
||||||
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,6 +255,27 @@ func TestRenderYAMLFail(t *testing.T) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderTOML(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
data := map[string]any{
|
||||||
|
"foo": "bar",
|
||||||
|
"html": "<b>",
|
||||||
|
}
|
||||||
|
(TOML{data}).WriteContentType(w)
|
||||||
|
assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
err := (TOML{data}).Render(w)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "foo = 'bar'\nhtml = '<b>'\n", w.Body.String())
|
||||||
|
assert.Equal(t, "application/toml; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderTOMLFail(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
err := (TOML{net.IPv4bcast}).Render(w)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
// test Protobuf rendering
|
// test Protobuf rendering
|
||||||
func TestRenderProtoBuf(t *testing.T) {
|
func TestRenderProtoBuf(t *testing.T) {
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
|
@ -7,7 +7,7 @@ package render
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// YAML contains the given interface object.
|
// YAML contains the given interface object.
|
||||||
|
|
|
@ -61,6 +61,7 @@ func (w *responseWriter) WriteHeader(code int) {
|
||||||
if code > 0 && w.status != code {
|
if code > 0 && w.status != code {
|
||||||
if w.Written() {
|
if w.Written() {
|
||||||
debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
|
debugPrint("[WARNING] Headers were already written. Wanted to override status code %d with %d", w.status, code)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
w.status = code
|
w.status = code
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,3 +132,21 @@ func TestResponseWriterFlush(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
|
assert.Equal(t, http.StatusInternalServerError, resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestResponseWriterStatusCode(t *testing.T) {
|
||||||
|
testWriter := httptest.NewRecorder()
|
||||||
|
writer := &responseWriter{}
|
||||||
|
writer.reset(testWriter)
|
||||||
|
w := ResponseWriter(writer)
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
w.WriteHeaderNow()
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Status())
|
||||||
|
assert.True(t, w.Written())
|
||||||
|
|
||||||
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
|
|
||||||
|
// status must be 200 although we tried to change it
|
||||||
|
assert.Equal(t, http.StatusOK, w.Status())
|
||||||
|
}
|
||||||
|
|
|
@ -670,3 +670,22 @@ func TestRouteContextHoldsFullPath(t *testing.T) {
|
||||||
w := PerformRequest(router, http.MethodGet, "/not-found")
|
w := PerformRequest(router, http.MethodGet, "/not-found")
|
||||||
assert.Equal(t, http.StatusNotFound, w.Code)
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEngineHandleMethodNotAllowedCornerCase(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
r.HandleMethodNotAllowed = true
|
||||||
|
|
||||||
|
base := r.Group("base")
|
||||||
|
base.GET("/metrics", handlerTest1)
|
||||||
|
|
||||||
|
v1 := base.Group("v1")
|
||||||
|
|
||||||
|
v1.GET("/:id/devices", handlerTest1)
|
||||||
|
v1.GET("/user/:id/groups", handlerTest1)
|
||||||
|
|
||||||
|
v1.GET("/orgs/:id", handlerTest1)
|
||||||
|
v1.DELETE("/orgs/:id", handlerTest1)
|
||||||
|
|
||||||
|
w := PerformRequest(r, "GET", "/base/v1/user/groups")
|
||||||
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
}
|
||||||
|
|
18
tree.go
18
tree.go
|
@ -459,9 +459,9 @@ walk: // Outer loop for walking the tree
|
||||||
// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
|
// If the path at the end of the loop is not equal to '/' and the current node has no child nodes
|
||||||
// the current node needs to roll back to last valid skippedNode
|
// the current node needs to roll back to last valid skippedNode
|
||||||
if path != "/" {
|
if path != "/" {
|
||||||
for l := len(*skippedNodes); l > 0; {
|
for length := len(*skippedNodes); length > 0; length-- {
|
||||||
skippedNode := (*skippedNodes)[l-1]
|
skippedNode := (*skippedNodes)[length-1]
|
||||||
*skippedNodes = (*skippedNodes)[:l-1]
|
*skippedNodes = (*skippedNodes)[:length-1]
|
||||||
if strings.HasSuffix(skippedNode.path, path) {
|
if strings.HasSuffix(skippedNode.path, path) {
|
||||||
path = skippedNode.path
|
path = skippedNode.path
|
||||||
n = skippedNode.node
|
n = skippedNode.node
|
||||||
|
@ -576,9 +576,9 @@ walk: // Outer loop for walking the tree
|
||||||
// If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node
|
// If the current path does not equal '/' and the node does not have a registered handle and the most recently matched node has a child node
|
||||||
// the current node needs to roll back to last valid skippedNode
|
// the current node needs to roll back to last valid skippedNode
|
||||||
if n.handlers == nil && path != "/" {
|
if n.handlers == nil && path != "/" {
|
||||||
for l := len(*skippedNodes); l > 0; {
|
for length := len(*skippedNodes); length > 0; length-- {
|
||||||
skippedNode := (*skippedNodes)[l-1]
|
skippedNode := (*skippedNodes)[length-1]
|
||||||
*skippedNodes = (*skippedNodes)[:l-1]
|
*skippedNodes = (*skippedNodes)[:length-1]
|
||||||
if strings.HasSuffix(skippedNode.path, path) {
|
if strings.HasSuffix(skippedNode.path, path) {
|
||||||
path = skippedNode.path
|
path = skippedNode.path
|
||||||
n = skippedNode.node
|
n = skippedNode.node
|
||||||
|
@ -633,9 +633,9 @@ walk: // Outer loop for walking the tree
|
||||||
|
|
||||||
// roll back to last valid skippedNode
|
// roll back to last valid skippedNode
|
||||||
if !value.tsr && path != "/" {
|
if !value.tsr && path != "/" {
|
||||||
for l := len(*skippedNodes); l > 0; {
|
for length := len(*skippedNodes); length > 0; length-- {
|
||||||
skippedNode := (*skippedNodes)[l-1]
|
skippedNode := (*skippedNodes)[length-1]
|
||||||
*skippedNodes = (*skippedNodes)[:l-1]
|
*skippedNodes = (*skippedNodes)[:length-1]
|
||||||
if strings.HasSuffix(skippedNode.path, path) {
|
if strings.HasSuffix(skippedNode.path, path) {
|
||||||
path = skippedNode.path
|
path = skippedNode.path
|
||||||
n = skippedNode.node
|
n = skippedNode.node
|
||||||
|
|
Loading…
Reference in New Issue