404 Not Found

404 Not Found


nginx

HTTP编程

第22课:HTTP编程

🎯 生活类比

想象你经营一家餐厅:

Go的net/http包就是你的"餐厅管理系统",帮你快速搭建一个高效稳定的Web服务。


📚 核心概念

概念 说明
http.Get/Post 发送HTTP请求,获取远程资源
http.ListenAndServe 启动HTTP服务器,监听端口
Handler 接口 定义请求处理规范的核心接口
HandlerFunc 将普通函数适配为Handler的适配器
ServeMux HTTP请求多路复用器(路由器)
中间件(Middleware) 在请求处理前后插入通用逻辑的模式

📝 基本语法与用法

1. 发送HTTP请求

GO
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	// 发送GET请求
	resp, err := http.Get("https://httpbin.org/get")
	if err != nil {
		fmt.Println("请求失败:", err)
		return
	}
	defer resp.Body.Close() // 💡 必须关闭响应体,避免资源泄漏

	// 读取响应内容
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("读取失败:", err)
		return
	}

	fmt.Println("状态码:", resp.StatusCode)
	fmt.Println("响应内容:", string(body))
}
💡 提示resp.Body使用完毕后必须调用Close(),否则会导致连接泄漏。使用defer是最佳实践。

2. 发送POST请求

GO
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

func main() {
	// 构造JSON数据
	data := map[string]string{
		"username": "gopher",
		"email":    "gopher@example.com",
	}
	jsonData, _ := json.Marshal(data)

	// 发送POST请求
	// 💡 第三个参数是请求体,需要io.Reader类型
	resp, err := http.Post(
		"https://httpbin.org/post",
		"application/json",
		bytes.NewBuffer(jsonData),
	)
	if err != nil {
		fmt.Println("请求失败:", err)
		return
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println("状态码:", resp.StatusCode)
	fmt.Println("响应:", string(body))
}
💡 提示http.Post的Content-Type参数很重要,服务端依靠它来解析请求体格式。

3. 启动HTTP服务器

GO
package main

import (
	"fmt"
	"net/http"
)

func main() {
	// 注册路由处理函数
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "欢迎访问首页!")
	})

	http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "你好,Gopher!")
	})

	// 启动服务器,监听8080端口
	// 💡 ListenAndServe会阻塞运行,直到服务器关闭
	fmt.Println("服务器启动在 http://localhost:8080")
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println("服务器启动失败:", err)
	}
}
💡 提示:第二个参数传nil表示使用默认的DefaultServeMux。生产环境建议创建自定义路由器。

4. Handler接口

GO
// Handler接口定义:任何实现了ServeHTTP方法的类型都是Handler
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
GO
package main

import (
	"fmt"
	"net/http"
)

// 自定义Handler类型
type GreetingHandler struct {
	Message string
}

// 实现Handler接口的ServeHTTP方法
func (g GreetingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, g.Message)
}

func main() {
	// 使用自定义Handler
	handler := GreetingHandler{Message: "你好,这是自定义Handler!"}
	http.Handle("/greet", handler) // 💡 注意:用Handle而非HandleFunc

	http.ListenAndServe(":8080", nil)
}
💡 提示Handle接收Handler接口,HandleFunc接收函数。两者功能等价,只是形式不同。

5. HandlerFunc适配器

GO
package main

import (
	"fmt"
	"net/http"
)

func myHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "通过HandlerFunc适配的处理函数")
}

func main() {
	// HandlerFunc将普通函数转换为Handler
	// 💡 本质是类型转换:type HandlerFunc func(ResponseWriter, *Request)
	http.Handle("/adapted", http.HandlerFunc(myHandler))

	// 等价写法(更常用)
	http.HandleFunc("/simple", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "直接使用HandleFunc更简洁")
	})

	http.ListenAndServe(":8080", nil)
}
💡 提示HandlerFunc是一个类型适配器,让普通函数也能满足Handler接口。


🧪 实战示例

示例:简单的静态文件服务器(难度⭐)

