Skip to content

net/http, Handler, Router

Phase 03 — Go Web 涵盖:net/http · Handler · Router


1. 学习目标

完成本知识点后,你应该能够:

  • 使用 net/http 标准库启动 HTTP 服务器
  • 理解 Handler 接口和 HandlerFunc 适配器
  • 编写处理 JSON 请求/响应的 handler 函数
  • 使用 ServeMux 注册路由(Go 1.22+ 增强路由)
  • 提取 URL 路径参数和查询参数
  • 为数字孪生 API 实现基础 CRUD handler

2. 为什么需要

Go 标准库 net/http 功能完整,无需框架即可构建生产级 API。理解标准库原理后,学 Gin 等框架只是语法糖。先用 net/http 能帮你明白框架背后做了什么。

每个 HTTP 请求由 Go runtime 在独立 goroutine 中调用你的 Handler——这与 Node.js 单线程事件循环不同,你需要考虑并发安全(Phase 02 的 Mutex)。


3. 核心概念

3.1 Handler 接口

go
type Handler interface {
    ServeHTTP(w http.ResponseWriter, r *http.Request)
}

任何实现了 ServeHTTP 的类型都是 Handler。

3.2 HandlerFunc

go
type HandlerFunc func(w http.ResponseWriter, r *http.Request)
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    f(w, r)
}

普通函数可通过 http.HandlerFunc(fn) 转为 Handler。

3.3 核心类型

类型作用
http.ResponseWriter写响应状态码、Header、Body
http.Request读 Method、URL、Header、Body
http.ServeMux路由 multiplexer

3.4 Go 1.22+ ServeMux

支持 method 和 path 变量:

go
mux.HandleFunc("GET /api/devices/{id}", getDevice)

4. 基础语法

完整示例见 workspace/phase-03/net-http-handler-router/main.go

go
package main

import (
    "encoding/json"
    "log"
    "net/http"
)

type Device struct {
    ID     string  `json:"id"`
    Name   string  `json:"name"`
    Online bool    `json:"online"`
    X      float64 `json:"x"`
    Y      float64 `json:"y"`
}

var devices = map[string]Device{
    "AGV-001": {ID: "AGV-001", Name: "Alpha", Online: true, X: 10, Y: 5},
}

func writeJSON(w http.ResponseWriter, status int, v any) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(status)
    _ = json.NewEncoder(w).Encode(v)
}

func listDevices(w http.ResponseWriter, r *http.Request) {
    list := make([]Device, 0, len(devices))
    for _, d := range devices {
        list = append(list, d)
    }
    writeJSON(w, http.StatusOK, list)
}

func getDevice(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    d, ok := devices[id]
    if !ok {
        writeJSON(w, http.StatusNotFound, map[string]string{"message": "device not found"})
        return
    }
    writeJSON(w, http.StatusOK, d)
}

func createDevice(w http.ResponseWriter, r *http.Request) {
    var d Device
    if err := json.NewDecoder(r.Body).Decode(&d); err != nil {
        writeJSON(w, http.StatusBadRequest, map[string]string{"message": "invalid JSON"})
        return
    }
    if d.ID == "" {
        writeJSON(w, http.StatusBadRequest, map[string]string{"message": "id is required"})
        return
    }
    devices[d.ID] = d
    writeJSON(w, http.StatusCreated, d)
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("GET /api/devices", listDevices)
    mux.HandleFunc("GET /api/devices/{id}", getDevice)
    mux.HandleFunc("POST /api/devices", createDevice)

    log.Println("listening on :8080")
    log.Fatal(http.ListenAndServe(":8080", mux))
}

测试:

bash
curl http://localhost:8080/api/devices
curl http://localhost:8080/api/devices/AGV-001
curl -X POST http://localhost:8080/api/devices \
  -H "Content-Type: application/json" \
  -d '{"id":"AGV-002","name":"Beta","online":false,"x":0,"y":0}'

5. 代码解析

go
mux.HandleFunc("GET /api/devices/{id}", getDevice)

Go 1.22+ 路由模式:METHOD PATH{id} 为路径参数,通过 r.PathValue("id") 获取。

go
json.NewDecoder(r.Body).Decode(&d)

从请求体流式解析 JSON,比 ioutil.ReadAll + Unmarshal 更高效。记得处理 error。

go
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)

