Go Web and Network Programming Essentials
Knowledge Sources:
- 《Building Web Apps with Go》
- 《Go API Programming》
- 《Go Web Programming》(Go Web Programming, Sau Sheong Chang)
- 《Go Network Programming》(Network Programming with Go)
- 《Mastering Go Web Services》
- 《Go Web Programming》(2013, Xie Mengjun/astaxie)
I. Deep Dive into the net/http Package (Handler Interface, ServeMux, Middleware Chain)
1.1 Handler Interface – The Core of Everything
The core abstraction for Go HTTP handling is a single interface:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
The entire design philosophy of net/http revolves around this interface. Routers, middleware, static file servers – all are implementations of Handler.
HandlerFunc Adapter – Functions can also be Handlers:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
This is a classic Go design pattern: enabling ordinary functions to satisfy an interface through type conversion.
// 以下两种注册方式完全等价
http.Handle("/hello", http.HandlerFunc(helloFunc))
http.HandleFunc("/hello", helloFunc)
Custom Handler Struct – Carrying Dependency Injection:
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 Routing Mechanism
DefaultServeMux vs. Custom 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)
Routing Matching Rules:
- Exact matches take precedence over prefix matches
- Patterns ending with / are subtree matches (prefix matches)
- Patterns not ending with / are exact matches
- Longest match takes precedence
Go 1.22+ Enhanced Routing – Finally Supports Methods and Path Parameters:
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") // 提取路径参数
// ...
}
Precedence Rules (Go 1.22+):
- More specific patterns take precedence (GET /users/{id} takes precedence over /users/{id})
- Patterns with methods take precedence over those without methods
- If two patterns conflict and cannot be compared, registration will panic
1.3 Custom Server Configuration
In production environments, a custom http.Server must be used:
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 Support:
// 方式一:直接使用证书文件
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 Middleware Pattern
Middleware is a function that takes a Handler and returns a new Handler, forming an onion model:
type Middleware func(http.Handler) http.Handler
Logging Middleware:
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 Middleware (panic recovery):
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)
})
}
Middleware Chaining:
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 Middleware:
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)
})
}
II. HTTP Server Implementation Principles
2.1 Source Code Analysis of Startup Process
The complete call chain of 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
Key Design: One goroutine per request. Go's goroutines are very lightweight (initial stack is only 2KB), allowing tens of thousands of concurrent connections to be handled simultaneously without callbacks or thread pools.
2.2 Detailed Connection Handling
// 简化版的 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 The Three-Layer Interface of 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) Implementation
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()
}
}
}
III. RESTful API Design and Implementation
3.1 Resource Design and Route Registration
// 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 Unified Response Format
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 Handling Techniques
// 结构体 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 Pagination, Filtering, and Sorting
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 Request Validation
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 Version Management
// 方式一: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))
})
}
IV. WebSocket Programming
4.1 Basic WebSocket Service
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 Chat Room Hub Pattern
This is a classic architecture for WebSocket applications, derived from the official gorilla/websocket example:
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 (A More Modern Choice)
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, "")
}
V. TCP/UDP Network Programming
5.1 TCP Server
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 Client
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 Length-Prefixed Message Protocol
TCP is a stream protocol, requiring manual handling of message boundaries. A common solution is length prefixing:
// 发送: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 Programming
// 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 Concurrent Connection Limit
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)
}()
}
}
VI. HTTP Client and Connection Pool Management
6.1 Custom Transport (Connection Pool Core)
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,
}
Key Points:
- http.DefaultClient has no timeout and must never be used in production.
- http.Transport internally maintains a connection pool; it must be reused, not recreated for every request.
- MaxIdleConnsPerHost defaults to 2; it must be increased for high-concurrency scenarios.
- Always read and close resp.Body; otherwise, the connection cannot be returned to the connection pool.
6.2 Constructing Requests
// 带 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 With Retries and Exponential Backoff
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 Connection Pool Monitoring
// 获取连接池状态(用于 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))
VII. Web Security (CSRF, XSS, SQL Injection Protection, JWT Authentication)
7.1 CSRF Protection
Principle: Cross-Site Request Forgery (CSRF) exploits the browser's mechanism of automatically sending cookies. The defense method is to embed a server-generated random Token in the form and validate it upon submission.
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)
})
}
Recommended to use the gorilla/csrf library:
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 Protection
Core Principle: Never trust user input; always escape it upon output.
// 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 Injection Protection
// 错误写法:直接拼接 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 Complete JWT Authentication Implementation
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 Password Security
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 Request Rate Limiting
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
}
VIII. Template Engine (html/template)
8.1 Basic Usage
// 解析并渲染模板
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 Complete Template Syntax
<!-- 变量输出(自动 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 Custom Template Functions
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 Pattern (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)
}
IX. Static File Serving
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 File Upload
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 File Download
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)
}
X. Session/Cookie Management
10.1 Cookie Operations
// 设置 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 Server-Side Session Storage
// 内存存储(仅开发环境,不支持多实例部署)
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 Using gorilla/sessions (Recommended)
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 Storage (Recommended for Production)
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()
}
XI. Web Framework Comparison (Gin, Echo, etc.)
11.1 Standard Library vs. Framework Selection Reference
| Feature | net/http (1.22+) | Gin | Echo | Chi |
|---|---|---|---|---|
| Route Parameters | Supported | Supported | Supported | Supported |
| Method Routing | Supported | Supported | Supported | Supported |
| Route Grouping | Not Supported | Supported | Supported | Supported |
| Middleware | Manual Chain | Built-in | Built-in | Built-in |
| Parameter Binding | Manual | Automatic | Automatic | Manual |
| Validation | Manual | Integrated | Integrated | Manual |
| Performance | High | Very High | Very High | High |
| Dependencies | Zero | Few | Few | Very Few |
| Learning Curve | Low | Low | Low | Low |
Selection Advice:
- Small projects/microservices: Standard library net/http (Go 1.22+) or Chi
- Medium to large API projects: Gin (largest ecosystem, most active community)
- For extreme performance: Gin or Echo
- Preferring standard library style: Chi (fully compatible with net/http)
11.2 Gin Framework Core Usage
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 Framework Core Usage
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 Framework (Standard Library Compatible Style)
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")
// ...
}
XII. Context Application in Web Development
12.1 Request-Level 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 Passing Values in Middleware
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)
}
XIII. RPC and gRPC
13.1 Standard Library 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)
}
XIV. Database Operations
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()
XV. 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")
}
XVI. DNS and Address Resolution
// 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")
XVII. Quick Reference for Common Third-Party Libraries
| Purpose | Library Name | Description |
|---|---|---|
| HTTP Routing | chi, gorilla/mux | Feature-rich router |
| Web Framework | gin, echo, fiber | High-performance Web framework |
| ORM | gorm, ent, sqlx | Database ORM |
| Validation | go-playground/validator | Struct field validation |
| Configuration | viper, envconfig | Configuration management |
| Logging | slog, zap, zerolog | Structured logging |
| JWT | golang-jwt/jwt | JWT authentication |
| WebSocket | gorilla/websocket, nhooyr/websocket | WebSocket support |
| HTTP Client | resty, go-retryablehttp | Enhanced HTTP client |
| gRPC | google.golang.org/grpc | gRPC framework |
| Swagger | swaggo/swag | API documentation generation |
| CSRF | gorilla/csrf | CSRF protection |
| Session | gorilla/sessions | Session management |
| HTML Sanitization | bluemonday | XSS protection |
| Rate Limiting | x/time/rate, tollbooth | Request rate limiting |
XVIII. Project Structure Best Practices
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
XIX. Quick Reference Memo
HTTP Status Code Constants
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
Key Interfaces in net Package
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