GO
package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	// 使用默认路由注册静态文件服务
	// StripPrefix移除URL中的"/static/"前缀
	// FileServer提供目录下的文件访问
	fs := http.FileServer(http.Dir("./public"))
	http.Handle("/static/", http.StripPrefix("/static/", fs))

	// 首页处理
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/" {
			http.NotFound(w, r) // 💡 404处理
			return
		}
		fmt.Fprintf(w, "欢迎来到我的网站!")
	})

	fmt.Println("服务器运行在 http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}
▶ 试一试

运行方式:

BASH
# 创建测试目录和文件
mkdir -p public
echo "<h1>Hello</h1>" > public/index.html

# 运行服务器
go run main.go

# 测试访问(另一个终端)
curl http://localhost:8080/
curl http://localhost:8080/static/index.html

示例:自定义路由与中间件(难度⭐⭐)

GO
package main

import (
	"fmt"
	"log"
	"net/http"
	"time"
)

// 日志中间件:记录每个请求的处理时间
func LoggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		log.Printf("开始 %s %s", r.Method, r.URL.Path)

		next.ServeHTTP(w, r) // 💡 调用下一个处理器

		log.Printf("完成 %s %s 耗时 %v", r.Method, r.URL.Path, time.Since(start))
	})
}

// 认证中间件:检查请求头中的Token
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
		}
		log.Printf("用户Token: %s", token)
		next.ServeHTTP(w, r)
	})
}

func main() {
	mux := http.NewServeMux() // 💡 创建自定义路由器

	// 公开路由
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "欢迎访问API服务!")
	})

	mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, `{"status": "ok"}`)
	})

	// 需要认证的路由
	protected := http.NewServeMux()
	protected.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "这是你的个人资料页面")
	})
	protected.HandleFunc("/settings", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "这是设置页面")
	})

	// 💡 中间件链:先日志,再认证,最后处理
	mux.Handle("/api/", AuthMiddleware(protected))

	// 应用日志中间件到所有路由
	finalHandler := LoggingMiddleware(mux)

	fmt.Println("服务器运行在 http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", finalHandler))
}
▶ 试一试

运行与测试:

BASH
# 运行
go run main.go

# 测试公开路由
curl http://localhost:8080/
curl http://localhost:8080/health

# 测试需要认证的路由(无Token)
curl http://localhost:8080/api/profile
# 输出: 未授权访问

# 测试需要认证的路由(带Token)
curl -H "Authorization: Bearer mytoken123" http://localhost:8080/api/profile
# 输出: 这是你的个人资料页面

示例:完整的RESTful API服务(难度⭐⭐⭐)

GO
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"strconv"
	"strings"
	"sync"
)

// User 用户模型
type User struct {
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Email string `json:"email"`
}

// APIResponse 统一响应格式
type APIResponse struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

// UserStore 用户存储(使用map模拟数据库)
type UserStore struct {
	mu     sync.RWMutex
	users  map[int]*User
	nextID int
}

func NewUserStore() *UserStore {
	return &UserStore{
		users:  make(map[int]*User),
		nextID: 1,
	}
}

func (s *UserStore) Create(name, email string) *User {
	s.mu.Lock()
	defer s.mu.Unlock()

	user := &User{
		ID:    s.nextID,
		Name:  name,
		Email: email,
	}
	s.users[s.nextID] = user
	s.nextID++
	return user
}

func (s *UserStore) Get(id int) *User {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.users[id]
}

func (s *UserStore) List() []*User {
	s.mu.RLock()
	defer s.mu.RUnlock()

	users := make([]*User, 0, len(s.users))
	for _, u := range s.users {
		users = append(users, u)
	}
	return users
}

func (s *UserStore) Update(id int, name, email string) *User {
	s.mu.Lock()
	defer s.mu.Unlock()

	user, ok := s.users[id]
	if !ok {
		return nil
	}
	if name != "" {
		user.Name = name
	}
	if email != "" {
		user.Email = email
	}
	return user
}

func (s *UserStore) Delete(id int) bool {
	s.mu.Lock()
	defer s.mu.Unlock()

	if _, ok := s.users[id]; !ok {
		return false
	}
	delete(s.users, id)
	return true
}

// UserHandler 用户API处理器
type UserHandler struct {
	store *UserStore
}

// writeJSON 写入JSON响应
func (h *UserHandler) writeJSON(w http.ResponseWriter, code int, resp APIResponse) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(code)
	json.NewEncoder(w).Encode(resp)
}

// List 获取用户列表 GET /users
func (h *UserHandler) List(w http.ResponseWriter, r *http.Request) {
	users := h.store.List()
	h.writeJSON(w, http.StatusOK, APIResponse{
		Code:    0,
		Message: "success",
		Data:    users,
	})
}

