diff --git a/README.md b/README.md index a4bc4e76..df531cfe 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,7 @@ Gin is a web framework written in Go (Golang). It features a martini-like API wi - [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) - [http2 server push](#http2-server-push) + - [Support limiting the number of accepted requests using netutil.LimitListener](#RunLimited) - [Testing](#testing) - [Users](#users) @@ -1835,6 +1836,29 @@ func main() { } ``` +### RunLimited + +Limit the number of accepted requests via [netutils.LimitListener](https://godoc.org/golang.org/x/net/netutil) + + +[embedmd]:# (examples/run-limited/main.go go) +```go +package main + +import "github.com/gin-gonic/gin" + +const maxConnections = 10 +func main() { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "pong", + }) + }) + r.RunLimited(maxConnections, ":80") // listen and serve on 0.0.0.0:8080 +} +``` + ## Testing The `net/http/httptest` package is preferable way for HTTP testing. diff --git a/examples/run-limited/main.go b/examples/run-limited/main.go new file mode 100644 index 00000000..3d6d148d --- /dev/null +++ b/examples/run-limited/main.go @@ -0,0 +1,14 @@ +package main + +import "github.com/gin-gonic/gin" + +const maxConnections = 10 +func main() { + r := gin.Default() + r.GET("/ping", func(c *gin.Context) { + c.JSON(200, gin.H{ + "message": "pong", + }) + }) + r.RunLimited(maxConnections, ":80") // listen and serve on 0.0.0.0:8080 +} diff --git a/gin.go b/gin.go index aa62e014..75d0fba6 100644 --- a/gin.go +++ b/gin.go @@ -10,8 +10,10 @@ import ( "net/http" "os" "sync" + "time" "github.com/gin-gonic/gin/render" + "golang.org/x/net/netutil" ) const ( @@ -288,6 +290,42 @@ func (engine *Engine) Run(addr ...string) (err error) { return } +// RunLimited - use netuil.LimitListener to limit the number of inbound accepts +func (engine *GalapagosEngine) RunLimited(limit int, addr ...string) (err error) { + defer func() { debugPrintError(err) }() + + address := resolveAddress(addr) + debugPrint("Listening and serving HTTP on %s\n", address) + + // err = http.ListenAndServe(address, engine) + + srv := &http.Server{Addr: address, Handler: engine} + address = srv.Addr + if address == "" { + address = ":http" + } + ln, err := net.Listen("tcp", address) + if err != nil { + return err + } + lnLimited := netutil.LimitListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, limit) + return srv.Serve(lnLimited) +} + +type tcpKeepAliveListener struct { + *net.TCPListener +} + +func (ln tcpKeepAliveListener) Accept() (net.Conn, error) { + tc, err := ln.AcceptTCP() + if err != nil { + return nil, err + } + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(3 * time.Minute) + return tc, nil +} + // RunTLS attaches the router to a http.Server and starts listening and serving HTTPS (secure) requests. // It is a shortcut for http.ListenAndServeTLS(addr, certFile, keyFile, router) // Note: this method will block the calling goroutine indefinitely unless an error happens.