跳到主要内容

net/http 详解

问题

Go 的 net/http 包怎么使用?Handler 接口核心是什么?如何实现中间件?

答案

Handler 接口

Go HTTP 服务的核心是 http.Handler 接口:

type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}

任何实现了 ServeHTTP 方法的类型都可以作为 HTTP 处理器。

创建 HTTP Server

// 方式 1:HandlerFunc(最常用)
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
http.ListenAndServe(":8080", nil) // nil 使用 DefaultServeMux

// 方式 2:自定义 Handler
type HelloHandler struct{}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
}

http.Handle("/hello", &HelloHandler{})

// 方式 3:自定义 ServeMux(推荐)
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", getUser) // Go 1.22+ 增强路由
mux.HandleFunc("POST /users", createUser)

server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 120 * time.Second,
}
server.ListenAndServe()

Go 1.22 增强路由

mux := http.NewServeMux()

// 支持方法匹配
mux.HandleFunc("GET /users", listUsers)
mux.HandleFunc("POST /users", createUser)

// 支持路径参数
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // 获取路径参数
fmt.Fprintf(w, "User: %s", id)
})

// 通配符
mux.HandleFunc("GET /files/{path...}", func(w http.ResponseWriter, r *http.Request) {
path := r.PathValue("path") // 匹配剩余路径
fmt.Fprintf(w, "File: %s", path)
})

中间件

中间件本质是函数装饰器——接收 Handler,返回加强版 Handler:

// 中间件类型
type Middleware func(http.Handler) http.Handler

// 日志中间件
func logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r) // 调用下一个 Handler
log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start))
})
}

// 认证中间件
func auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return // 不调用 next,终止链
}
next.ServeHTTP(w, r)
})
}

// 组合中间件
handler := logging(auth(mux))
http.ListenAndServe(":8080", handler)

HTTP Client

// 默认 Client(不推荐生产使用,没有超时)
resp, err := http.Get("https://api.example.com/data")

// 自定义 Client(推荐)
client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
},
}

// 带 Context 的请求
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
req.Header.Set("Authorization", "Bearer "+token)
resp, err := client.Do(req)
if err != nil { return err }
defer resp.Body.Close()
HTTP Client 注意事项
  1. 不要使用 http.DefaultClient——没有超时设置
  2. 必须关闭 resp.Body——否则连接不会被复用
  3. 读完 Body——io.Copy(io.Discard, resp.Body) 确保连接复用
  4. Client 应该复用——不要每次请求都创建新 Client

优雅关闭

server := &http.Server{Addr: ":8080", Handler: mux}

go func() {
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()

// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit

// 优雅关闭:等待已有请求完成
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
server.Shutdown(ctx)

常见面试问题

Q1: http.Handler 和 http.HandlerFunc 的关系?

答案

http.HandlerFunc 是一个函数类型,实现了 http.Handler 接口:

type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}

这是一个适配器模式——让普通函数满足 Handler 接口。

Q2: Go 1.22 的路由增强解决了什么问题?

答案

之前 http.NewServeMux 不支持:

  • HTTP 方法匹配(只能在 Handler 内部判断)
  • 路径参数(/users/:id 必须用第三方库)

Go 1.22 添加了方法匹配和路径参数,减少了对 gorilla/mux、chi 等第三方路由库的依赖。

Q3: 如何避免 goroutine 泄漏在 HTTP Handler 中?

答案

使用 r.Context()——当客户端断开连接时,Context 自动取消:

func handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
result, err := longOperation(ctx) // 传递 Context
if ctx.Err() != nil {
return // 客户端已断开
}
json.NewEncoder(w).Encode(result)
}

相关链接