// Create 创建用户 POST /users
func (h *UserHandler) Create(w http.ResponseWriter, r *http.Request) {
	var req struct {
		Name  string `json:"name"`
		Email string `json:"email"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    1,
			Message: "无效的请求数据",
		})
		return
	}

	if req.Name == "" || req.Email == "" {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    2,
			Message: "姓名和邮箱不能为空",
		})
		return
	}

	user := h.store.Create(req.Name, req.Email)
	h.writeJSON(w, http.StatusCreated, APIResponse{
		Code:    0,
		Message: "创建成功",
		Data:    user,
	})
}

// Get 获取单个用户 GET /users/{id}
func (h *UserHandler) Get(w http.ResponseWriter, r *http.Request) {
	idStr := strings.TrimPrefix(r.URL.Path, "/users/")
	id, err := strconv.Atoi(idStr)
	if err != nil {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    1,
			Message: "无效的用户ID",
		})
		return
	}

	user := h.store.Get(id)
	if user == nil {
		h.writeJSON(w, http.StatusNotFound, APIResponse{
			Code:    3,
			Message: "用户不存在",
		})
		return
	}

	h.writeJSON(w, http.StatusOK, APIResponse{
		Code:    0,
		Message: "success",
		Data:    user,
	})
}

// Update 更新用户 PUT /users/{id}
func (h *UserHandler) Update(w http.ResponseWriter, r *http.Request) {
	idStr := strings.TrimPrefix(r.URL.Path, "/users/")
	id, err := strconv.Atoi(idStr)
	if err != nil {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    1,
			Message: "无效的用户ID",
		})
		return
	}

	var req struct {
		Name  string `json:"name"`
		Email string `json:"email"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    1,
			Message: "无效的请求数据",
		})
		return
	}

	user := h.store.Update(id, req.Name, req.Email)
	if user == nil {
		h.writeJSON(w, http.StatusNotFound, APIResponse{
			Code:    3,
			Message: "用户不存在",
		})
		return
	}

	h.writeJSON(w, http.StatusOK, APIResponse{
		Code:    0,
		Message: "更新成功",
		Data:    user,
	})
}

// Delete 删除用户 DELETE /users/{id}
func (h *UserHandler) Delete(w http.ResponseWriter, r *http.Request) {
	idStr := strings.TrimPrefix(r.URL.Path, "/users/")
	id, err := strconv.Atoi(idStr)
	if err != nil {
		h.writeJSON(w, http.StatusBadRequest, APIResponse{
			Code:    1,
			Message: "无效的用户ID",
		})
		return
	}

	if !h.store.Delete(id) {
		h.writeJSON(w, http.StatusNotFound, APIResponse{
			Code:    3,
			Message: "用户不存在",
		})
		return
	}

	h.writeJSON(w, http.StatusOK, APIResponse{
		Code:    0,
		Message: "删除成功",
	})
}

// CORS中间件
func CORSMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")

		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusOK)
			return
		}

		next.ServeHTTP(w, r)
	})
}

// Recovery中间件:捕获panic
func RecoveryMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if err := recover(); err != nil {
				log.Printf("捕获到panic: %v", err)
				http.Error(w, "服务器内部错误", http.StatusInternalServerError)
			}
		}()
		next.ServeHTTP(w, r)
	})
}

func main() {
	store := NewUserStore()
	handler := &UserHandler{store: store}

	// 创建路由器
	mux := http.NewServeMux()

	// 💡 注册路由:根据HTTP方法分发到不同处理函数
	mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			handler.List(w, r)
		case http.MethodPost:
			handler.Create(w, r)
		default:
			http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
		}
	})

	// 💡 处理带路径参数的路由 /users/{id}
	mux.HandleFunc("/users/", func(w http.ResponseWriter, r *http.Request) {
		switch r.Method {
		case http.MethodGet:
			handler.Get(w, r)
		case http.MethodPut:
			handler.Update(w, r)
		case http.MethodDelete:
			handler.Delete(w, r)
		default:
			http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
		}
	})

	// 应用中间件链
	finalHandler := RecoveryMiddleware(CORSMiddleware(mux))

	// 创建服务器(可配置超时)
	server := &http.Server{
		Addr:         ":8080",
		Handler:      finalHandler,
		ReadTimeout:  10 * time.Second,  // 💡 读取超时
		WriteTimeout: 10 * time.Second,  // 💡 写入超时
	}

	// 插入测试数据
	store.Create("张三", "zhangsan@example.com")
	store.Create("李四", "lisi@example.com")

	fmt.Println("RESTful API服务器运行在 http://localhost:8080")
	fmt.Println("可用接口:")
	fmt.Println("  GET    /users      - 获取用户列表")
	fmt.Println("  POST   /users      - 创建用户")
	fmt.Println("  GET    /users/{id} - 获取单个用户")
	fmt.Println("  PUT    /users/{id} - 更新用户")
	fmt.Println("  DELETE /users/{id} - 删除用户")

	log.Fatal(server.ListenAndServe())
}
▶ 试一试

