Go net/http 标准库基础
一、知识点总结
1.1 net/http 包的设计理念
Go 的 net/http 包是标准库中最具代表性的设计之一,它遵循接口驱动的设计哲学。整个包围绕两个核心接口展开:
Handler接口:处理 HTTP 请求的核心抽象ResponseWriter+*Request:请求-响应的载体
这种设计的精妙之处在于解耦 。你可以用任何自定义类型实现 Handler 接口,然后注册到服务端,不需要继承任何类或框架提供的基类。这是 Go "接口即契约" 思想的典型体现。
1.2 Handler 接口
go
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
任何实现了 ServeHTTP 方法的类型都可以作为 HTTP 处理器。标准库提供了 HandlerFunc 类型作为函数适配器,让普通函数也能当作 Handler 使用:
go
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
这个技巧非常经典------它让函数 和实现了接口的结构体可以互换使用,既保留了接口的扩展性,又提供了函数的便利性。
1.3 ServeMux ------ 路由多路复用器
ServeMux 是标准库内置的路由分发器,负责将 URL 匹配到对应的 Handler:
- 注册路由:
mux.Handle("/path", handler)或mux.HandleFunc("/path", fn) - 匹配规则:最长前缀匹配 ,例如
/api/users比/api/更优先 - 路径规范:会自动处理冗余斜杠(
//→/) - 区分大小写:URL 路径匹配是大小写敏感的
1.4 Server 结构体与 ListenAndServe
http.Server 是一个可配置的服务端结构体:
go
type Server struct {
Addr string // 监听地址,如 ":8080"
Handler Handler // 若nil则使用 DefaultServeMux
ReadTimeout time.Duration // 读取请求超时
WriteTimeout time.Duration // 写入响应超时
IdleTimeout time.Duration // Keep-Alive 连接空闲超时
MaxHeaderBytes int // 请求头最大字节数
}
http.ListenAndServe 是快捷函数,内部创建一个默认的 Server。生产环境建议显式配置 Server,以便控制超时行为------没有超时的服务是生产事故的温床。
1.5 ResponseWriter 与 Request
ResponseWriter 用于构造响应:
WriteHeader(statusCode)------ 写入状态码(只能调用一次,且必须在 Write 之前)Write([]byte)------ 写入响应体(若未调用 WriteHeader,自动写入 200 OK)Header()------ 获取响应头http.Header(必须在 WriteHeader 前设置)
Request 封装了请求的所有信息:
r.Method------ HTTP 方法(GET/POST/PUT/DELETE 等)r.URL------ 解析后的 URL 对象(含 Path、RawQuery、Scheme 等)r.Header------ 请求头r.Body------ 请求体(io.ReadCloser,必须关闭)r.FormValue(key)------ 获取表单值(自动解析 query string 和 POST body)
1.6 重要注意事项
- WriteHeader 只能调用一次:重复调用会触发 panic 或无效
- Header 必须在 WriteHeader 前设置:否则不生效
- 请求体必须关闭 :
defer r.Body.Close(),否则连接池会泄漏 - 没有路由参数 :标准库 ServeMux 不支持
/users/:id这种路由参数,需要手动从r.URL.Path解析或使用第三方框架 - 默认无超时 :
ListenAndServe没有配置任何超时,生产环境必须显式设置
二、练习代码
示例 1:最基础的 HTTP 服务
go
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// 使用 HandleFunc 注册路由,底层自动包装为 HandlerFunc
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, Go HTTP! Method=%s, Path=%s\n", r.Method, r.URL.Path)
})
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, `{"status":"ok"}`)
})
log.Println("Server starting on :8080")
// 等价于 http.ListenAndServe(":8080", nil)
// nil 表示使用 DefaultServeMux
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
示例 2:自定义 Handler 结构体
go
package main
import (
"fmt"
"log"
"net/http"
"sync/atomic"
)
// CounterHandler 是一个自定义 Handler,统计访问次数
type CounterHandler struct {
count atomic.Int64
}
func (h *CounterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 原子递增,线程安全
c := h.count.Add(1)
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "你是第 %d 位访客\n", c)
}
func main() {
mux := http.NewServeMux()
counter := &CounterHandler{}
// 注册自定义 Handler
mux.Handle("/counter", counter)
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "访问 /counter 查看计数")
})
log.Println("Server on :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
示例 3:带超时的生产级 Server 配置
go
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello with timeout protection!")
})
mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
// 模拟慢请求:5秒延迟
time.Sleep(5 * time.Second)
fmt.Fprintln(w, "slow response")
})
// 生产环境推荐:显式配置 Server,设置超时
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 3 * time.Second, // 读取完整请求的最大时间
WriteTimeout: 5 * time.Second, // 写入响应的最大时间
IdleTimeout: 60 * time.Second, // Keep-Alive 空闲超时
}
// 优雅关闭:在独立 goroutine 中启动服务
go func() {
log.Println("Server starting on :8080")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
// 监听系统信号,实现优雅关闭
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
log.Println("Shutting down server...")
// 给正在处理的请求 5 秒缓冲时间
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Shutdown error: %v", err)
}
log.Println("Server gracefully stopped")
}
示例 4:ResponseWriter 使用要点演示
go
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
// 正确设置 Header 的顺序演示
mux.HandleFunc("/headers", func(w http.ResponseWriter, r *http.Request) {
// 1. 先设置响应头
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Custom-Header", "myvalue")
// 2. 再调用 WriteHeader(可选,Write 会自动调)
w.WriteHeader(http.StatusCreated) // 201
// 3. 最后写入响应体
fmt.Fprintln(w, `{"message":"created"}`)
// 下面这行无效:WriteHeader 已调用,再设置 Header 不生效
w.Header().Set("X-Late-Header", "too-late")
})
// 演示请求信息读取
mux.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "Method: %s\n", r.Method)
fmt.Fprintf(w, "URL Path: %s\n", r.URL.Path)
fmt.Fprintf(w, "Query: %s\n", r.URL.RawQuery)
fmt.Fprintf(w, "User-Agent: %s\n", r.UserAgent())
fmt.Fprintf(w, "Host: %s\n", r.Host)
fmt.Fprintf(w, "RemoteAddr: %s\n", r.RemoteAddr)
})
log.Println("Server on :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}