mirror of
https://github.com/gin-gonic/gin.git
synced 2026-09-04 14:49:27 +08:00
Merge 8c2e70ddd4d7a2c6e18d733dc39e5f3279f22700 into dcaa4296d111981ffb31ac3eba90bb63e1eb5ab9
This commit is contained in:
commit
dc5c70b52d
19
docs/doc.md
19
docs/doc.md
@ -2091,6 +2091,25 @@ See a complete example in the `https://github.com/gin-gonic/examples/tree/master
|
|||||||
|
|
||||||
> Configure HTTP servers, TLS, proxies, and runtime settings.
|
> Configure HTTP servers, TLS, proxies, and runtime settings.
|
||||||
|
|
||||||
|
### Default ReadHeaderTimeout
|
||||||
|
|
||||||
|
`Run`, `RunTLS`, `RunUnix`, and `RunListener` build their `http.Server` with a default
|
||||||
|
`ReadHeaderTimeout` of 5 seconds (`engine.ReadHeaderTimeout`), which mitigates
|
||||||
|
Slowloris-style attacks where a client trickles request headers in slowly to exhaust
|
||||||
|
server connections. It only bounds the time to read headers, not the request body or
|
||||||
|
the response, so it's safe for streaming/SSE/long-polling handlers.
|
||||||
|
|
||||||
|
To use a different value, or to disable it and restore the previous unbounded
|
||||||
|
behavior, set the field before calling `Run`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
router := gin.Default()
|
||||||
|
router.ReadHeaderTimeout = 0 // disable, or set your own duration
|
||||||
|
router.Run(":8080")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Custom HTTP configuration
|
### Custom HTTP configuration
|
||||||
|
|
||||||
Use `http.ListenAndServe()` directly, like this:
|
Use `http.ListenAndServe()` directly, like this:
|
||||||
|
|||||||
44
gin.go
44
gin.go
@ -13,6 +13,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||||
filesystem "github.com/gin-gonic/gin/internal/fs"
|
filesystem "github.com/gin-gonic/gin/internal/fs"
|
||||||
@ -23,10 +24,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultMultipartMemory = 32 << 20 // 32 MB
|
defaultMultipartMemory = 32 << 20 // 32 MB
|
||||||
escapedColon = "\\:"
|
defaultReadHeaderTimeout = 5 * time.Second
|
||||||
colon = ":"
|
escapedColon = "\\:"
|
||||||
backslash = "\\"
|
colon = ":"
|
||||||
|
backslash = "\\"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -166,6 +168,14 @@ type Engine struct {
|
|||||||
// method call.
|
// method call.
|
||||||
MaxMultipartMemory int64
|
MaxMultipartMemory int64
|
||||||
|
|
||||||
|
// ReadHeaderTimeout is the amount of time allowed to read request headers on
|
||||||
|
// connections created by Run, RunTLS, RunUnix, and RunListener. It protects
|
||||||
|
// against Slowloris-style attacks where a client trickles headers in slowly.
|
||||||
|
// It does not bound how long handlers may take to read the body or write the
|
||||||
|
// response, so it is safe for streaming/SSE/long-polling handlers.
|
||||||
|
// Set to 0 to disable (restores pre-default unbounded behavior).
|
||||||
|
ReadHeaderTimeout time.Duration
|
||||||
|
|
||||||
// UseH2C enable h2c support.
|
// UseH2C enable h2c support.
|
||||||
UseH2C bool
|
UseH2C bool
|
||||||
|
|
||||||
@ -199,6 +209,7 @@ var _ IRouter = (*Engine)(nil)
|
|||||||
// - UseRawPath: false
|
// - UseRawPath: false
|
||||||
// - UseEscapedPath: false
|
// - UseEscapedPath: false
|
||||||
// - UnescapePathValues: true
|
// - UnescapePathValues: true
|
||||||
|
// - ReadHeaderTimeout: 5 * time.Second
|
||||||
func New(opts ...OptionFunc) *Engine {
|
func New(opts ...OptionFunc) *Engine {
|
||||||
debugPrintWARNINGNew()
|
debugPrintWARNINGNew()
|
||||||
engine := &Engine{
|
engine := &Engine{
|
||||||
@ -219,6 +230,7 @@ func New(opts ...OptionFunc) *Engine {
|
|||||||
RemoveExtraSlash: false,
|
RemoveExtraSlash: false,
|
||||||
UnescapePathValues: true,
|
UnescapePathValues: true,
|
||||||
MaxMultipartMemory: defaultMultipartMemory,
|
MaxMultipartMemory: defaultMultipartMemory,
|
||||||
|
ReadHeaderTimeout: defaultReadHeaderTimeout,
|
||||||
trees: make(methodTrees, 0, 9),
|
trees: make(methodTrees, 0, 9),
|
||||||
delims: render.Delims{Left: "{{", Right: "}}"},
|
delims: render.Delims{Left: "{{", Right: "}}"},
|
||||||
secureJSONPrefix: "while(1);",
|
secureJSONPrefix: "while(1);",
|
||||||
@ -547,9 +559,10 @@ func (engine *Engine) Run(addr ...string) (err error) {
|
|||||||
engine.updateRouteTrees()
|
engine.updateRouteTrees()
|
||||||
address := resolveAddress(addr)
|
address := resolveAddress(addr)
|
||||||
debugPrint("Listening and serving HTTP on %s\n", address)
|
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||||
server := &http.Server{ // #nosec G112
|
server := &http.Server{
|
||||||
Addr: address,
|
Addr: address,
|
||||||
Handler: engine.Handler(),
|
Handler: engine.Handler(),
|
||||||
|
ReadHeaderTimeout: engine.ReadHeaderTimeout,
|
||||||
}
|
}
|
||||||
err = server.ListenAndServe()
|
err = server.ListenAndServe()
|
||||||
return
|
return
|
||||||
@ -567,9 +580,10 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
|
|||||||
"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
||||||
}
|
}
|
||||||
|
|
||||||
server := &http.Server{ // #nosec G112
|
server := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: engine.Handler(),
|
Handler: engine.Handler(),
|
||||||
|
ReadHeaderTimeout: engine.ReadHeaderTimeout,
|
||||||
}
|
}
|
||||||
err = server.ListenAndServeTLS(certFile, keyFile)
|
err = server.ListenAndServeTLS(certFile, keyFile)
|
||||||
return
|
return
|
||||||
@ -594,8 +608,9 @@ func (engine *Engine) RunUnix(file string) (err error) {
|
|||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
defer os.Remove(file)
|
defer os.Remove(file)
|
||||||
|
|
||||||
server := &http.Server{ // #nosec G112
|
server := &http.Server{
|
||||||
Handler: engine.Handler(),
|
Handler: engine.Handler(),
|
||||||
|
ReadHeaderTimeout: engine.ReadHeaderTimeout,
|
||||||
}
|
}
|
||||||
err = server.Serve(listener)
|
err = server.Serve(listener)
|
||||||
return
|
return
|
||||||
@ -651,8 +666,9 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
|||||||
"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
"Please check https://github.com/gin-gonic/gin/blob/master/docs/doc.md#dont-trust-all-proxies for details.")
|
||||||
}
|
}
|
||||||
|
|
||||||
server := &http.Server{ // #nosec G112
|
server := &http.Server{
|
||||||
Handler: engine.Handler(),
|
Handler: engine.Handler(),
|
||||||
|
ReadHeaderTimeout: engine.ReadHeaderTimeout,
|
||||||
}
|
}
|
||||||
err = server.Serve(listener)
|
err = server.Serve(listener)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -245,6 +245,37 @@ func TestRunWithPort(t *testing.T) {
|
|||||||
testRequest(t, "http://localhost:5150/example")
|
testRequest(t, "http://localhost:5150/example")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRunDefaultReadHeaderTimeout(t *testing.T) {
|
||||||
|
router := New()
|
||||||
|
router.ReadHeaderTimeout = 200 * time.Millisecond
|
||||||
|
router.GET("/example", func(c *Context) { c.String(http.StatusOK, "it worked") })
|
||||||
|
go func() {
|
||||||
|
assert.NoError(t, router.Run(":5151"))
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Wait for server to be ready with exponential backoff
|
||||||
|
err := waitForServerReady("http://localhost:5151/example", 10)
|
||||||
|
require.NoError(t, err, "server should start successfully")
|
||||||
|
|
||||||
|
// A normal request should still succeed unaffected by the header timeout.
|
||||||
|
testRequest(t, "http://localhost:5151/example")
|
||||||
|
|
||||||
|
// Open a raw connection and trickle in a partial request header,
|
||||||
|
// never completing it. The server should close the connection once
|
||||||
|
// ReadHeaderTimeout elapses instead of waiting forever.
|
||||||
|
conn, err := net.Dial("tcp", "localhost:5151")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
_, err = fmt.Fprint(conn, "GET /example HTTP/1.1\r\nHost: localhost\r\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NoError(t, conn.SetReadDeadline(time.Now().Add(2*time.Second)))
|
||||||
|
buf := make([]byte, 1)
|
||||||
|
_, err = conn.Read(buf)
|
||||||
|
assert.ErrorIs(t, err, io.EOF, "connection should be closed by the server after ReadHeaderTimeout")
|
||||||
|
}
|
||||||
|
|
||||||
func TestUnixSocket(t *testing.T) {
|
func TestUnixSocket(t *testing.T) {
|
||||||
router := New()
|
router := New()
|
||||||
|
|
||||||
|
|||||||
16
gin_test.go
16
gin_test.go
@ -1024,6 +1024,22 @@ func TestWithOptionFunc(t *testing.T) {
|
|||||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest2"})
|
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest2"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewDefaultReadHeaderTimeout(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
assert.Equal(t, defaultReadHeaderTimeout, r.ReadHeaderTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewReadHeaderTimeoutOverride(t *testing.T) {
|
||||||
|
r := New(func(e *Engine) {
|
||||||
|
e.ReadHeaderTimeout = 0
|
||||||
|
})
|
||||||
|
assert.Equal(t, time.Duration(0), r.ReadHeaderTimeout)
|
||||||
|
|
||||||
|
r2 := New()
|
||||||
|
r2.ReadHeaderTimeout = 30 * time.Second
|
||||||
|
assert.Equal(t, 30*time.Second, r2.ReadHeaderTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
type Birthday string
|
type Birthday string
|
||||||
|
|
||||||
func (b *Birthday) UnmarshalParam(param string) error {
|
func (b *Birthday) UnmarshalParam(param string) error {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user