运行与完整测试:

BASH
# 运行服务器
go run main.go

# 获取用户列表
curl http://localhost:8080/users

# 创建用户
curl -X POST http://localhost:8080/users \
  -H "Content-Type: application/json" \
  -d '{"name": "王五", "email": "wangwu@example.com"}'

# 获取单个用户
curl http://localhost:8080/users/1

# 更新用户
curl -X PUT http://localhost:8080/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name": "张三丰"}'

# 删除用户
curl -X DELETE http://localhost:8080/users/3

预期输出示例:

TEXT
# GET /users
{"code":0,"message":"success","data":[{"id":1,"name":"张三","email":"zhangsan@example.com"},{"id":2,"name":"李四","email":"lisi@example.com"}]}

# POST /users
{"code":0,"message":"创建成功","data":{"id":3,"name":"王五","email":"wangwu@example.com"}}

# GET /users/1
{"code":0,"message":"success","data":{"id":1,"name":"张三","email":"zhangsan@example.com"}}

🎬 场景演练

场景1:构建天气查询服务

GO
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

// WeatherResponse 模拟天气API响应
type WeatherResponse struct {
	City        string  `json:"city"`
	Temperature float64 `json:"temperature"`
	Condition   string  `json:"condition"`
	Humidity    int     `json:"humidity"`
}

// WeatherService 天气服务处理器
type WeatherService struct {
	// 模拟数据
	data map[string]WeatherResponse
}

func NewWeatherService() *WeatherService {
	return &WeatherService{
		data: map[string]WeatherResponse{
			"beijing":  {City: "北京", Temperature: 28.5, Condition: "晴", Humidity: 45},
			"shanghai": {City: "上海", Temperature: 30.2, Condition: "多云", Humidity: 65},
			"guangzhou": {City: "广州", Temperature: 33.1, Condition: "雷阵雨", Humidity: 80},
		},
	}
}

// GetWeather 查询天气 GET /weather?city=beijing
func (s *WeatherService) GetWeather(w http.ResponseWriter, r *http.Request) {
	// 💡 从URL查询参数获取城市名
	city := r.URL.Query().Get("city")
	if city == "" {
		http.Error(w, `{"error": "请提供city参数"}`, http.StatusBadRequest)
		return
	}

	// URL解码(支持中文城市名)
	city, _ = url.QueryUnescape(city)

	// 模拟查找天气数据
	weather, ok := s.data[city]
	if !ok {
		http.Error(w, fmt.Sprintf(`{"error": "未找到城市: %s"}`, city), http.StatusNotFound)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(weather)
}

// GetForecast 预报列表 GET /forecast
func (s *WeatherService) GetForecast(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	// 返回所有城市天气
	forecasts := make([]WeatherResponse, 0, len(s.data))
	for _, v := range s.data {
		forecasts = append(forecasts, v)
	}

	json.NewEncoder(w).Encode(map[string]interface{}{
		"count":     len(forecasts),
		"forecasts": forecasts,
	})
}

func main() {
	service := NewWeatherService()

	mux := http.NewServeMux()
	mux.HandleFunc("/weather", service.GetWeather)
	mux.HandleFunc("/forecast", service.GetForecast)

	fmt.Println("天气服务运行在 http://localhost:8080")
	fmt.Println("使用方式:")
	fmt.Println("  GET /weather?city=beijing")
	fmt.Println("  GET /forecast")

	http.ListenAndServe(":8080", mux)
}
BASH
# 测试
curl "http://localhost:8080/weather?city=beijing"
# {"city":"北京","temperature":28.5,"condition":"晴","humidity":45}

curl http://localhost:8080/forecast
# {"count":3,"forecasts":[...]}

场景2:实现请求限流中间件

GO
package main

import (
	"fmt"
	"net/http"
	"sync"
	"time"
)

// RateLimiter 令牌桶限流器
type RateLimiter struct {
	mu       sync.Mutex
	tokens   map[string]int
	limit    int
	interval time.Duration
	lastTick map[string]time.Time
}

func NewRateLimiter(limit int, interval time.Duration) *RateLimiter {
	return &RateLimiter{
		tokens:   make(map[string]int),
		limit:    limit,
		interval: interval,
		lastTick: make(map[string]time.Time),
	}
}

// Allow 检查请求是否允许通过
func (rl *RateLimiter) Allow(key string) bool {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	now := time.Now()
	last, exists := rl.lastTick[key]

	if !exists || now.Sub(last) >= rl.interval {
		// 💡 重置令牌
		rl.tokens[key] = rl.limit
		rl.lastTick[key] = now
	}

	if rl.tokens[key] <= 0 {
		return false
	}

	rl.tokens[key]--
	return true
}

// RateLimitMiddleware 限流中间件
func RateLimitMiddleware(limiter *RateLimiter) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// 使用客户端IP作为限流key
			clientIP := r.RemoteAddr

			if !limiter.Allow(clientIP) {
				w.Header().Set("Content-Type", "application/json")
				w.WriteHeader(http.StatusTooManyRequests) // 429
				fmt.Fprintf(w, `{"error": "请求过于频繁,请稍后再试"}`)
				return
			}

			next.ServeHTTP(w, r)
		})
	}
}

