Gin 的
Run()方法虽然方便,但很多时候我们需要更精细地控制 HTTP 服务器的行为。超时时间、缓冲区大小、最大请求体这些参数,在处理大文件上传或慢客户端时特别重要。
默认的 http.Server 配置比较宽松:
生产环境需要根据实际情况调整这些参数。
不使用 router.Run(),而是自己创建 http.Server:
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello World")
})
server := &http.Server{
Addr: ":8080",
Handler: router,
// 这里可以添加各种配置
}
log.Fatal(server.ListenAndServe())
}
超时是最重要的配置之一,能有效防止资源耗尽:
server := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 10 * time.Second, // 读取请求超时
WriteTimeout: 10 * time.Second, // 写入响应超时
IdleTimeout: 60 * time.Second, // 空闲连接超时
}
各超时参数的含义:
| 参数 | 说明 |
|---|---|
| ReadTimeout | 从建立连接到读取完请求头的超时时间 |
| ReadHeaderTimeout | 读取请求头的超时时间 |
| WriteTimeout | 写入响应的超时时间 |
| IdleTimeout | Keep-Alive 连接空闲超时时间 |
防止客户端发送超大请求体:
router := gin.Default()
// 限制请求体最大 10MB
router.Use(func(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 10*1024*1024)
c.Next()
})
或者用 http.Server 的配置:
server := &http.Server{
MaxHeaderBytes: 1 << 20, // 1MB,限制请求头大小
}
调整缓冲区大小可以优化性能:
server := &http.Server{
ReadBufferSize: 4096, // 读缓冲区 4KB
WriteBufferSize: 4096, // 写缓冲区 4KB
}
缓冲区越大,系统调用次数越少,但内存占用也越高。需要根据实际情况权衡。
一个生产环境可用的配置:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
)
func main() {
// 生产模式
gin.SetMode(gin.ReleaseMode)
router := gin.New()
router.Use(gin.Recovery())
// 请求体大小限制
router.Use(func(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 10*1024*1024)
c.Next()
})
router.GET("/", func(c *gin.Context) {
c.String(200, "Hello World")
})
router.GET("/slow", func(c *gin.Context) {
time.Sleep(5 * time.Second) // 模拟慢处理
c.String(200, "Slow response")
})
server := &http.Server{
Addr: ":8080",
Handler: router,
ReadTimeout: 10 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
MaxHeaderBytes: 1 << 20, // 1MB
ReadBufferSize: 4 * 1024,
WriteBufferSize: 4 * 1024,
}
// 优雅关闭
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Printf("Server shutdown error: %v", err)
}
log.Println("Server stopped")
}
HTTP Keep-Alive 允许复用 TCP 连接,减少连接建立开销。Go 默认开启 Keep-Alive,通过 IdleTimeout 控制空闲连接的存活时间:
server := &http.Server{
Addr: ":8080",
Handler: router,
IdleTimeout: 120 * time.Second, // 空闲连接保持 2 分钟
}
如果不需要 Keep-Alive,可以禁用:
server := &http.Server{
Addr: ":8080",
Handler: router,
IdleTimeout: -1, // 禁用 Keep-Alive
}
但通常不建议禁用,Keep-Alive 能显著提升性能。
可以监控连接状态变化:
server := &http.Server{
Addr: ":8080",
Handler: router,
ConnState: func(conn net.Conn, state http.ConnState) {
log.Printf("Connection %s -> %s", conn.RemoteAddr(), state)
},
}
连接状态包括:
http.StateNew:新连接http.StateActive:正在处理请求http.StateIdle:空闲,等待新请求http.StateHijacked:被劫持(如 WebSocket)http.StateClosed:已关闭这个功能可以用来监控连接数、发现异常连接等。
自定义错误日志输出:
type customLogger struct {
*log.Logger
}
func (l *customLogger) Write(p []byte) (n int, err error) {
// 可以在这里过滤或格式化日志
return l.Logger.Write(p)
}
func main() {
router := gin.Default()
logger := &customLogger{log.New(os.Stderr, "[HTTP] ", log.LstdFlags)}
server := &http.Server{
Addr: ":8080",
Handler: router,
ErrorLog: logger,
}
log.Fatal(server.ListenAndServe())
}
有时候需要监听多个端口:
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
c.String(200, "Main server")
})
// 主服务
mainServer := &http.Server{
Addr: ":8080",
Handler: router,
}
// 管理接口
adminRouter := gin.New()
adminRouter.GET("/health", func(c *gin.Context) {
c.String(200, "OK")
})
adminRouter.GET("/metrics", func(c *gin.Context) {
c.String(200, "Metrics endpoint")
})
adminServer := &http.Server{
Addr: ":8081",
Handler: adminRouter,
}
// 并发启动
go func() {
log.Println("Main server on :8080")
if err := mainServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
go func() {
log.Println("Admin server on :8081")
if err := adminServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
// 等待退出信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down...")
}
可以在创建连接时注入自定义 context:
type contextKey string
const requestIDKey contextKey = "requestID"
func main() {
router := gin.Default()
router.GET("/", func(c *gin.Context) {
requestID := c.Request.Context().Value(requestIDKey)
c.String(200, "Request ID: %v", requestID)
})
server := &http.Server{
Addr: ":8080",
Handler: router,
BaseContext: func(ln net.Listener) context.Context {
// 可以在这里添加全局 context 值
return context.Background()
},
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
// 每个连接的 context
requestID := uuid.New().String()
return context.WithValue(ctx, requestIDKey, requestID)
},
}
log.Fatal(server.ListenAndServe())
}
自定义 HTTP 配置是生产环境必备技能:
这些配置没有放之四海皆准的值,需要根据实际业务场景测试调整。建议在压力测试中观察服务器表现,找到最适合的配置组合。