2026-04-28 19:33:43 +08:00
|
|
|
|
---
|
|
|
|
|
|
tags: [后端, Go, Gin, 服务器, 部署]
|
|
|
|
|
|
create time: 2026-04-28 00:00
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# 服务器配置与安全
|
|
|
|
|
|
|
|
|
|
|
|
## 概述
|
|
|
|
|
|
|
|
|
|
|
|
`gin.New()` 创建的是一个不带任何中间件的 bare Engine,配合标准库 `http.Server` 可以实现完全可控的服务启动。这一节涵盖高级服务器配置(超时、TLS、KeepAlive)、Cookie 操作、以及可信代理链的安全考量。
|
|
|
|
|
|
|
|
|
|
|
|
思考题:为什么在生产环境中不能依赖 `gin.Default()` 和 `r.Run()` 启动服务?生产环境应该用什么样的 `http.Server` 配置?
|
|
|
|
|
|
|
|
|
|
|
|
## 正文
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 自定义 Engine + `http.Server` 启动
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
// 不使用 Default(无自动 Logger/Recovery)
|
|
|
|
|
|
r := gin.New()
|
|
|
|
|
|
|
|
|
|
|
|
// 手动添加需要的中间件
|
|
|
|
|
|
r.Use(gin.Recovery())
|
|
|
|
|
|
r.Use(requestID())
|
|
|
|
|
|
r.Use(logger())
|
|
|
|
|
|
|
|
|
|
|
|
r.GET("/ping", func(c *gin.Context) {
|
|
|
|
|
|
c.JSON(200, gin.H{"message": "pong"})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 完整控制 http.Server
|
|
|
|
|
|
srv := &http.Server{
|
|
|
|
|
|
Addr: ":8080",
|
|
|
|
|
|
Handler: r, // Gin Engine 作为 Handler
|
|
|
|
|
|
ReadTimeout: 5 * time.Second,
|
|
|
|
|
|
WriteTimeout: 10 * time.Second,
|
|
|
|
|
|
IdleTimeout: 60 * time.Second,
|
|
|
|
|
|
MaxHeaderBytes: 1 << 20, // 1MB header 限制
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
log.Fatal(srv.ListenAndServe())
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**超时配置说明:**
|
|
|
|
|
|
|
|
|
|
|
|
| 超时项 | 作用 | 建议值 | 风险 |
|
|
|
|
|
|
|--------|------|--------|------|
|
|
|
|
|
|
| `ReadTimeout` | 整个请求体读取时间 | 5-10s | 不设 → slowloris 攻击 |
|
|
|
|
|
|
| `WriteTimeout` | 响应写入时间 | 10-30s | 不设 → 长连接耗尽 |
|
|
|
|
|
|
| `IdleTimeout` | KeepAlive 空闲等待 | 60-120s | 不设 → goroutine 泄漏 |
|
|
|
|
|
|
| `MaxHeaderBytes` | 请求头最大大小 | 1MB | 不设 → 默认 1MB |
|
|
|
|
|
|
|
|
|
|
|
|
> **提问:** `ReadTimeout` 是从连接建立开始计时,还是从最后一个字节读完开始?如果客户端发送一个巨型请求头(比如 100KB),`ReadTimeout` 会生效吗?
|
|
|
|
|
|
|
|
|
|
|
|
### 2. Cookie 操作
|
|
|
|
|
|
|
|
|
|
|
|
Gin 封装了便捷的 Cookie 读写方法:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
func setCookie(c *gin.Context) {
|
|
|
|
|
|
// 设置 Cookie
|
|
|
|
|
|
c.SetCookie(
|
|
|
|
|
|
"session_id", // name
|
|
|
|
|
|
"abc123xyz", // value
|
|
|
|
|
|
3600, // maxAge (秒)
|
|
|
|
|
|
"/", // path
|
|
|
|
|
|
"example.com", // domain (空 = 当前域名)
|
|
|
|
|
|
true, // secure (HTTPS only)
|
|
|
|
|
|
true, // httpOnly (JS 不可访问)
|
|
|
|
|
|
)
|
|
|
|
|
|
c.JSON(200, gin.H{"message": "cookie set"})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func getCookie(c *gin.Context) {
|
|
|
|
|
|
cookie, err := c.Cookie("session_id")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
c.JSON(400, gin.H{"error": "no cookie"})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
c.JSON(200, gin.H{"session_id": cookie})
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
**Cookie 安全标志组合:**
|
|
|
|
|
|
|
|
|
|
|
|
| secure | httpOnly | SameSite | 适用场景 |
|
|
|
|
|
|
|--------|----------|----------|----------|
|
|
|
|
|
|
| true | true | Strict | 认证 Cookie(最高安全) |
|
|
|
|
|
|
| true | true | Lax | 会话 Cookie(平衡安全与体验) |
|
|
|
|
|
|
| false | true | Lax | 开发环境 |
|
|
|
|
|
|
|
|
|
|
|
|
### 3. TLS / Let's Encrypt
|
|
|
|
|
|
|
|
|
|
|
|
Gin 本身不做 TLS 终止——你通过标准库 `http.Server` 配置:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
srv := &http.Server{
|
|
|
|
|
|
Addr: ":443",
|
|
|
|
|
|
Handler: r,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 方式一:已知证书
|
|
|
|
|
|
log.Fatal(srv.ListenAndServeTLS("/path/to/cert.pem", "/path/to/key.pem"))
|
|
|
|
|
|
|
|
|
|
|
|
// 方式二:Let's Encrypt (acme.AutoHTTPS)
|
|
|
|
|
|
// 最简单的方式——不需要证书文件
|
|
|
|
|
|
srv := &http.Server{
|
|
|
|
|
|
Addr: ":443",
|
|
|
|
|
|
Handler: r,
|
|
|
|
|
|
}
|
|
|
|
|
|
// acme 包会在首次请求时自动申请证书
|
|
|
|
|
|
r.RunTLS("", "", "") // Gin 快捷方式,等价于 ListenAndServeTLS("", "")
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Gin 的 `RunTLS` 快捷方法:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
// ListenAndServeTLS 的简化
|
|
|
|
|
|
r.RunTLS(":443", "/cert.pem", "/key.pem") // 指定文件和端口
|
|
|
|
|
|
r.RunTLS(":443", "", "") // ACME 自动 HTTPS
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 4. 可信代理链(Trusted Proxies)
|
|
|
|
|
|
|
|
|
|
|
|
当 Gin 跑在 Nginx/Cloudflare/K8s Ingress 后面时,`c.ClientIP()` 默认拿到的是负载均衡器的内网 IP,不是真实用户 IP:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
r := gin.New()
|
|
|
|
|
|
|
|
|
|
|
|
// 告诉 Gin 哪些 IP 是可信的代理
|
|
|
|
|
|
gin.SetMode(gin.ReleaseMode)
|
|
|
|
|
|
gin.DefaultSkipPanicHandler = true
|
|
|
|
|
|
|
|
|
|
|
|
// 标记所有内网 IP 为可信代理
|
|
|
|
|
|
gin.SetTrustedProxies([]string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"})
|
|
|
|
|
|
|
|
|
|
|
|
// 或在运行时
|
|
|
|
|
|
r.Use(func(c *gin.Context) {
|
|
|
|
|
|
c.SetTrustedProxyFn(func(remoteAddr net.Addr) bool {
|
|
|
|
|
|
ip := remoteAddr.(*net.TCPAddr).IP
|
|
|
|
|
|
return ip.IsPrivate() || ip.Equal(net.IPv4loopback)
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 现在 c.ClientIP() 会正确解析 X-Forwarded-For / X-Real-IP
|
|
|
|
|
|
r.GET("/hello", func(c *gin.Context) {
|
|
|
|
|
|
ip := c.ClientIP()
|
|
|
|
|
|
c.String(200, "Hello from %s", ip)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
r.Run()
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
> **安全警告:** 如果不设置可信代理,攻击者可以伪造 `X-Forwarded-For` 头注入任意 IP,绕过 IP 白名单限流。务必只信任你知道的代理 IP 段。
|
|
|
|
|
|
|
2026-05-15 16:26:14 +08:00
|
|
|
|
### 5. 完整的生产环境启动规范
|
2026-04-28 19:33:43 +08:00
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
func main() {
|
|
|
|
|
|
r := setupRouter()
|
|
|
|
|
|
|
|
|
|
|
|
srv := &http.Server{
|
|
|
|
|
|
Addr: ":" + os.Getenv("PORT"),
|
|
|
|
|
|
Handler: r,
|
|
|
|
|
|
ReadTimeout: 10 * time.Second,
|
|
|
|
|
|
WriteTimeout: 30 * time.Second,
|
|
|
|
|
|
IdleTimeout: 120 * time.Second,
|
|
|
|
|
|
MaxHeaderBytes: 1 << 20,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 优雅关闭(见 [[13-graceful-shutdown]])
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
|
|
|
|
log.Fatalf("server error: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
// 阻塞主 goroutine
|
|
|
|
|
|
select {}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
思考题:如果你的服务同时接收短连接(HTTP/1.1)和长连接(HTTP/2),`IdleTimeout` 对两种协议的行为一样吗?什么情况下它会失效?
|
|
|
|
|
|
|
|
|
|
|
|
## 关联笔记
|
|
|
|
|
|
|
|
|
|
|
|
- [[GIN/13-graceful-shutdown]] — server.Shutdown() 搭配使用的优雅关停机制
|
|
|
|
|
|
- [[GIN/logging]] — 生产环境日志配置
|
|
|
|
|
|
- [[部署与运维基础]] — 生产部署的超时、健康检查、反代配置
|