func main() {
	// 创建限流器:每个IP每分钟最多10个请求
	limiter := NewRateLimiter(10, time.Minute)

	mux := http.NewServeMux()

	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "请求成功!时间: %s", time.Now().Format("15:04:05"))
	})

	mux.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, `{"data": "这是API数据", "time": "%s"}`, time.Now().Format("15:04:05"))
	})

	// 应用限流中间件
	handler := RateLimitMiddleware(limiter)(mux)

	fmt.Println("限流服务器运行在 http://localhost:8080")
	fmt.Println("限流规则: 每分钟最多10个请求")
	http.ListenAndServe(":8080", handler)
}
BASH
# 测试限流(快速发送多次请求)
for i in {1..15}; do
  curl -s http://localhost:8080/
  echo ""
done
# 前10次正常返回,后续返回429错误

❓ 常见问题

Q1:http.HandleFunchttp.Handle有什么区别?

GO
// HandleFunc 接收函数
http.HandleFunc("/path", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello")
})

// Handle 接收Handler接口
type MyHandler struct{}
func (h MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello")
}
http.Handle("/path", MyHandler{})

区别:


Q2:如何在处理函数中获取请求参数?

GO
func handler(w http.ResponseWriter, r *http.Request) {
	// 💡 查询参数(URL中的?key=value)
	name := r.URL.Query().Get("name")

	// 💡 表单数据(POST form-urlencoded)
	r.ParseForm()
	email := r.Form.Get("email")

	// 💡 路径参数(需要自己解析或使用第三方路由)
	// 例如 /users/123 中的 123
	path := r.URL.Path // "/users/123"
	// 手动解析:strings.TrimPrefix(path, "/users/")

	// 💡 请求头
	token := r.Header.Get("Authorization")

	// 💡 JSON请求体
	var data map[string]string
	json.NewDecoder(r.Body).Decode(&data)

	fmt.Fprintf(w, "name=%s, email=%s, token=%s", name, email, token)
}

Q3:如何实现优雅关闭服务器?

GO
package main

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	server := &http.Server{
		Addr: ":8080",
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			time.Sleep(2 * time.Second) // 模拟耗时操作
			fmt.Fprintf(w, "处理完成")
		}),
	}

	// 在goroutine中启动服务器
	go func() {
		fmt.Println("服务器启动在 :8080")
		if err := server.ListenAndServe(); err != http.ErrServerClosed {
			log.Fatalf("服务器异常: %v", err)
		}
	}()

	// 💡 监听系统信号
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit // 阻塞等待信号

	fmt.Println("\n正在优雅关闭服务器...")

	// 💡 给现有请求5秒时间完成
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := server.Shutdown(ctx); err != nil {
		log.Fatalf("关闭失败: %v", err)
	}

	fmt.Println("服务器已安全关闭")
}
BASH
# 运行后按 Ctrl+C 触发优雅关闭
go run main.go
# 输出: 服务器启动在 :8080
# 按Ctrl+C
# 输出: 正在优雅关闭服务器...
# 输出: 服务器已安全关闭