Set Header, WriteHeader,最后 Write/Encode。WriteHeader 只能调用一次。

go
http.ListenAndServe(":8080", mux)

阻塞监听。每个请求在新 goroutine 中执行 handler。


6. JavaScript / TypeScript 对比

概念Go net/httpExpress.js
启动ListenAndServeapp.listen(8080)
路由ServeMux.HandleFuncapp.get('/path', fn)
路径参数r.PathValue("id")req.params.id
查询参数r.URL.Query().Get("page")req.query.page
请求体json.Decoder(r.Body)express.json() middleware
响应ResponseWriterres.json()

关键差异

  1. Go 无内置 res.json(),需手动 Set Content-Type 和 Encode
  2. 标准库路由较基础,复杂路由可后续用 Gin 或 chi
  3. 每个请求已是 goroutine,无需 async 关键字,但共享 map 需 Mutex

7. 常见错误

错误 1:WriteHeader 顺序错误

go
w.WriteHeader(200)
w.Header().Set("Content-Type", "application/json")  // ❌ 太晚了

错误 2:忘记关闭 Body

go
// server 端 r.Body 需 Close(通常 defer r.Body.Close())
// client 端 resp.Body 必须 Close

错误 3:并发写 map

go
var devices = map[string]Device{}  // 多 goroutine 写 → panic
// ✅ 用 sync.RWMutex 或 sync.Map

错误 4:未设置 Content-Type

前端 axios 可能无法自动 parse JSON。

错误 5:handler 里 panic

未 recover 的 panic 导致 500 且可能中断连接。业务 error 应返回 4xx/5xx JSON。


8. 实际应用

数字孪生 — 设备 CRUD 服务

GET    /api/devices          → listDevices
GET    /api/devices/{id}     → getDevice
POST   /api/devices          → createDevice
PUT    /api/devices/{id}     → updateDevice
DELETE /api/devices/{id}     → deleteDevice

内存 store 起步,Phase 04 换 PostgreSQL repository,handler 签名不变。

查询参数过滤

go
deviceType := r.URL.Query().Get("type")
online := r.URL.Query().Get("online") == "true"

Vue 前端:/api/devices?type=agv&online=true


9. 深入理解

9.1 http.Server 配置

go
server := &http.Server{
    Addr:         ":8080",
    Handler:      mux,
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 10 * time.Second,
}

生产环境应设置超时,防止慢客户端耗尽 goroutine。

9.2 中间件模式

go
func withLogging(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Println(r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

ListenAndServe(addr, withLogging(mux)) — 下一知识点展开。

9.3 与 Gin 的关系

Gin 底层仍是 HTTP,提供路由分组、JSON helper、中间件链。标准库学会后 Gin 上手很快。


10. 练习

详细题目见 exercises/phase-03-web/02-net-http-handler-router.md

Level 1 — 基础

练习 1.1:启动服务器,GET /health 返回 {"status":"ok"}

练习 1.2:GET /api/devices 返回硬编码 JSON 数组。

练习 1.3:读取查询参数 ?name=xxx 并 echo。

Level 2 — 应用

练习 2.1:实现 Device CRUD 四个 handler(内存 map)。

练习 2.2:路径参数 {id},404 处理。

练习 2.3:POST 解析 JSON body,校验 id 非空。

Level 3 — 综合

练习 3.1:加 RWMutex 保护并发 map 访问。

练习 3.2:统一 writeJSON 和 writeError helper。

Level 4 — 项目实践

练习 4.1:在 workspace/phase-03/net-http-handler-router/ 完成 Device REST API,可用 curl 测试全部端点。


11. 学习检查

  1. Handler 接口的方法签名是什么?
  2. WriteHeader 为什么只能调用一次?
  3. Go 1.22+ 如何获取路径参数 {id}
  4. 如何从请求体解析 JSON?
  5. 为什么多 handler 写同一 map 需要 Mutex?
  6. ListenAndServe 阻塞吗?每个请求在哪个并发单元执行?

12. 下一步

已完成下一知识点关系
net/http, Handler, RouterMiddleware, Validation, CORS日志、鉴权、跨域、参数校验
Gin, JWT, API design框架加速与认证

继续完善 net/http 服务器的横切能力,再考虑 Gin。


学习导航

上一篇:HTTP and REST API · 对应练习 · 下一篇:Middleware、Validation、CORS