#HTTP 服务
#最小可用服务
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, Go Web!")
})
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println("服务退出:", err)
}
}运行后访问 http://localhost:8080 即可看到响应。
#Go 1.22+ 增强路由
Go 1.22 为 ServeMux 引入了方法匹配与路径参数,覆盖了此前需要框架才能实现的能力:
package main
import (
"encoding/json"
"log"
"net/http"
)
type Task struct {
ID int64 `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}
var tasks = []Task{
{ID: 1, Title: "学习 Go", Done: false},
{ID: 2, Title: "部署服务", Done: true},
}
func main() {
mux := http.NewServeMux()
// 精确匹配方法与路径
mux.HandleFunc("GET /api/tasks", listTasks)
mux.HandleFunc("POST /api/tasks", createTask)
// 路径参数:{id} 会被解析并可通过 r.PathValue("id") 获取
mux.HandleFunc("GET /api/tasks/{id}", getTask)
log.Fatal(http.ListenAndServe(":8080", mux))
}
func listTasks(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, tasks)
}
func createTask(w http.ResponseWriter, r *http.Request) {
var t Task
if err := json.NewDecoder(r.Body).Decode(&t); err != nil {
http.Error(w, "请求体不合法", http.StatusBadRequest)
return
}
t.ID = int64(len(tasks) + 1)
tasks = append(tasks, t)
writeJSON(w, http.StatusCreated, t)
}
func getTask(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
for _, t := range tasks {
if fmt.Sprintf("%d", t.ID) == id {
writeJSON(w, http.StatusOK, t)
return
}
}
http.Error(w, "任务不存在", http.StatusNotFound)
}
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}Tip
路由语法要点:
"GET /api/tasks"限定 HTTP 方法,未匹配方法返回405 Method Not Allowed。/{id}匹配单段路径参数;/{path...}匹配剩余全部路径(通配)。- 以
/结尾的注册路径(如/static/)作为子树前缀匹配。
JSON 编解码细节见 JSON 处理章节。
#中间件
中间件是包装 http.Handler 的函数,用于在业务逻辑前后插入横切逻辑(日志、恢复 panic、认证等):
package main
import (
"log"
"net/http"
"time"
)
// 中间件:函数接收并返回 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) // 调用链中的下一个处理器
log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
})
}
// 中间件:recover panic,避免单请求崩溃拖垮服务
func recoverPanic(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Println("panic:", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
// 链式包裹:请求依次经过 recover -> logging -> mux
handler := recoverPanic(logging(mux))
log.Fatal(http.ListenAndServe(":8080", handler))
}#优雅退出
生产服务需要处理 SIGTERM/SIGINT 信号,完成存量请求后再退出:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
srv := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second, // 防止慢连接攻击
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
go func() {
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal("服务异常退出:", err)
}
}()
// 阻塞等待终止信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
// 给存量请求 10 秒完成时间
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Println("强制退出:", err)
}
}#超越标准库
当路由规则、参数校验、文档生成等需求增长时,可引入框架:
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/api/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{"id": id})
})
r.Run(":8080")
}#小结
- Go 1.22+ 的 ServeMux 支持方法匹配与路径参数,多数场景无需框架。
- 中间件通过包装
http.Handler实现横切逻辑,注意 recover 兜底。 - 生产服务应配置超时并实现信号驱动的优雅退出。