第 14 章:Web 开发实战
jerry北京市2026年5月9日Go 19 次阅读 约 21 分钟

使用标准库 net/http 和 Gin 框架构建 RESTful API,涵盖路由、中间件、JSON 处理等核心技能。
14.1 net/http 基础
Go 标准库自带强大的 HTTP 服务器:
package main
import (
"fmt"
"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)
fmt.Println("服务器启动在 :8080")
http.ListenAndServe(":8080", nil)
}
请求处理
func handler(w http.ResponseWriter, r *http.Request) {
// 请求方法
fmt.Println(r.Method) // GET, POST, PUT, DELETE...
// URL 参数
name := r.URL.Query().Get("name")
// 请求头
contentType := r.Header.Get("Content-Type")
// 请求体
body, _ := io.ReadAll(r.Body)
defer r.Body.Close()
// 设置响应头
w.Header().Set("Content-Type", "application/json")
// 设置状态码
w.WriteHeader(http.StatusOK)
// 写入响应体
w.Write([]byte(`{"message": "ok"}`))
}
JSON 请求和响应
type CreateUserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
}
type Response struct {
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"`
}
func createUserHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(Response{
Code: 400,
Message: "无效的请求体",
})
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Response{
Code: 200,
Message: "创建成功",
Data: req,
})
}
14.2 自定义 ServeMux
mux := http.NewServeMux()
// Go 1.22+ 支持方法和路径参数
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
fmt.Fprintf(w, "用户 ID: %s", id)
})
mux.HandleFunc("POST /users", createUserHandler)
mux.HandleFunc("GET /health", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("OK"))
})
http.ListenAndServe(":8080", mux)
14.3 中间件
// 中间件:接收 handler 返回 handler
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
fmt.Printf("%s %s %v\n", r.Method, r.URL.Path, time.Since(start))
})
}
func authMiddleware(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, "未授权", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// 链式调用
handler := loggingMiddleware(authMiddleware(mux))
http.ListenAndServe(":8080", handler)
14.4 使用 Gin 框架
Gin 是 Go 最流行的 Web 框架,性能优秀,API 简洁:
go get github.com/gin-gonic/gin
基本用法
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default() // 包含 Logger 和 Recovery 中间件
// 路由
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run(":8080")
}
RESTful API 实战
type User struct {
ID int `json:"id"`
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
}
var users = []User{
{ID: 1, Name: "Alice", Email: "alice@example.com"},
{ID: 2, Name: "Bob", Email: "bob@example.com"},
}
func main() {
r := gin.Default()
v1 := r.Group("/api/v1")
{
v1.GET("/users", getUsers)
v1.GET("/users/:id", getUser)
v1.POST("/users", createUser)
v1.PUT("/users/:id", updateUser)
v1.DELETE("/users/:id", deleteUser)
}
r.Run(":8080")
}
func getUsers(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 200,
"data": users,
})
}
func getUser(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
return
}
for _, u := range users {
if u.ID == id {
c.JSON(http.StatusOK, gin.H{"data": u})
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
}
func createUser(c *gin.Context) {
var user User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
user.ID = len(users) + 1
users = append(users, user)
c.JSON(http.StatusCreated, gin.H{"data": user})
}
func updateUser(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var input User
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
for i, u := range users {
if u.ID == id {
users[i].Name = input.Name
users[i].Email = input.Email
c.JSON(http.StatusOK, gin.H{"data": users[i]})
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
}
func deleteUser(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
for i, u := range users {
if u.ID == id {
users = append(users[:i], users[i+1:]...)
c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
return
}
}
c.JSON(http.StatusNotFound, gin.H{"error": "用户不存在"})
}
Gin 中间件
// 自定义中间件
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "未授权"})
c.Abort()
return
}
// 验证 token...
c.Set("userID", 123) // 设置上下文值
c.Next()
}
}
// 使用中间件
authorized := r.Group("/api")
authorized.Use(AuthMiddleware())
{
authorized.GET("/profile", func(c *gin.Context) {
userID := c.GetInt("userID")
c.JSON(200, gin.H{"userID": userID})
})
}
请求参数绑定
// Query 参数
// GET /search?q=golang&page=1
func search(c *gin.Context) {
q := c.Query("q")
page := c.DefaultQuery("page", "1")
c.JSON(200, gin.H{"q": q, "page": page})
}
// 路径参数
// GET /users/123
func getUser(c *gin.Context) {
id := c.Param("id")
c.JSON(200, gin.H{"id": id})
}
// 结构体绑定
type SearchQuery struct {
Q string `form:"q" binding:"required"`
Page int `form:"page" binding:"min=1"`
}
func search(c *gin.Context) {
var query SearchQuery
if err := c.ShouldBindQuery(&query); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 使用 query.Q 和 query.Page
}
14.5 优雅关闭
func main() {
r := gin.Default()
// 注册路由...
srv := &http.Server{
Addr: ":8080",
Handler: r,
}
go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("启动失败: %s\n", err)
}
}()
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("正在关闭服务器...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("服务器强制关闭:", err)
}
log.Println("服务器已退出")
}
14.6 面试要点
-
net/http 的 Handler 接口?
ServeHTTP(ResponseWriter, *Request)方法HandlerFunc是实现了 Handler 接口的函数类型
-
中间件的实现原理?
- 函数接收 Handler 返回 Handler,形成调用链
- 洋葱模型:请求从外到内,响应从内到外
-
如何实现优雅关闭?
- 监听系统信号
- 调用
Server.Shutdown()等待现有请求完成
-
Gin 和标准库的区别?
- Gin 基于 httprouter,路由性能更好
- 提供参数绑定、验证、中间件等便捷功能
- 标准库 Go 1.22+ 也支持了方法路由和路径参数
练习
- 使用标准库实现一个简单的 TODO API(CRUD)
- 使用 Gin 实现带认证中间件的 RESTful API
- 实现一个请求限流中间件
- 实现优雅关闭,验证关闭时正在处理的请求能正常完成
评论
登录 后发表评论
暂无评论