Go Web 与网络编程精华
知识来源:
- 《Building Web Apps with Go》
- 《Go API 编程》
- 《Go Web 编程》(Go Web Programming, Sau Sheong Chang)
- 《Go 网络编程》(Network Programming with Go)
- 《Mastering Go Web Services》
- 《Go Web 编程》(2013, 谢孟军/astaxie)
一、net/http 包深入(Handler 接口、ServeMux、Middleware 链)
1.1 Handler 接口——一切的核心
Go HTTP 处理的核心抽象只有一个接口:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
整个 net/http 的设计哲学都围绕这个接口展开。路由器、中间件、静态文件服务器——全部是 Handler 的实现。
HandlerFunc 适配器——函数也能当 Handler:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
这是 Go 的经典设计模式:通过类型转换,让普通函数满足接口。
// 以下两种注册方式完全等价
http.Handle("/hello", http.HandlerFunc(helloFunc))
http.HandleFunc("/hello", helloFunc)
自定义 Handler 结构体——携带依赖注入:
type UserHandler struct {
db *sql.DB
logger *slog.Logger
cache *redis.Client
}
func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 可以访问 h.db, h.logger, h.cache
users, err := h.db.QueryContext(r.Context(), "SELECT ...")
// ...
}
// 注册
mux.Handle("/users", &UserHandler{db: db, logger: logger, cache: cache})
1.2 ServeMux 路由机制
DefaultServeMux 与自定义 Mux:
// 使用 DefaultServeMux(不推荐,全局状态,存在安全隐患)
http.HandleFunc("/api/users", usersHandler)
http.ListenAndServe(":8080", nil) // nil = DefaultServeMux
// 使用自定义 ServeMux(推荐)
mux := http.NewServeMux()
mux.HandleFunc("/api/users", usersHandler)
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
http.ListenAndServe(":8080", mux)
路由匹配规则:
- 精确匹配优先于前缀匹配
- 以 / 结尾的模式是子树匹配(前缀匹配)
- 不以 / 结尾的模式是精确匹配
- 最长匹配优先
Go 1.22+ 增强路由——终于支持方法和路径参数:
mux := http.NewServeMux()
// 方法限定
mux.HandleFunc("GET /api/users", listUsers)
mux.HandleFunc("POST /api/users", createUser)
// 路径参数
mux.HandleFunc("GET /api/users/{id}", getUser)
mux.HandleFunc("DELETE /api/users/{id}", deleteUser)
// 通配符(匹配剩余路径)
mux.HandleFunc("GET /files/{path...}", serveFile)
func getUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // 提取路径参数
// ...
}
优先级规则(Go 1.22+):
- 更具体的模式优先(GET /users/{id} 优先于 /users/{id})
- 带方法的优先于不带方法的
- 如果两个模式冲突且无法比较,注册时 panic
1.3 自定义 Server 配置
生产环境必须使用自定义 http.Server:
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 15 * time.Second, // 读取整个请求的超时
ReadHeaderTimeout: 5 * time.Second, // 读取请求头的超时
WriteTimeout: 15 * time.Second, // 写响应的超时
IdleTimeout: 60 * time.Second, // Keep-Alive 空闲超时
MaxHeaderBytes: 1 << 20, // 请求头最大 1MB
}
log.Fatal(server.ListenAndServe())
HTTPS / TLS 支持:
// 方式一:直接使用证书文件
server.ListenAndServeTLS("cert.pem", "key.pem")
// 方式二:自定义 TLS 配置
server.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.X25519, tls.CurveP256},
PreferServerCipherSuites: true,
}
server.ListenAndServeTLS("cert.pem", "key.pem")
// 方式三:Let's Encrypt 自动证书
import "golang.org/x/crypto/acme/autocert"
manager := &autocert.Manager{
Cache: autocert.DirCache("certs"),
Prompt: autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example.com"),
}
server.TLSConfig = manager.TLSConfig()
server.ListenAndServeTLS("", "")
1.4 中间件模式
中间件是接收 Handler 并返回新 Handler 的函数,形成洋葱模型:
type Middleware func(http.Handler) http.Handler
日志中间件:
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 包装 ResponseWriter 以捕获状态码
ww := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(ww, r)
slog.Info("request",
"method", r.Method,
"path", r.URL.Path,
"status", ww.statusCode,
"duration", time.Since(start),
"ip", r.RemoteAddr,
)
})
}
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
Recovery 中间件(panic 恢复):
func recoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
slog.Error("panic recovered",
"error", err,
"stack", string(debug.Stack()),
)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
中间件链式组合:
func chainMiddleware(handler http.Handler, middlewares ...Middleware) http.Handler {
// 从后往前包装,确保执行顺序是从前到后
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
// 使用
finalHandler := chainMiddleware(
myHandler,
recoveryMiddleware, // 最外层,最先执行
loggingMiddleware,
corsMiddleware,
authMiddleware, // 最内层,最后执行
)
CORS 中间件:
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Max-Age", "86400")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
二、HTTP 服务器实现原理
2.1 启动流程源码分析
ListenAndServe 的完整调用链:
ListenAndServe(addr, handler)
-> net.Listen("tcp", addr) // 创建 TCP 监听器
-> server.Serve(listener) // 进入主循环
-> for { listener.Accept() } // 循环接受连接
-> go conn.serve(ctx) // 每个连接一个 goroutine
-> serverHandler.ServeHTTP(w, r) // 调用 Handler
关键设计:每请求一个 goroutine。 Go 的 goroutine 非常轻量(初始栈仅 2KB),因此可以同时处理数万并发连接,无需回调或线程池。
2.2 连接处理详解
// 简化版的 conn.serve 核心逻辑
func (c *conn) serve(ctx context.Context) {
defer c.close()
// TLS 握手(如果是 HTTPS)
if tlsConn, ok := c.rwc.(*tls.Conn); ok {
tlsConn.HandshakeContext(ctx)
}
for {
// 读取请求(支持 HTTP/1.1 Keep-Alive,一个连接可复用多次)
w, err := c.readRequest(ctx)
if err != nil {
return
}
// 调用 Handler 处理请求
serverHandler{c.server}.ServeHTTP(w, w.req)
// 刷新响应
w.finishRequest()
// 检查是否 Keep-Alive
if !w.shouldReuseConnection() {
return
}
}
}
2.3 ResponseWriter 的三层接口
// 基础接口
type ResponseWriter interface {
Header() http.Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}
// 实际实现还满足以下可选接口:
// http.Flusher -> Flush() 方法,用于 SSE 等流式响应
// http.Hijacker -> Hijack() 方法,用于 WebSocket 升级
// http.Pusher -> Push() 方法,用于 HTTP/2 Server Push
// 检测并使用 Flusher(SSE 场景)
if flusher, ok := w.(http.Flusher); ok {
fmt.Fprintf(w, "data: %s\n\n", message)
flusher.Flush()
}
// Hijack 接管连接(WebSocket 底层原理)
if hijacker, ok := w.(http.Hijacker); ok {
conn, bufrw, err := hijacker.Hijack()
// 此后可以直接操作底层 TCP 连接
}
2.4 Server-Sent Events (SSE) 实现
func sseHandler(w http.ResponseWriter, r *http.Request) {
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-r.Context().Done():
return
case t := <-ticker.C:
fmt.Fprintf(w, "event: time\ndata: %s\n\n", t.Format(time.RFC3339))
flusher.Flush()
}
}
}
三、RESTful API 设计与实现
3.1 资源设计与路由注册
// CRUD 对应 HTTP 方法
// GET /api/v1/users -> 列表
// GET /api/v1/users/{id} -> 详情
// POST /api/v1/users -> 创建
// PUT /api/v1/users/{id} -> 全量更新
// PATCH /api/v1/users/{id} -> 部分更新
// DELETE /api/v1/users/{id} -> 删除
type UserAPI struct {
store UserStore
logger *slog.Logger
}
func (api *UserAPI) RegisterRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /api/v1/users", api.List)
mux.HandleFunc("GET /api/v1/users/{id}", api.Get)
mux.HandleFunc("POST /api/v1/users", api.Create)
mux.HandleFunc("PUT /api/v1/users/{id}", api.Update)
mux.HandleFunc("DELETE /api/v1/users/{id}", api.Delete)
}
3.2 统一响应格式
type APIResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
type PagedResponse struct {
Items interface{} `json:"items"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalCount int64 `json:"total_count"`
TotalPages int `json:"total_pages"`
}
func respondJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func success(w http.ResponseWriter, data interface{}) {
respondJSON(w, http.StatusOK, APIResponse{Code: 0, Message: "success", Data: data})
}
func errorResponse(w http.ResponseWriter, status int, msg string) {
respondJSON(w, status, APIResponse{Code: -1, Message: msg})
}
3.3 JSON 处理技巧
// 结构体 json tag
type User struct {
ID int64 `json:"id"`
Name string `json:"name"`
Email string `json:"email,omitempty"` // 空值不输出
Password string `json:"-"` // 永远不序列化
CreatedAt time.Time `json:"created_at"`
}
// 安全解码:限制大小 + 拒绝未知字段
func decodeJSON(r *http.Request, v interface{}) error {
// 限制请求体大小 1MB
r.Body = http.MaxBytesReader(nil, r.Body, 1<<20)
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
if err := decoder.Decode(v); err != nil {
return fmt.Errorf("invalid JSON: %w", err)
}
// 确保没有多余数据
if decoder.More() {
return errors.New("request body must only contain a single JSON object")
}
return nil
}
// 自定义 JSON 序列化
type UnixTime time.Time
func (t UnixTime) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(t).Unix())
}
func (t *UnixTime) UnmarshalJSON(data []byte) error {
var unix int64
if err := json.Unmarshal(data, &unix); err != nil {
return err
}
*t = UnixTime(time.Unix(unix, 0))
return nil
}
3.4 分页、过滤与排序
type PaginationParams struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
Sort string `json:"sort"`
Order string `json:"order"` // asc, desc
}
func parsePagination(r *http.Request) PaginationParams {
q := r.URL.Query()
page, _ := strconv.Atoi(q.Get("page"))
if page < 1 {
page = 1
}
pageSize, _ := strconv.Atoi(q.Get("page_size"))
if pageSize < 1 || pageSize > 100 {
pageSize = 20
}
sort := q.Get("sort")
order := q.Get("order")
if order != "asc" && order != "desc" {
order = "desc"
}
return PaginationParams{Page: page, PageSize: pageSize, Sort: sort, Order: order}
}
// SQL 中使用
// offset := (params.Page - 1) * params.PageSize
// query := fmt.Sprintf("SELECT * FROM users ORDER BY %s %s LIMIT ? OFFSET ?", sort, order)
3.5 请求验证
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
Password string `json:"password"`
Age int `json:"age"`
}
func (req CreateUserRequest) Validate() error {
var errs []string
if strings.TrimSpace(req.Name) == "" {
errs = append(errs, "name is required")
}
if !strings.Contains(req.Email, "@") {
errs = append(errs, "invalid email format")
}
if len(req.Password) < 8 {
errs = append(errs, "password must be at least 8 characters")
}
if req.Age < 0 || req.Age > 150 {
errs = append(errs, "age must be between 0 and 150")
}
if len(errs) > 0 {
return fmt.Errorf("validation failed: %s", strings.Join(errs, "; "))
}
return nil
}
// 也可用 go-playground/validator 做声明式验证
import "github.com/go-playground/validator/v10"
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=2,max=50"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
Age int `json:"age" validate:"gte=0,lte=150"`
}
var validate = validator.New()
func (req CreateUserRequest) Validate() error {
return validate.Struct(req)
}
3.6 API 版本管理
// 方式一:URL 路径版本(最常用)
mux.HandleFunc("GET /api/v1/users", v1ListUsers)
mux.HandleFunc("GET /api/v2/users", v2ListUsers)
// 方式二:请求头版本
func versionMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
version := r.Header.Get("Accept-Version")
if version == "" {
version = "v1"
}
ctx := context.WithValue(r.Context(), "api-version", version)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
四、WebSocket 编程
4.1 基础 WebSocket 服务
import "github.com/gorilla/websocket"
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
// 生产环境应严格检查 Origin
origin := r.Header.Get("Origin")
return origin == "https://myapp.com"
},
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("Upgrade error:", err)
return
}
defer conn.Close()
// 设置读取限制和超时
conn.SetReadLimit(512)
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
conn.SetPongHandler(func(string) error {
conn.SetReadDeadline(time.Now().Add(60 * time.Second))
return nil
})
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err,
websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("WebSocket error: %v", err)
}
break
}
log.Printf("Received: %s", message)
err = conn.WriteMessage(messageType, message)
if err != nil {
break
}
}
}
4.2 聊天室 Hub 模式
这是 WebSocket 应用的经典架构,来源于 gorilla/websocket 官方示例:
type Hub struct {
clients map[*Client]bool
broadcast chan []byte
register chan *Client
unregister chan *Client
mu sync.RWMutex
}
type Client struct {
hub *Hub
conn *websocket.Conn
send chan []byte
}
func newHub() *Hub {
return &Hub{
clients: make(map[*Client]bool),
broadcast: make(chan []byte, 256),
register: make(chan *Client),
unregister: make(chan *Client),
}
}
func (h *Hub) Run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
case message := <-h.broadcast:
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
// 客户端读取 goroutine
func (c *Client) readPump() {
defer func() {
c.hub.unregister <- c
c.conn.Close()
}()
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
break
}
c.hub.broadcast <- message
}
}
// 客户端写入 goroutine
func (c *Client) writePump() {
ticker := time.NewTicker(54 * time.Second)
defer func() {
ticker.Stop()
c.conn.Close()
}()
for {
select {
case message, ok := <-c.send:
if !ok {
c.conn.WriteMessage(websocket.CloseMessage, []byte{})
return
}
c.conn.WriteMessage(websocket.TextMessage, message)
case <-ticker.C:
// 定时发送 Ping 保持连接
if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
return
}
}
}
}
4.3 nhooyr/websocket(更现代的选择)
import "nhooyr.io/websocket"
func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{
OriginPatterns: []string{"myapp.com"},
})
if err != nil {
return
}
defer conn.CloseNow()
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Minute)
defer cancel()
for {
typ, data, err := conn.Read(ctx)
if err != nil {
break
}
conn.Write(ctx, typ, data)
}
conn.Close(websocket.StatusNormalClosure, "")
}
五、TCP/UDP 网络编程
5.1 TCP 服务器
func startTCPServer() {
listener, err := net.Listen("tcp", ":9000")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
log.Println("TCP server listening on :9000")
for {
conn, err := listener.Accept()
if err != nil {
log.Println("Accept error:", err)
continue
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
line := scanner.Text()
log.Printf("Received: %s from %s", line, conn.RemoteAddr())
fmt.Fprintf(conn, "Echo: %s\n", line)
// 每次收到数据后刷新超时
conn.SetReadDeadline(time.Now().Add(30 * time.Second))
}
}
5.2 TCP 客户端
func tcpClient() {
conn, err := net.DialTimeout("tcp", "localhost:9000", 5*time.Second)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
fmt.Fprintln(conn, "Hello, Server!")
response, err := bufio.NewReader(conn).ReadString('\n')
if err != nil {
log.Fatal(err)
}
fmt.Printf("Response: %s", response)
}
5.3 带长度前缀的消息协议
TCP 是流协议,需要自行处理消息边界。常见方案是长度前缀:
// 发送:4字节大端序长度前缀 + 消息体
func sendMessage(conn net.Conn, msg []byte) error {
length := uint32(len(msg))
buf := make([]byte, 4+len(msg))
binary.BigEndian.PutUint32(buf[:4], length)
copy(buf[4:], msg)
_, err := conn.Write(buf)
return err
}
// 接收
func recvMessage(conn net.Conn) ([]byte, error) {
header := make([]byte, 4)
if _, err := io.ReadFull(conn, header); err != nil {
return nil, err
}
length := binary.BigEndian.Uint32(header)
if length > 10<<20 { // 限制最大 10MB
return nil, fmt.Errorf("message too large: %d", length)
}
data := make([]byte, length)
if _, err := io.ReadFull(conn, data); err != nil {
return nil, err
}
return data, nil
}
5.4 UDP 编程
// UDP 服务器
func udpServer() {
addr, _ := net.ResolveUDPAddr("udp", ":9001")
conn, err := net.ListenUDP("udp", addr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
buf := make([]byte, 1024)
for {
n, remoteAddr, err := conn.ReadFromUDP(buf)
if err != nil {
continue
}
log.Printf("From %s: %s", remoteAddr, buf[:n])
conn.WriteToUDP([]byte("ACK"), remoteAddr)
}
}
// UDP 客户端
func udpClient() {
addr, _ := net.ResolveUDPAddr("udp", "localhost:9001")
conn, err := net.DialUDP("udp", nil, addr)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
conn.Write([]byte("Hello UDP"))
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
fmt.Println("Response:", string(buf[:n]))
}
5.5 并发连接限制
func startLimitedServer(maxConns int) {
listener, _ := net.Listen("tcp", ":9000")
sem := make(chan struct{}, maxConns) // 信号量限制并发
for {
conn, err := listener.Accept()
if err != nil {
continue
}
sem <- struct{}{} // 获取信号量
go func() {
defer func() { <-sem }() // 释放信号量
handleConnection(conn)
}()
}
}
六、HTTP 客户端与连接池管理
6.1 自定义 Transport(连接池核心)
transport := &http.Transport{
// 连接池配置
MaxIdleConns: 100, // 全局最大空闲连接数
MaxIdleConnsPerHost: 10, // 每个 Host 最大空闲连接数
MaxConnsPerHost: 100, // 每个 Host 最大连接数(含活跃+空闲)
IdleConnTimeout: 90 * time.Second, // 空闲连接超时回收
// 连接超时
DialContext: (&net.Dialer{
Timeout: 30 * time.Second, // TCP 连接超时
KeepAlive: 30 * time.Second, // TCP Keep-Alive 间隔
}).DialContext,
// TLS 握手超时
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
// 启用 HTTP/2
ForceAttemptHTTP2: true,
}
client := &http.Client{
Timeout: 30 * time.Second, // 整个请求超时(含连接、发送、接收)
Transport: transport,
}
关键要点:
- http.DefaultClient 没有超时,生产环境绝对不能用
- http.Transport 内部维护连接池,必须复用,不要每次请求新建
- MaxIdleConnsPerHost 默认值只有 2,高并发场景必须调大
- 一定要读完并关闭 resp.Body,否则连接无法归还连接池
6.2 构造请求
// 带 Context 的请求(推荐)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "POST", "https://api.example.com/users", body)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("User-Agent", "MyApp/1.0")
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// 必须读完 Body,即使不用,否则连接不会归还连接池
if resp.StatusCode != http.StatusOK {
io.Copy(io.Discard, resp.Body)
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
}
6.3 带重试与指数退避
func doWithRetry(client *http.Client, req *http.Request, maxRetries int) (*http.Response, error) {
var resp *http.Response
var err error
for attempt := 0; attempt <= maxRetries; attempt++ {
// 重要:需要克隆 Body,因为请求体只能读一次
if req.GetBody != nil {
req.Body, _ = req.GetBody()
}
resp, err = client.Do(req)
if err == nil && resp.StatusCode < 500 {
return resp, nil
}
if resp != nil {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
}
// 指数退避 + 抖动
backoff := time.Duration(1<<uint(attempt)) * time.Second
jitter := time.Duration(rand.Int63n(int64(backoff / 2)))
time.Sleep(backoff + jitter)
}
return resp, fmt.Errorf("failed after %d retries: %w", maxRetries, err)
}
6.4 连接池监控
// 获取连接池状态(用于 Prometheus 指标等)
func monitorTransport(t *http.Transport) {
ticker := time.NewTicker(10 * time.Second)
for range ticker.C {
// Go 没有直接暴露连接池状态的 API
// 可通过 CloseIdleConnections() 手动清理
// 或用 httptrace 包追踪连接复用情况
}
}
// 使用 httptrace 追踪连接行为
import "net/http/httptrace"
trace := &httptrace.ClientTrace{
GotConn: func(info httptrace.GotConnInfo) {
log.Printf("连接复用: %v, 空闲: %v, 空闲时长: %v",
info.Reused, info.WasIdle, info.IdleTime)
},
ConnectStart: func(network, addr string) {
log.Printf("开始建立连接: %s %s", network, addr)
},
}
req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
七、Web 安全(CSRF、XSS、SQL 注入防护、JWT 认证)
7.1 CSRF 防护
原理: 跨站请求伪造利用浏览器自动携带 Cookie 的机制。防御方式是在表单中嵌入一个服务器生成的随机 Token,提交时验证。
import "crypto/rand"
// 生成 CSRF Token
func generateCSRFToken() string {
b := make([]byte, 32)
rand.Read(b)
return base64.URLEncoding.EncodeToString(b)
}
// CSRF 中间件
func csrfMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// GET/HEAD/OPTIONS 请求不检查
if r.Method == "GET" || r.Method == "HEAD" || r.Method == "OPTIONS" {
// 生成 Token 并设置 Cookie
token := generateCSRFToken()
http.SetCookie(w, &http.Cookie{
Name: "csrf_token",
Value: token,
Path: "/",
HttpOnly: false, // JS 需要读取
SameSite: http.SameSiteStrictMode,
})
ctx := context.WithValue(r.Context(), "csrf_token", token)
next.ServeHTTP(w, r.WithContext(ctx))
return
}
// POST/PUT/DELETE 请求验证 Token
cookie, err := r.Cookie("csrf_token")
if err != nil {
http.Error(w, "CSRF token missing", http.StatusForbidden)
return
}
formToken := r.Header.Get("X-CSRF-Token")
if formToken == "" {
formToken = r.FormValue("csrf_token")
}
if !hmac.Equal([]byte(cookie.Value), []byte(formToken)) {
http.Error(w, "CSRF token mismatch", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
推荐使用 gorilla/csrf 库:
import "github.com/gorilla/csrf"
csrfProtect := csrf.Protect(
[]byte("32-byte-long-auth-key-here!!!!!"),
csrf.Secure(true), // 仅 HTTPS
)
http.ListenAndServe(":8080", csrfProtect(mux))
7.2 XSS 防护
核心原则:永远不要信任用户输入,输出时必须转义。
// 1. html/template 自动转义(最重要的防线)
// html/template 会自动对 {{.}} 进行 HTML 转义
// <script>alert('xss')</script> -> <script>alert('xss')</script>
// 2. 手动转义
import "html"
safe := html.EscapeString(userInput)
// 3. 输入清洗(使用 bluemonday)
import "github.com/microcosm-cc/bluemonday"
// 严格模式:去除所有 HTML 标签
p := bluemonday.StrictPolicy()
clean := p.Sanitize(userInput)
// UGC 模式:允许安全标签
p := bluemonday.UGCPolicy()
clean := p.Sanitize(userInput)
// 4. Content-Security-Policy 响应头
func securityHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-XSS-Protection", "1; mode=block")
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
next.ServeHTTP(w, r)
})
}
7.3 SQL 注入防护
// 错误写法:直接拼接 SQL(严重安全漏洞)
query := "SELECT * FROM users WHERE name = '" + username + "'"
// 攻击者输入: ' OR '1'='1
// 正确写法:使用参数化查询(预编译语句)
// MySQL
row := db.QueryRow("SELECT id, name FROM users WHERE name = ?", username)
// PostgreSQL
row := db.QueryRow("SELECT id, name FROM users WHERE name = $1", username)
// 预编译语句(高频查询推荐)
stmt, err := db.Prepare("SELECT id, name FROM users WHERE name = ? AND age > ?")
defer stmt.Close()
rows, err := stmt.Query(username, age)
// ORM 也要注意:避免原生 SQL 拼接
// GORM 正确方式
db.Where("name = ?", username).First(&user)
// GORM 错误方式
db.Where(fmt.Sprintf("name = '%s'", username)).First(&user) // 仍然有注入风险
7.4 JWT 认证完整实现
import "github.com/golang-jwt/jwt/v5"
var jwtSecret = []byte(os.Getenv("JWT_SECRET"))
type Claims struct {
UserID int64 `json:"user_id"`
Role string `json:"role"`
jwt.RegisteredClaims
}
// 生成 Access Token + Refresh Token
func generateTokenPair(userID int64, role string) (accessToken, refreshToken string, err error) {
// Access Token(短期,如 15 分钟)
accessClaims := Claims{
UserID: userID,
Role: role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(15 * time.Minute)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "myapp",
},
}
accessToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims).SignedString(jwtSecret)
if err != nil {
return
}
// Refresh Token(长期,如 7 天)
refreshClaims := jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(7 * 24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: strconv.FormatInt(userID, 10),
}
refreshToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims).SignedString(jwtSecret)
return
}
// 验证 Token
func parseToken(tokenString string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{},
func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return jwtSecret, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, errors.New("invalid token")
}
// JWT 认证中间件
func jwtAuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authHeader := r.Header.Get("Authorization")
if !strings.HasPrefix(authHeader, "Bearer ") {
http.Error(w, "Missing or invalid Authorization header", http.StatusUnauthorized)
return
}
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
claims, err := parseToken(tokenString)
if err != nil {
http.Error(w, "Invalid token: "+err.Error(), http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "claims", claims)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// 基于角色的权限控制
func requireRole(role string, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := r.Context().Value("claims").(*Claims)
if claims.Role != role {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
7.5 密码安全
import "golang.org/x/crypto/bcrypt"
func hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
func checkPassword(hashedPassword, password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
return err == nil
}
7.6 请求限流
import "golang.org/x/time/rate"
// 全局限流
func rateLimitMiddleware(rps float64, burst int) Middleware {
limiter := rate.NewLimiter(rate.Limit(rps), burst)
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
// 按 IP 限流
type IPRateLimiter struct {
limiters sync.Map
rate rate.Limit
burst int
}
func (rl *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
if limiter, ok := rl.limiters.Load(ip); ok {
return limiter.(*rate.Limiter)
}
limiter := rate.NewLimiter(rl.rate, rl.burst)
rl.limiters.Store(ip, limiter)
return limiter
}
八、模板引擎(html/template)
8.1 基础用法
// 解析并渲染模板
tmpl, err := template.ParseFiles("templates/index.html")
tmpl.Execute(w, data)
// 解析目录下所有模板
tmpl, err := template.ParseGlob("templates/*.html")
tmpl.ExecuteTemplate(w, "index.html", data)
// 生产环境推荐:启动时预解析,避免运行时文件读取
var templates = template.Must(template.ParseGlob("templates/*.html"))
8.2 模板语法大全
<!-- 变量输出(自动 HTML 转义) -->
<h1>{{.Title}}</h1>
<!-- 条件判断 -->
{{if .IsLoggedIn}}
<p>Welcome, {{.Username}}!</p>
{{else if .IsGuest}}
<p>Welcome, Guest!</p>
{{else}}
<a href="/login">Login</a>
{{end}}
<!-- 循环 -->
{{range .Items}}
<li>{{.Name}} - {{.Price}}</li>
{{else}}
<li>No items found.</li>
{{end}}
<!-- range 带 index -->
{{range $index, $item := .Items}}
<li>{{$index}}: {{$item.Name}}</li>
{{end}}
<!-- 变量赋值 -->
{{$name := .User.Name}}
<p>{{$name}}</p>
<!-- 管道操作 -->
<p>{{.CreatedAt | formatDate}}</p>
<p>{{.Name | upper | printf "Hello, %s"}}</p>
<!-- 模板嵌套 -->
{{template "header" .}}
{{template "content" .}}
<!-- 使用 block 定义可覆盖的块 -->
{{block "sidebar" .}}
<p>Default sidebar content</p>
{{end}}
<!-- with 改变上下文 -->
{{with .User}}
<p>{{.Name}} ({{.Email}})</p>
{{end}}
8.3 自定义模板函数
funcMap := template.FuncMap{
"formatDate": func(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
},
"upper": strings.ToUpper,
"truncate": func(s string, n int) string {
if len(s) > n {
return s[:n] + "..."
}
return s
},
"safeHTML": func(s string) template.HTML {
return template.HTML(s) // 注意:跳过转义,仅用于可信内容
},
"dict": func(values ...interface{}) map[string]interface{} {
m := make(map[string]interface{})
for i := 0; i < len(values); i += 2 {
m[values[i].(string)] = values[i+1]
}
return m
},
}
// 必须在 ParseFiles 之前调用 Funcs
tmpl := template.New("").Funcs(funcMap)
tmpl = template.Must(tmpl.ParseGlob("templates/*.html"))
8.4 布局模式(Layout / Base Template)
// templates/base.html
{{define "base"}}
<!DOCTYPE html>
<html>
<head>
<title>{{template "title" .}}</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<nav>{{template "nav" .}}</nav>
<main>{{template "content" .}}</main>
<footer>{{template "footer" .}}</footer>
</body>
</html>
{{end}}
// templates/home.html
{{define "title"}}Home Page{{end}}
{{define "content"}}
<h1>Welcome, {{.User.Name}}</h1>
{{end}}
// 渲染时指定 "base" 模板
func renderPage(w http.ResponseWriter, page string, data interface{}) {
tmpl := template.Must(template.ParseFiles(
"templates/base.html",
"templates/"+page+".html",
))
tmpl.ExecuteTemplate(w, "base", data)
}
九、静态文件服务
9.1 FileServer
// 基础方式
fs := http.FileServer(http.Dir("./static"))
mux.Handle("/static/", http.StripPrefix("/static/", fs))
// Go 1.16+ embed 嵌入静态文件(部署时无需附带文件目录)
//go:embed static/*
var staticFiles embed.FS
mux.Handle("/static/", http.FileServer(http.FS(staticFiles)))
// 单页应用(SPA)支持:找不到文件时返回 index.html
func spaHandler(staticFS http.FileSystem) http.Handler {
fileServer := http.FileServer(staticFS)
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
// 尝试打开文件
f, err := staticFS.Open(path)
if err != nil {
// 文件不存在,返回 index.html(由前端路由接管)
r.URL.Path = "/"
} else {
f.Close()
}
fileServer.ServeHTTP(w, r)
})
}
9.2 文件上传
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// 限制上传大小
r.Body = http.MaxBytesReader(w, r.Body, 10<<20) // 10MB
err := r.ParseMultipartForm(10 << 20)
if err != nil {
http.Error(w, "File too large", http.StatusBadRequest)
return
}
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "Invalid file", http.StatusBadRequest)
return
}
defer file.Close()
// 验证文件类型
buf := make([]byte, 512)
file.Read(buf)
contentType := http.DetectContentType(buf)
if contentType != "image/jpeg" && contentType != "image/png" {
http.Error(w, "Only JPEG and PNG allowed", http.StatusBadRequest)
return
}
file.Seek(0, io.SeekStart) // 重置读取位置
// 生成安全文件名
ext := filepath.Ext(header.Filename)
newName := fmt.Sprintf("%s%s", uuid.New().String(), ext)
dst, err := os.Create(filepath.Join("./uploads", newName))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer dst.Close()
io.Copy(dst, file)
fmt.Fprintf(w, "Uploaded: %s (%d bytes)", newName, header.Size)
}
9.3 文件下载
func downloadHandler(w http.ResponseWriter, r *http.Request) {
filename := filepath.Base(r.URL.Query().Get("file")) // 安全处理:只取文件名
filePath := filepath.Join("./files", filename)
// 检查文件是否存在
if _, err := os.Stat(filePath); os.IsNotExist(err) {
http.Error(w, "File not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Disposition", "attachment; filename="+filename)
w.Header().Set("Content-Type", "application/octet-stream")
http.ServeFile(w, r, filePath)
}
十、Session/Cookie 管理
10.1 Cookie 操作
// 设置 Cookie
func setSessionCookie(w http.ResponseWriter, sessionID string) {
http.SetCookie(w, &http.Cookie{
Name: "session_id",
Value: sessionID,
Path: "/",
HttpOnly: true, // JS 不可读取(防 XSS)
Secure: true, // 仅 HTTPS 传输
SameSite: http.SameSiteLaxMode, // 防 CSRF
MaxAge: 86400, // 过期时间(秒)
})
}
// 读取 Cookie
func getSessionID(r *http.Request) (string, error) {
cookie, err := r.Cookie("session_id")
if err != nil {
return "", err
}
return cookie.Value, nil
}
// 删除 Cookie
func clearSessionCookie(w http.ResponseWriter) {
http.SetCookie(w, &http.Cookie{
Name: "session_id",
Value: "",
Path: "/",
MaxAge: -1, // 立即过期
})
}
10.2 服务端 Session 存储
// 内存存储(仅开发环境,不支持多实例部署)
type MemorySessionStore struct {
mu sync.RWMutex
sessions map[string]*Session
}
type Session struct {
ID string
Data map[string]interface{}
CreatedAt time.Time
ExpiresAt time.Time
}
func (store *MemorySessionStore) Get(id string) (*Session, bool) {
store.mu.RLock()
defer store.mu.RUnlock()
s, ok := store.sessions[id]
if !ok || time.Now().After(s.ExpiresAt) {
return nil, false
}
return s, true
}
func (store *MemorySessionStore) Create() *Session {
store.mu.Lock()
defer store.mu.Unlock()
id := generateSessionID()
s := &Session{
ID: id,
Data: make(map[string]interface{}),
CreatedAt: time.Now(),
ExpiresAt: time.Now().Add(24 * time.Hour),
}
store.sessions[id] = s
return s
}
func generateSessionID() string {
b := make([]byte, 32)
rand.Read(b)
return base64.URLEncoding.EncodeToString(b)
}
10.3 使用 gorilla/sessions(推荐)
import "github.com/gorilla/sessions"
// Cookie Store(Session 数据加密存储在 Cookie 中)
var store = sessions.NewCookieStore([]byte("secret-key"))
// 或使用 Redis/数据库后端
// var store = redistore.NewRediStore(10, "tcp", "localhost:6379", "", []byte("secret-key"))
func loginHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
// 认证逻辑...
session.Values["user_id"] = user.ID
session.Values["role"] = user.Role
session.Options.MaxAge = 86400 * 7 // 7 天
session.Save(r, w)
}
func protectedHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
userID, ok := session.Values["user_id"].(int64)
if !ok {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}
// 已认证,继续处理...
}
func logoutHandler(w http.ResponseWriter, r *http.Request) {
session, _ := store.Get(r, "session-name")
session.Options.MaxAge = -1 // 删除 Session
session.Save(r, w)
http.Redirect(w, r, "/", http.StatusSeeOther)
}
10.4 Redis Session 存储(生产环境推荐)
import "github.com/redis/go-redis/v9"
type RedisSessionStore struct {
client *redis.Client
prefix string
ttl time.Duration
}
func (s *RedisSessionStore) Get(ctx context.Context, sessionID string) (map[string]interface{}, error) {
data, err := s.client.Get(ctx, s.prefix+sessionID).Bytes()
if err == redis.Nil {
return nil, nil
}
var session map[string]interface{}
json.Unmarshal(data, &session)
return session, err
}
func (s *RedisSessionStore) Set(ctx context.Context, sessionID string, data map[string]interface{}) error {
b, _ := json.Marshal(data)
return s.client.Set(ctx, s.prefix+sessionID, b, s.ttl).Err()
}
func (s *RedisSessionStore) Delete(ctx context.Context, sessionID string) error {
return s.client.Del(ctx, s.prefix+sessionID).Err()
}
十一、Web 框架对比(Gin、Echo 等)
11.1 标准库 vs 框架选型参考
| 特性 | net/http (1.22+) | Gin | Echo | Chi |
|---|---|---|---|---|
| 路由参数 | 支持 | 支持 | 支持 | 支持 |
| 方法路由 | 支持 | 支持 | 支持 | 支持 |
| 路由分组 | 不支持 | 支持 | 支持 | 支持 |
| 中间件 | 手动链 | 内置 | 内置 | 内置 |
| 参数绑定 | 手动 | 自动 | 自动 | 手动 |
| 验证 | 手动 | 集成 | 集成 | 手动 |
| 性能 | 高 | 极高 | 极高 | 高 |
| 依赖 | 零 | 较少 | 较少 | 极少 |
| 学习曲线 | 低 | 低 | 低 | 低 |
选型建议:
- 小型项目/微服务:标准库 net/http(Go 1.22+) 或 Chi
- 中大型 API 项目:Gin(生态最大、社区最活跃)
- 需要极致性能:Gin 或 Echo
- 偏好标准库风格:Chi(完全兼容 net/http)
11.2 Gin 框架核心用法
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default() // 包含 Logger 和 Recovery 中间件
// 路由分组
api := r.Group("/api/v1")
api.Use(authMiddleware()) // 组级中间件
{
api.GET("/users", listUsers)
api.GET("/users/:id", getUser)
api.POST("/users", createUser)
api.PUT("/users/:id", updateUser)
api.DELETE("/users/:id", deleteUser)
}
r.Run(":8080")
}
// Handler 使用 gin.Context
func getUser(c *gin.Context) {
id := c.Param("id") // 路径参数
name := c.Query("name") // 查询参数
page := c.DefaultQuery("page", "1")
user, err := findUser(id)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
return
}
c.JSON(http.StatusOK, gin.H{"data": user})
}
// 参数绑定与验证
type CreateUserInput struct {
Name string `json:"name" binding:"required,min=2,max=50"`
Email string `json:"email" binding:"required,email"`
Password string `json:"password" binding:"required,min=8"`
}
func createUser(c *gin.Context) {
var input CreateUserInput
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 创建用户...
c.JSON(http.StatusCreated, gin.H{"data": user})
}
// Gin 中间件
func authMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
return
}
claims, err := parseToken(strings.TrimPrefix(token, "Bearer "))
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
return
}
c.Set("user_id", claims.UserID) // 存入上下文
c.Next() // 继续处理
}
}
11.3 Echo 框架核心用法
import "github.com/labstack/echo/v4"
func main() {
e := echo.New()
// 内置中间件
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CORS())
e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))
// 路由分组
api := e.Group("/api/v1")
api.Use(jwtMiddleware)
api.GET("/users", listUsers)
api.GET("/users/:id", getUser)
api.POST("/users", createUser)
e.Logger.Fatal(e.Start(":8080"))
}
func getUser(c echo.Context) error {
id := c.Param("id")
user, err := findUser(id)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, "user not found")
}
return c.JSON(http.StatusOK, user)
}
// 参数绑定
type CreateUserInput struct {
Name string `json:"name" validate:"required"`
Email string `json:"email" validate:"required,email"`
}
func createUser(c echo.Context) error {
var input CreateUserInput
if err := c.Bind(&input); err != nil {
return err
}
if err := c.Validate(&input); err != nil {
return err
}
// ...
return c.JSON(http.StatusCreated, user)
}
11.4 Chi 框架(标准库兼容风格)
import "github.com/go-chi/chi/v5"
func main() {
r := chi.NewRouter()
// 中间件(标准 net/http 中间件完全兼容)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
r.Route("/api/v1", func(r chi.Router) {
r.Use(jwtAuth) // 组级中间件
r.Route("/users", func(r chi.Router) {
r.Get("/", listUsers) // GET /api/v1/users
r.Post("/", createUser) // POST /api/v1/users
r.Route("/{id}", func(r chi.Router) {
r.Get("/", getUser) // GET /api/v1/users/{id}
r.Put("/", updateUser) // PUT /api/v1/users/{id}
r.Delete("/", deleteUser)
})
})
})
http.ListenAndServe(":8080", r)
}
// Handler 签名完全是标准 net/http
func getUser(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
// ...
}
十二、Context 在 Web 开发中的应用
12.1 请求级 Context
func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() // 请求取消时自动 cancel
// 派生超时 Context
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
result, err := queryDatabase(ctx, "SELECT ...")
if err != nil {
if ctx.Err() == context.DeadlineExceeded {
http.Error(w, "Request timeout", http.StatusGatewayTimeout)
return
}
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
respondJSON(w, http.StatusOK, result)
}
12.2 在中间件中传递值
type contextKey string
const userKey contextKey = "user"
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, err := authenticate(r)
if err != nil {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), userKey, user)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func profileHandler(w http.ResponseWriter, r *http.Request) {
user := r.Context().Value(userKey).(*User)
respondJSON(w, http.StatusOK, user)
}
十三、RPC 与 gRPC
13.1 标准库 net/rpc
type MathService struct{}
type Args struct {
A, B int
}
func (m *MathService) Add(args *Args, reply *int) error {
*reply = args.A + args.B
return nil
}
// 服务端
func startRPCServer() {
rpc.Register(new(MathService))
rpc.HandleHTTP()
listener, _ := net.Listen("tcp", ":1234")
http.Serve(listener, nil)
}
// 客户端
func rpcClient() {
client, _ := rpc.DialHTTP("tcp", "localhost:1234")
var reply int
client.Call("MathService.Add", &Args{7, 8}, &reply)
fmt.Printf("7 + 8 = %d\n", reply) // 15
}
13.2 gRPC
// user.proto
syntax = "proto3";
package user;
service UserService {
rpc GetUser (GetUserRequest) returns (UserResponse);
rpc ListUsers (ListUsersRequest) returns (stream UserResponse);
}
message GetUserRequest { int64 id = 1; }
message UserResponse {
int64 id = 1;
string name = 2;
string email = 3;
}
// 服务端
type userServer struct {
pb.UnimplementedUserServiceServer
}
func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.UserResponse, error) {
return &pb.UserResponse{Id: req.Id, Name: "Alice", Email: "alice@example.com"}, nil
}
func main() {
lis, _ := net.Listen("tcp", ":50051")
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, &userServer{})
s.Serve(lis)
}
十四、数据库操作
14.1 database/sql
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func initDB() (*sql.DB, error) {
db, err := sql.Open("mysql", "user:pass@tcp(localhost:3306)/dbname?parseTime=true")
if err != nil {
return nil, err
}
db.SetMaxOpenConns(25) // 最大打开连接数
db.SetMaxIdleConns(5) // 最大空闲连接数
db.SetConnMaxLifetime(5 * time.Minute) // 连接最大存活时间
return db, db.Ping()
}
// 查询
var user User
err := db.QueryRowContext(ctx, "SELECT id, name FROM users WHERE id = ?", id).
Scan(&user.ID, &user.Name)
// 事务
tx, err := db.BeginTx(ctx, nil)
defer tx.Rollback()
tx.ExecContext(ctx, "UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID)
tx.ExecContext(ctx, "UPDATE accounts SET balance = balance + ? WHERE id = ?", amount, toID)
err = tx.Commit()
十五、优雅关闭(Graceful Shutdown)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)
server := &http.Server{Addr: ":8080", Handler: mux}
go func() {
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("HTTP 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(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited gracefully")
}
十六、DNS 与地址解析
// DNS 查询
ips, _ := net.LookupIP("example.com")
names, _ := net.LookupAddr("93.184.216.34")
cname, _ := net.LookupCNAME("www.example.com")
mxRecords, _ := net.LookupMX("example.com")
// 解析地址
host, port, _ := net.SplitHostPort("localhost:8080")
addr, _ := net.ResolveTCPAddr("tcp", "localhost:8080")
十七、常用第三方库速查
| 用途 | 库名 | 说明 |
|---|---|---|
| HTTP 路由 | chi, gorilla/mux | 功能丰富的路由器 |
| Web 框架 | gin, echo, fiber | 高性能 Web 框架 |
| ORM | gorm, ent, sqlx | 数据库 ORM |
| 验证 | go-playground/validator | 结构体字段验证 |
| 配置 | viper, envconfig | 配置管理 |
| 日志 | slog, zap, zerolog | 结构化日志 |
| JWT | golang-jwt/jwt | JWT 认证 |
| WebSocket | gorilla/websocket, nhooyr/websocket | WebSocket 支持 |
| HTTP 客户端 | resty, go-retryablehttp | 增强版 HTTP 客户端 |
| gRPC | google.golang.org/grpc | gRPC 框架 |
| Swagger | swaggo/swag | API 文档生成 |
| CSRF | gorilla/csrf | CSRF 防护 |
| Session | gorilla/sessions | Session 管理 |
| HTML 清洗 | bluemonday | XSS 防护 |
| 限流 | x/time/rate, tollbooth | 请求限流 |
十八、项目结构最佳实践
myapp/
├── cmd/
│ └── server/
│ └── main.go # 入口:组装依赖、启动服务
├── internal/
│ ├── handler/ # HTTP 处理器(Controller 层)
│ │ ├── user.go
│ │ └── middleware.go
│ ├── service/ # 业务逻辑(Service 层)
│ │ └── user.go
│ ├── repository/ # 数据访问(DAO 层)
│ │ └── user.go
│ └── model/ # 数据模型
│ └── user.go
├── pkg/ # 可导出的公共包
│ ├── response/
│ └── validator/
├── api/ # API 定义(OpenAPI/Proto)
├── configs/ # 配置文件
├── migrations/ # 数据库迁移
├── static/ # 静态资源
├── templates/ # 模板文件
├── go.mod
└── go.sum
十九、速查备忘
HTTP 状态码常量
http.StatusOK // 200
http.StatusCreated // 201
http.StatusNoContent // 204
http.StatusBadRequest // 400
http.StatusUnauthorized // 401
http.StatusForbidden // 403
http.StatusNotFound // 404
http.StatusMethodNotAllowed // 405
http.StatusConflict // 409
http.StatusUnprocessableEntity // 422
http.StatusTooManyRequests // 429
http.StatusInternalServerError // 500
http.StatusBadGateway // 502
http.StatusServiceUnavailable // 503
net 包关键接口
type Conn interface {
Read(b []byte) (n int, err error)
Write(b []byte) (n int, err error)
Close() error
LocalAddr() Addr
RemoteAddr() Addr
SetDeadline(t time.Time) error
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
}
type Listener interface {
Accept() (Conn, error)
Close() error
Addr() Addr
}
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/6741