mirror of
https://github.com/gin-gonic/gin.git
synced 2026-09-04 14:49:27 +08:00
fix(engine): add read header timeout by default
This commit is contained in:
parent
34dac209ff
commit
1954c48625
12
docs/doc.md
12
docs/doc.md
@ -2091,6 +2091,18 @@ See a complete example in the `https://github.com/gin-gonic/examples/tree/master
|
||||
|
||||
> Configure HTTP servers, TLS, proxies, and runtime settings.
|
||||
|
||||
### Default Read header timeout
|
||||
|
||||
You can change the default read header timeout by `engine.ReadHeaderTimeout` if its necessary.
|
||||
|
||||
```go
|
||||
func main() {
|
||||
router := gin.Default()
|
||||
router.ReadHeaderTimeout = 5 * time.Second
|
||||
http.ListenAndServe(":8000", router)
|
||||
}
|
||||
```
|
||||
|
||||
### Custom HTTP configuration
|
||||
|
||||
Use `http.ListenAndServe()` directly, like this:
|
||||
|
||||
15
gin.go
15
gin.go
@ -13,6 +13,7 @@ import (
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin/internal/bytesconv"
|
||||
filesystem "github.com/gin-gonic/gin/internal/fs"
|
||||
@ -24,6 +25,7 @@ import (
|
||||
|
||||
const (
|
||||
defaultMultipartMemory = 32 << 20 // 32 MB
|
||||
defaultReadHeaderTimeout = 10 * time.Second
|
||||
escapedColon = "\\:"
|
||||
colon = ":"
|
||||
backslash = "\\"
|
||||
@ -114,6 +116,17 @@ type Engine struct {
|
||||
// RedirectTrailingSlash is independent of this option.
|
||||
RedirectFixedPath bool
|
||||
|
||||
// ReadHeaderTimeout is the maximum duration for reading the entire
|
||||
// request header, including the body. A zero or negative value means
|
||||
// there will be no timeout.
|
||||
//
|
||||
// This setting helps protect against Slowloris attacks by limiting
|
||||
// the time a client can take to send headers. If the timeout expires
|
||||
// before the full header is received, the server closes the connection.
|
||||
// It corresponds directly to http.Server.ReadHeaderTimeout in the
|
||||
// standard library.
|
||||
ReadHeaderTimeout time.Duration
|
||||
|
||||
// HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the
|
||||
// current route, if the current request can not be routed.
|
||||
// If this is the case, the request is answered with 'Method Not Allowed'
|
||||
@ -210,6 +223,7 @@ func New(opts ...OptionFunc) *Engine {
|
||||
FuncMap: template.FuncMap{},
|
||||
RedirectTrailingSlash: true,
|
||||
RedirectFixedPath: false,
|
||||
ReadHeaderTimeout: defaultReadHeaderTimeout,
|
||||
HandleMethodNotAllowed: false,
|
||||
ForwardedByClientIP: true,
|
||||
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
|
||||
@ -550,6 +564,7 @@ func (engine *Engine) Run(addr ...string) (err error) {
|
||||
server := &http.Server{ // #nosec G112
|
||||
Addr: address,
|
||||
Handler: engine.Handler(),
|
||||
ReadHeaderTimeout: engine.ReadHeaderTimeout,
|
||||
}
|
||||
err = server.ListenAndServe()
|
||||
return
|
||||
|
||||
@ -605,3 +605,56 @@ func TestEscapedColon(t *testing.T) {
|
||||
testRequest(t, ts.URL+"/r/r/:r", "", "/r/r/\\:r")
|
||||
testRequest(t, ts.URL+"/r/r/r:r", "", "/r/r/r\\:r")
|
||||
}
|
||||
|
||||
// Tests the ReadHeaderTimeout fail
|
||||
func TestEngineReadHeaderTimeout(t *testing.T) {
|
||||
const timeout = 200 * time.Millisecond
|
||||
|
||||
router := New()
|
||||
router.ReadHeaderTimeout = timeout
|
||||
router.GET("/test", func(c *Context) {
|
||||
c.String(http.StatusOK, "ok")
|
||||
})
|
||||
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
defer ln.Close()
|
||||
|
||||
go func() {
|
||||
_ = http.Serve(ln, router.Handler())
|
||||
}()
|
||||
|
||||
conn, err := net.Dial("tcp", ln.Addr().String())
|
||||
require.NoError(t, err)
|
||||
defer conn.Close()
|
||||
|
||||
_, err = conn.Write([]byte("GET /test HTTP/1.1\r\nHost: localhost\r\n"))
|
||||
require.NoError(t, err)
|
||||
|
||||
conn.SetReadDeadline(time.Now().Add(timeout + 500*time.Millisecond))
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
_, err = conn.Read(buf)
|
||||
|
||||
assert.Error(t, err, "expected connection to be closed by server due to ReadHeaderTimeout")
|
||||
|
||||
if err == nil {
|
||||
t.Fatalf("expected error but got response: %s", string(buf))
|
||||
}
|
||||
}
|
||||
|
||||
// Tests the ReadHeaderTimeout success case
|
||||
func TestEngineReadHeaderTimeoutSuccess(t *testing.T) {
|
||||
router := New()
|
||||
router.ReadHeaderTimeout = 5 * time.Second
|
||||
router.GET("/test", func(c *Context) {
|
||||
c.String(http.StatusOK, "success")
|
||||
})
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, "success", w.Body.String())
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user