Q4:为什么ListenAndServe的第二个参数建议传自定义Mux?

GO
// ❌ 使用默认的DefaultServeMux(不推荐)
http.HandleFunc("/api/users", usersHandler)
http.ListenAndServe(":8080", nil) // nil表示使用DefaultServeMux

// ✅ 使用自定义Mux(推荐)
mux := http.NewServeMux()
mux.HandleFunc("/api/users", usersHandler)
http.ListenAndServe(":8080", mux)

原因:

  1. DefaultServeMux是全局的,任何包都可以注册路由,可能导致冲突
  2. 自定义Mux的作用域可控,避免意外的路由覆盖
  3. 方便在不同服务器实例间复用和测试

📖 小节

本节课我们学习了Go HTTP编程的核心内容:

知识点 掌握要点
HTTP客户端 http.Get/Post发送请求,resp.Body必须关闭
HTTP服务器 http.ListenAndServe启动服务,路由注册
Handler接口 ServeHTTP(ResponseWriter, *Request)方法
HandlerFunc 函数适配器,将函数转为Handler
ServeMux 路由器,支持前缀匹配
中间件 包装Handler,在请求前后插入通用逻辑
优雅关闭 使用server.Shutdown和信号处理

核心模式:

TEXT
客户端请求 → 中间件链 → 路由匹配 → Handler处理 → 响应返回

📝 作业

练习1:实现文件上传服务

要求:

参考答案
GO
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

func uploadHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "只支持POST方法", http.StatusMethodNotAllowed)
		return
	}

	// 💡 限制上传大小为10MB
	r.ParseMultipartForm(10 << 20)

	file, header, err := r.FormFile("file")
	if err != nil {
		http.Error(w, "获取文件失败", http.StatusBadRequest)
		return
	}
	defer file.Close()

	// 检查文件类型
	ext := strings.ToLower(filepath.Ext(header.Filename))
	allowed := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true}
	if !allowed[ext] {
		http.Error(w, "只允许上传图片文件", http.StatusBadRequest)
		return
	}

	// 保存文件
	os.MkdirAll("./uploads", 0755)
	dst, err := os.Create(filepath.Join("./uploads", header.Filename))
	if err != nil {
		http.Error(w, "保存文件失败", http.StatusInternalServerError)
		return
	}
	defer dst.Close()

	io.Copy(dst, file)

	fmt.Fprintf(w, `{"url": "/files/%s", "size": %d}`, header.Filename, header.Size)
}

func main() {
	http.HandleFunc("/upload", uploadHandler)
	http.Handle("/files/", http.StripPrefix("/files/", http.FileServer(http.Dir("./uploads"))))

	fmt.Println("文件上传服务运行在 http://localhost:8080")
	http.ListenAndServe(":8080", nil)
}

练习2:实现JWT认证中间件

要求:

参考答案
GO
package main

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"
)

// 简化的JWT结构
type SimpleToken struct {
	Username string    `json:"username"`
	ExpireAt time.Time `json:"expire"`
}

// 生成简单token
func generateToken(username string) (string, error) {
	token := SimpleToken{
		Username: username,
		ExpireAt: time.Now().Add(1 * time.Hour),
	}
	data, err := json.Marshal(token)
	if err != nil {
		return "", err
	}
	return base64.StdEncoding.EncodeToString(data), nil
}

// 验证token
func validateToken(tokenStr string) (*SimpleToken, error) {
	data, err := base64.StdEncoding.DecodeString(tokenStr)
	if err != nil {
		return nil, fmt.Errorf("无效的token")
	}

	var token SimpleToken
	if err := json.Unmarshal(data, &token); err != nil {
		return nil, fmt.Errorf("token解析失败")
	}

	if time.Now().After(token.ExpireAt) {
		return nil, fmt.Errorf("token已过期")
	}

	return &token, nil
}

