mirror of
https://github.com/gin-gonic/gin.git
synced 2026-10-28 11:04:42 +08:00
fix(engine): add default ReadHeaderTimeout to http.Server in Run methods (#4800)
Engine.Run, RunTLS, and RunListener created an http.Server without ReadHeaderTimeout, leaving applications exposed to slowloris-style resource exhaustion. A new Engine.ReadHeaderTimeout field (default 10s) is now applied to the http.Server created by these methods. Users can customize the value or use a custom http.Server for full control. Fixes #4760 Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
This commit is contained in:
1 parent
8ad9f9cbea
commit
d8f2d588d7
2 files changed
+54
-5
No files matched your search
@@ -13,6 +13,7 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||
filesystem "github.com/gin-gonic/gin/internal/fs"
|
||||
@@ -29,6 +30,12 @@ const (
|
||||
backslash = "\\"
|
||||
)
|
||||
|
||||
// defaultReadHeaderTimeout is the default ReadHeaderTimeout applied to the
|
||||
// http.Server created by Run, RunTLS and RunListener. It protects against
|
||||
// slowloris-style resource exhaustion when the caller does not configure a
|
||||
// custom http.Server.
|
||||
const defaultReadHeaderTimeout = 10 * time.Second
|
||||
|
||||
var (
|
||||
default404Body = []byte("404 page not found")
|
||||
default405Body = []byte("405 method not allowed")
|
||||
@@ -166,6 +173,12 @@ type Engine struct {
|
||||
// method call.
|
||||
MaxMultipartMemory int64
|
||||
|
||||
// ReadHeaderTimeout is the maximum duration allowed for reading the request
|
||||
// headers by the http.Server created through Run, RunTLS and RunListener.
|
||||
// If zero or negative, a default of 10 seconds is used. Set to a positive
|
||||
// value to customize, or use a custom http.Server directly to disable it.
|
||||
ReadHeaderTimeout time.Duration
|
||||
|
||||
// UseH2C enable h2c support.
|
||||
UseH2C bool
|
||||
|
||||
@@ -219,6 +232,7 @@ func New(opts ...OptionFunc) *Engine {
|
||||
RemoveExtraSlash: false,
|
||||
UnescapePathValues: true,
|
||||
MaxMultipartMemory: defaultMultipartMemory,
|
||||
ReadHeaderTimeout: defaultReadHeaderTimeout,
|
||||
trees: make(methodTrees, 0, 9),
|
||||
delims: render.Delims{Left: "{{", Right: "}}"},
|
||||
secureJSONPrefix: "while(1);",
|
||||
@@ -255,6 +269,15 @@ func (engine *Engine) allocateContext(maxParams uint16) *Context {
|
||||
return &Context{engine: engine, params: &v, skippedNodes: &skippedNodes}
|
||||
}
|
||||
|
||||
// getReadHeaderTimeout returns the configured ReadHeaderTimeout, or the
|
||||
// default if it is zero or negative.
|
||||
func (engine *Engine) getReadHeaderTimeout() time.Duration {
|
||||
if engine.ReadHeaderTimeout <= 0 {
|
||||
return defaultReadHeaderTimeout
|
||||
}
|
||||
return engine.ReadHeaderTimeout
|
||||
}
|
||||
|
||||
// Delims sets template left and right delims and returns an Engine instance.
|
||||
func (engine *Engine) Delims(left, right string) *Engine {
|
||||
engine.delims = render.Delims{Left: left, Right: right}
|
||||
@@ -537,6 +560,9 @@ func parseIP(ip string) net.IP {
|
||||
// Run attaches the router to a http.Server and starts listening and serving HTTP requests.
|
||||
// It is a shortcut for http.ListenAndServe(addr, router)
|
||||
// Note: this method will block the calling goroutine indefinitely unless an error happens.
|
||||
// The created http.Server is configured with a default ReadHeaderTimeout of 10 seconds
|
||||
// (configurable via Engine.ReadHeaderTimeout) to mitigate slowloris attacks.
|
||||
// For full control over timeouts, create a custom http.Server and use ServeHTTP.
|
||||
func (engine *Engine) Run(addr ...string) (err error) {
|
||||
defer func() { debugPrintError(err) }()
|
||||
|
||||
@@ -548,8 +574,9 @@ func (engine *Engine) Run(addr ...string) (err error) {
|
||||
address := resolveAddress(addr)
|
||||
debugPrint("Listening and serving HTTP on %s\n", address)
|
||||
server := &http.Server{ // #nosec G112
|
||||
Addr: address,
|
||||
Handler: engine.Handler(),
|
||||
Addr: address,
|
||||
Handler: engine.Handler(),
|
||||
ReadHeaderTimeout: engine.getReadHeaderTimeout(),
|
||||
}
|
||||
err = server.ListenAndServe()
|
||||
return
|
||||
@@ -568,8 +595,9 @@ func (engine *Engine) RunTLS(addr, certFile, keyFile string) (err error) {
|
||||
}
|
||||
|
||||
server := &http.Server{ // #nosec G112
|
||||
Addr: addr,
|
||||
Handler: engine.Handler(),
|
||||
Addr: addr,
|
||||
Handler: engine.Handler(),
|
||||
ReadHeaderTimeout: engine.getReadHeaderTimeout(),
|
||||
}
|
||||
err = server.ListenAndServeTLS(certFile, keyFile)
|
||||
return
|
||||
@@ -652,7 +680,8 @@ func (engine *Engine) RunListener(listener net.Listener) (err error) {
|
||||
}
|
||||
|
||||
server := &http.Server{ // #nosec G112
|
||||
Handler: engine.Handler(),
|
||||
Handler: engine.Handler(),
|
||||
ReadHeaderTimeout: engine.getReadHeaderTimeout(),
|
||||
}
|
||||
err = server.Serve(listener)
|
||||
return
|
||||
|
||||
+20
@@ -1024,6 +1024,26 @@ func TestWithOptionFunc(t *testing.T) {
|
||||
assertRoutePresent(t, routes, RouteInfo{Path: "/test2", Method: http.MethodGet, Handler: "github.com/gin-gonic/gin.handlerTest2"})
|
||||
}
|
||||
|
||||
func TestReadHeaderTimeoutDefault(t *testing.T) {
|
||||
engine := New()
|
||||
assert.Equal(t, defaultReadHeaderTimeout, engine.ReadHeaderTimeout)
|
||||
assert.Equal(t, defaultReadHeaderTimeout, engine.getReadHeaderTimeout())
|
||||
}
|
||||
|
||||
func TestReadHeaderTimeoutCustom(t *testing.T) {
|
||||
engine := New(func(e *Engine) {
|
||||
e.ReadHeaderTimeout = 30 * time.Second
|
||||
})
|
||||
assert.Equal(t, 30*time.Second, engine.ReadHeaderTimeout)
|
||||
assert.Equal(t, 30*time.Second, engine.getReadHeaderTimeout())
|
||||
}
|
||||
|
||||
func TestReadHeaderTimeoutZeroFallsBackToDefault(t *testing.T) {
|
||||
engine := New()
|
||||
engine.ReadHeaderTimeout = 0
|
||||
assert.Equal(t, defaultReadHeaderTimeout, engine.getReadHeaderTimeout())
|
||||
}
|
||||
|
||||
type Birthday string
|
||||
|
||||
func (b *Birthday) UnmarshalParam(param string) error {
|
||||
|
||||
Reference in new issue
Block a user