Go: Go HTTP 服务开发
最后更新:2026-08-26
Go 标准库的 net/http 包功能完备,无需第三方框架就能构建生产级 Web 服务。
当你的团队决定"不引入任何 Web framework,就用标准库"时,你能否像 Gin 或 Echo 一样写出清晰的路由和中间件?这节课你将掌握 Go HTTP 服务的全部核心技术。
1. 你将学到
http.ListenAndServe启动服务Handler接口与HandlerFunc适配器ServeMux路由注册- Go 1.22 增强路由:method + 路径模式 + 路径参数
- JSON 响应编码
- 请求参数解析(查询参数、表单、路径参数)
- 静态文件服务
2. 一个后端工程师的真实故事
(1) 痛点:一个简单的 API,选了 Gin,三个月后升级卡住
Alice 是后端团队的一员,她需要搭一个用户管理的 REST API:
"我用 Gin 框架写了 3 个路由:GET /users、POST /users、GET /users/:id。但三个月后 Go 1.22 发布了,标准库原生支持了方法和路径参数。现在我想去掉 Gin dependency,但所有 handler 签名都要改——gin.Context vs http.ResponseWriter。老板说'为了少一个依赖重构几百行代码,不值得'。"
她当时的选择:
// Gin 依赖版本(三个月后想迁移)
r := gin.Default()
r.GET("/users", listUsers) // gin.Context
r.POST("/users", createUser) // gin.Context
r.GET("/users/:id", getUser) // gin.Context
// 想迁到标准库?所有 handler 签名都要改!
(2) Go 1.22 的解法:标准库原生路由
// 标准库版本(Go 1.22+,无需任何依赖)
package main
import (
"encoding/json"
"log"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
var users = []User{{ID: 1, Name: "Alice"}}
func main() {
mux := http.NewServeMux()
// Go 1.22 增强路由:method + 路径模式 + 路径参数
mux.HandleFunc("GET /users", listUsers)
mux.HandleFunc("POST /users", createUser)
mux.HandleFunc("GET /users/{id}", getUser)
log.Println("服务启动于 :8080")
log.Fatal(http.ListenAndServe(":8080", mux))
}
// 标准 handler 签名:http.ResponseWriter + *http.Request
func listUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func createUser(w http.ResponseWriter, r *http.Request) {
var u User
if err := json.NewDecoder(r.Body).Decode(&u); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
u.ID = len(users) + 1
users = append(users, u)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(u)
}
func getUser(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id") // 路径参数!
// 查找 user...
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(User{ID: 1, Name: "Alice"})
}
(3) 收益:Gin 风格 vs 标准库(Go 1.22)
| 特性 | Gin(第三方) | 标准库(Go < 1.22) | 标准库(Go 1.22+) |
|---|---|---|---|
| 路径参数 | :id ✅ |
❌ 需手动解析 | {id} ✅ |
| 方法路由 | ✅ | ❌ Handler 内判断 | "GET /path" ✅ |
| JSON 响应 | c.JSON() |
手动设置 Header | 手动设置 Header |
| 依赖 | 1 个外部包 | 0 | 0 |
| 性能 | 略慢(反射) | 原生 | 原生 |
net/http 增强路由已经足够大多数 Web 项目使用。如果你不需要框架特有的功能(如自动绑定/验证、丰富的中间件生态),优先考虑标准库。
3. HTTP 基础
▶ 示例:最简单的 HTTP 服务
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Query().Get("name"))
}
func main() {
http.HandleFunc("/hello", helloHandler)
log.Println("服务启动于 :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
测试:
$ curl "http://localhost:8080/hello?name=Alice"
Hello, Alice!
(1) 核心类型
| 类型 | 说明 |
|---|---|
http.ResponseWriter |
写入 HTTP 响应的接口 |
*http.Request |
HTTP 请求,包含 URL/Header/Body/Form |
http.Handler |
接口:ServeHTTP(w, r) |
http.HandlerFunc |
函数适配器:将普通函数转为 Handler |
http.ServeMux |
路由复用器 |
(2) Handler 接口详解
package main
import (
"fmt"
"log"
"net/http"
)
// 方式 1:实现 Handler 接口
type Greeter struct {
Greeting string
}
func (g *Greeter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "%s, %s!", g.Greeting, r.URL.Path[1:])
}
// 方式 2:HandlerFunc 适配器
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %s!", r.URL.Query().Get("name"))
}
func main() {
mux := http.NewServeMux()
// 结构体 Handler
mux.Handle("/greet", &Greeter{Greeting: "Welcome"})
// 函数 Handler(HandlerFunc 自动转换)
mux.HandleFunc("/hello", helloHandler)
log.Print(http.ListenAndServe(":8080", mux))
}
4. Go 1.22 增强路由
▶ 示例:方法 + 路径模式 + 路径参数
package main
import (
"encoding/json"
"log"
"net/http"
)
type Item struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
var items = []Item{
{ID: 1, Name: "Laptop", Price: 999.99},
{ID: 2, Name: "Mouse", Price: 29.99},
}
func main() {
mux := http.NewServeMux()
// Go 1.22 方法 + 路径模式
mux.HandleFunc("GET /items", listItems)
mux.HandleFunc("POST /items", createItem)
mux.HandleFunc("GET /items/{id}", getItem)
mux.HandleFunc("PUT /items/{id}", updateItem)
mux.HandleFunc("DELETE /items/{id}", deleteItem)
// 通配符后缀:路径前缀匹配
mux.HandleFunc("GET /items/{path...}", wildcardHandler)
log.Print(http.ListenAndServe(":8080", mux))
}
func listItems(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(items)
}
func createItem(w http.ResponseWriter, r *http.Request) {
var item Item
if err := json.NewDecoder(r.Body).Decode(&item); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
item.ID = len(items) + 1
items = append(items, item)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(item)
}
func getItem(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// 查找 item...
_ = id
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(items[0])
}
func updateItem(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
_ = id
// 更新逻辑...
w.WriteHeader(http.StatusNoContent)
}
func deleteItem(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
_ = id
// 删除逻辑...
w.WriteHeader(http.StatusNoContent)
}
func wildcardHandler(w http.ResponseWriter, r *http.Request) {
path := r.PathValue("path")
w.Header().Set("Content-Type", "text/plain")
http.Error(w, "Not found: "+path, http.StatusNotFound)
}
(1) Go 1.22 路由模式对比
| 模式 | Go < 1.22 | Go 1.22+ | 示例 |
|---|---|---|---|
| 方法匹配 | 不支持(Handler 内 if 判断) | 支持 | "GET /items" |
| 路径参数 | 不支持 | {name} 语法 |
"GET /items/{id}" |
| 通配符后缀 | 不支持 | {path...} |
"GET /static/{file...}" |
| 精确路径 | /items |
"GET /items" |
严格匹配 /items |
| 前缀匹配 | /items/ |
"GET /items/" |
匹配 /items/... |
▶ 示例:路由优先级
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
// 精确路径 > 前缀路径
mux.HandleFunc("GET /items", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "items list")
})
mux.HandleFunc("GET /items/{id}", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "item %s\n", r.PathValue("id"))
})
mux.HandleFunc("GET /items/featured", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "featured items")
})
log.Print(http.ListenAndServe(":8080", mux))
}
测试:
$ curl localhost:8080/items
items list
$ curl localhost:8080/items/42
item 42
$ curl localhost:8080/items/featured
featured items # 精确匹配优先于 {id} 通配
5. 请求与响应
▶ 示例:查询参数、表单、JSON
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
)
type Response struct {
Method string `json:"method"`
Path string `json:"path"`
Query interface{} `json:"query,omitempty"`
Form interface{} `json:"form,omitempty"`
JSON interface{} `json:"json,omitempty"`
}
func handler(w http.ResponseWriter, r *http.Request) {
resp := Response{
Method: r.Method,
Path: r.URL.Path,
}
// 查询参数
if r.Method == http.MethodGet {
resp.Query = r.URL.Query()
}
// 表单数据
if r.Method == http.MethodPost {
contentType := r.Header.Get("Content-Type")
switch {
case contentType == "application/x-www-form-urlencoded":
r.ParseForm()
resp.Form = r.Form
case contentType == "application/json":
var body interface{}
json.NewDecoder(r.Body).Decode(&body)
resp.JSON = body
}
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
log.Print(http.ListenAndServe(":8080", mux))
}
▶ 示例:JSON 响应工具函数
package main
import (
"encoding/json"
"log"
"net/http"
)
// JSON 响应工具函数
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
func getProduct(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
// 模拟查找
if id != "1" {
writeError(w, http.StatusNotFound, "product not found")
return
}
writeJSON(w, http.StatusOK, Product{ID: 1, Name: "Laptop", Price: 999.99})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /products/{id}", getProduct)
log.Print(http.ListenAndServe(":8080", mux))
}
sequenceDiagram
participant Client as HTTP 客户端
participant Mux as ServeMux
participant Handler as Handler
Client->>Mux: GET /products/1
Mux->>Mux: 路由匹配
Mux->>Handler: ServeHTTP(w, r)
Handler->>Handler: r.PathValue("id") → "1"
Handler->>Handler: writeJSON(w, 200, product)
Handler-->>Client: HTTP 200 + JSON body
(2) HTTP 状态码速查
| 代码 | 常量 | 用途 |
|---|---|---|
| 200 | http.StatusOK |
成功 |
| 201 | http.StatusCreated |
资源创建成功 |
| 204 | http.StatusNoContent |
成功但无响应体 |
| 400 | http.StatusBadRequest |
客户端请求错误 |
| 401 | http.StatusUnauthorized |
未认证 |
| 403 | http.StatusForbidden |
无权限 |
| 404 | http.StatusNotFound |
资源不存在 |
| 500 | http.StatusInternalServerError |
服务端内部错误 |
6. 静态文件服务
package main
import (
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
// 静态文件服务:/static/ 前缀 → ./static/ 目录
mux.Handle("GET /static/", http.StripPrefix("/static/",
http.FileServer(http.Dir("./static"))))
// 单文件
mux.Handle("GET /favicon.ico", http.FileServer(http.Dir("./static")))
log.Print(http.ListenAndServe(":8080", mux))
}
7. 完整示例:REST 风格笔记 API
// notes_api.go
package main
import (
"encoding/json"
"log"
"net/http"
"strconv"
"sync"
"time"
)
// ---------- Model ----------
type Note struct {
ID int `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ---------- Store ----------
type NoteStore struct {
mu sync.RWMutex
notes map[int]Note
nextID int
}
func NewNoteStore() *NoteStore {
return &NoteStore{
notes: make(map[int]Note),
nextID: 1,
}
}
func (s *NoteStore) Create(title, content string) Note {
s.mu.Lock()
defer s.mu.Unlock()
n := Note{
ID: s.nextID,
Title: title,
Content: content,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
s.nextID++
s.notes[n.ID] = n
return n
}
func (s *NoteStore) List() []Note {
s.mu.RLock()
defer s.mu.RUnlock()
result := make([]Note, 0, len(s.notes))
for _, n := range s.notes {
result = append(result, n)
}
return result
}
func (s *NoteStore) Get(id int) (Note, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
n, ok := s.notes[id]
return n, ok
}
func (s *NoteStore) Update(id int, title, content string) (Note, bool) {
s.mu.Lock()
defer s.mu.Unlock()
n, ok := s.notes[id]
if !ok {
return Note{}, false
}
n.Title = title
n.Content = content
n.UpdatedAt = time.Now()
s.notes[id] = n
return n, true
}
func (s *NoteStore) Delete(id int) bool {
s.mu.Lock()
defer s.mu.Unlock()
_, ok := s.notes[id]
if !ok {
return false
}
delete(s.notes, id)
return true
}
// ---------- API ----------
type NotesAPI struct {
store *NoteStore
}
func NewNotesAPI(store *NoteStore) *NotesAPI {
return &NotesAPI{store: store}
}
func (api *NotesAPI) Register(mux *http.ServeMux) {
mux.HandleFunc("GET /notes", api.ListNotes)
mux.HandleFunc("POST /notes", api.CreateNote)
mux.HandleFunc("GET /notes/{id}", api.GetNote)
mux.HandleFunc("PUT /notes/{id}", api.UpdateNote)
mux.HandleFunc("DELETE /notes/{id}", api.DeleteNote)
}
// 工具函数
func writeJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func writeError(w http.ResponseWriter, status int, message string) {
writeJSON(w, status, map[string]string{"error": message})
}
// ---------- Handlers ----------
func (api *NotesAPI) ListNotes(w http.ResponseWriter, r *http.Request) {
notes := api.store.List()
writeJSON(w, http.StatusOK, notes)
}
func (api *NotesAPI) CreateNote(w http.ResponseWriter, r *http.Request) {
var input struct {
Title string `json:"title"`
Content string `json:"content"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON body")
return
}
if input.Title == "" {
writeError(w, http.StatusBadRequest, "title is required")
return
}
note := api.store.Create(input.Title, input.Content)
writeJSON(w, http.StatusCreated, note)
}
func (api *NotesAPI) GetNote(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid note ID")
return
}
note, ok := api.store.Get(id)
if !ok {
writeError(w, http.StatusNotFound, "note not found")
return
}
writeJSON(w, http.StatusOK, note)
}
func (api *NotesAPI) UpdateNote(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid note ID")
return
}
var input struct {
Title string `json:"title"`
Content string `json:"content"`
}
if err := json.NewDecoder(r.Body).Decode(&input); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON body")
return
}
note, ok := api.store.Update(id, input.Title, input.Content)
if !ok {
writeError(w, http.StatusNotFound, "note not found")
return
}
writeJSON(w, http.StatusOK, note)
}
func (api *NotesAPI) DeleteNote(w http.ResponseWriter, r *http.Request) {
idStr := r.PathValue("id")
id, err := strconv.Atoi(idStr)
if err != nil {
writeError(w, http.StatusBadRequest, "invalid note ID")
return
}
if !api.store.Delete(id) {
writeError(w, http.StatusNotFound, "note not found")
return
}
w.WriteHeader(http.StatusNoContent)
}
func main() {
store := NewNoteStore()
api := NewNotesAPI(store)
mux := http.NewServeMux()
api.Register(mux)
log.Println("Note API 启动于 :8080")
log.Println("可用端点:")
log.Println(" GET /notes — 列出所有笔记")
log.Println(" POST /notes — 创建笔记")
log.Println(" GET /notes/{id} — 获取单个笔记")
log.Println(" PUT /notes/{id} — 更新笔记")
log.Println(" DELETE /notes/{id} — 删除笔记")
log.Fatal(http.ListenAndServe(":8080", mux))
}
http.Error(w, msg, code) 不会设置 Content-Type 为 JSON。如果你返回 JSON error,使用 json.NewEncoder(w).Encode(errResp) 并手动设置 Header()。标准库的 http.Error() 返回纯文本。
❓ 常见问题
mux.Handle("/path", handler) 和 mux.HandleFunc("/path", handlerFunc) 效果相同。r.PathValue("name") 获取 {name} 类型的路径参数。旧版本需要手动从 r.URL.Path 解析或使用第三方库。路径参数名必须与路由模式中的 {name} 一致。http.ListenAndServeTLS(":443", "cert.pem", "key.pem", mux)。server.Shutdown(ctx) 配合 os/signal 捕获中断信号。Shutdown 会等待所有活跃连接处理完成后再关闭。不要用 server.Close()——它会强制中断正在处理的请求。http.DefaultServeMux(全局默认路由)。推荐显式创建 http.NewServeMux() 避免污染全局路由——尤其是在测试中需要隔离路由时。📖 小节
http.ListenAndServe启动 HTTP 服务Handlerinterface +HandlerFunc适配器- Go 1.22 增强路由:
"METHOD /path/{param}"syntax r.PathValue("name")获取路径参数- JSON response:设置
Content-Type+json.NewEncoder - 请求解析:查询参数、表单、JSON Body
- 静态文件服务:
http.FileServer - 状态码:使用
http.Status*constant
📝 作业
-
基础题(难度⭐):创建一个简单的 HTTP 服务,注册 3 个路由:
GET /time返回当前时间 JSON、GET /health返回{"status": "ok"}、GET /version返回版本号。使用 Go 1.22 增强路由语法。 -
进阶题(难度⭐⭐):实现一个待办事项 API(TODO List)。要求:(1) 完整的 CRUD 操作;(2) 使用 Go 1.22 方法和路径参数路由;(3) JSON 请求和响应;(4) 内存存储(map + RWMutex 保护);(5) 返回合适的 HTTP 状态码。
-
挑战题(难度⭐⭐⭐):实现一个短链接服务。要求:(1)
POST /shorten接收长 URL 返回短码(6 位随机字符串);(2)GET /{code}301 重定向到原始 URL;(3) 访问统计:GET /stats/{code}返回访问次数;(4) 用-race验证并发安全;(5) RWMutex 保护统计计数器。