This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hzh/GIN/12-server-config.md
T

194 lines
5.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 段。
### 5. 完整的生產環境啟動範例
```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]] — 生产环境日志配置
- [[部署与运维基础]] — 生产部署的超时、健康检查、反代配置