// 认证中间件
func AuthMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		auth := r.Header.Get("Authorization")
		if auth == "" {
			http.Error(w, `{"error": "请提供Authorization头"}`, http.StatusUnauthorized)
			return
		}

		// 解析 "Bearer <token>"
		parts := strings.SplitN(auth, " ", 2)
		if len(parts) != 2 || parts[0] != "Bearer" {
			http.Error(w, `{"error": "Authorization格式错误"}`, http.StatusUnauthorized)
			return
		}

		token, err := validateToken(parts[1])
		if err != nil {
			http.Error(w, fmt.Sprintf(`{"error": "%s"}`, err), http.StatusUnauthorized)
			return
		}

		// 将用户信息存入请求头(简化处理)
		r.Header.Set("X-Username", token.Username)
		next.ServeHTTP(w, r)
	})
}

func main() {
	mux := http.NewServeMux()

	// 登录接口
	mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "只支持POST", http.StatusMethodNotAllowed)
			return
		}

		var req struct {
			Username string `json:"username"`
			Password string `json:"password"`
		}
		json.NewDecoder(r.Body).Decode(&req)

		// 简单验证(实际应查数据库)
		if req.Username != "admin" || req.Password != "123456" {
			http.Error(w, `{"error": "用户名或密码错误"}`, http.StatusUnauthorized)
			return
		}

		token, _ := generateToken(req.Username)
		fmt.Fprintf(w, `{"token": "%s"}`, token)
	})

	// 受保护的接口
	protected := http.NewServeMux()
	protected.HandleFunc("/protected", func(w http.ResponseWriter, r *http.Request) {
		username := r.Header.Get("X-Username")
		fmt.Fprintf(w, `{"message": "欢迎 %s,这是受保护的内容"}`, username)
	})

	mux.Handle("/protected", AuthMiddleware(protected))

	fmt.Println("JWT认证服务运行在 http://localhost:8080")
	http.ListenAndServe(":8080", mux)
}
BASH
# 获取token
curl -X POST http://localhost:8080/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": "123456"}'

# 使用token访问受保护资源
curl -H "Authorization: Bearer <token>" http://localhost:8080/protected

练习3:构建并发安全的API限流器

要求:

参考答案
GO
package main

import (
	"fmt"
	"net/http"
	"sync"
	"time"
)

// SlidingWindowLimiter 滑动窗口限流器
type SlidingWindowLimiter struct {
	mu       sync.Mutex
	windows  map[string]*window
	limit    int
	interval time.Duration
}

type window struct {
	count     int
	startTime time.Time
}

func NewSlidingWindowLimiter(limit int, interval time.Duration) *SlidingWindowLimiter {
	return &SlidingWindowLimiter{
		windows:  make(map[string]*window),
		limit:    limit,
		interval: interval,
	}
}

func (l *SlidingWindowLimiter) Allow(key string) bool {
	l.mu.Lock()
	defer l.mu.Unlock()

	now := time.Now()
	w, exists := l.windows[key]

	if !exists || now.Sub(w.startTime) >= l.interval {
		// 开启新窗口
		l.windows[key] = &window{count: 1, startTime: now}
		return true
	}

	if w.count >= l.limit {
		return false
	}

	w.count++
	return true
}

// 清理过期窗口(应定期调用)
func (l *SlidingWindowLimiter) Cleanup() {
	l.mu.Lock()
	defer l.mu.Unlock()

	now := time.Now()
	for key, w := range l.windows {
		if now.Sub(w.startTime) >= l.interval*2 {
			delete(l.windows, key)
		}
	}
}

func SlidingWindowMiddleware(limiter *SlidingWindowLimiter) func(http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if !limiter.Allow(r.RemoteAddr) {
				w.Header().Set("Retry-After", "60")
				http.Error(w, `{"error": "请求过于频繁"}`, http.StatusTooManyRequests)
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}

func main() {
	limiter := NewSlidingWindowLimiter(5, time.Minute) // 每分钟5次

	// 定期清理
	go func() {
		for {
			time.Sleep(10 * time.Minute)
			limiter.Cleanup()
		}
	}()

	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "请求成功: %s", time.Now().Format("15:04:05"))
	})

	handler := SlidingWindowMiddleware(limiter)(mux)

	fmt.Println("滑动窗口限流服务运行在 http://localhost:8080")
	fmt.Println("限流规则: 每分钟5个请求")
	http.ListenAndServe(":8080", handler)
}

📚 下一课

恭喜你完成了HTTP编程的学习!在下一课中,我们将学习Go语言的测试编程——包括单元测试、表驱动测试、基准测试等,让你写出更可靠的代码。

下一课:测试编程 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