HTTP编程
第22课:HTTP编程
🎯 生活类比
想象你经营一家餐厅:
- HTTP服务器就像餐厅的前台接待——顾客(客户端)来了,前台根据需求把他们引导到正确的服务窗口
- 路由(ServeMux)就像餐厅的菜单分类——不同的菜品(URL路径)由不同的厨师(处理函数)负责
- Handler接口就像厨师的工作标准——无论做什么菜,都要遵循统一的流程规范
- 中间件就像餐厅的服务流程——顾客进门先换鞋、再洗手、然后入座,这些步骤是通用的,不针对某道菜
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.HandleFunc和http.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{})
区别:
HandleFunc:接收func(ResponseWriter, *Request)类型的函数Handle:接收实现了Handler接口的类型HandleFunc内部会将函数包装成HandlerFunc适配器
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)
原因:
DefaultServeMux是全局的,任何包都可以注册路由,可能导致冲突- 自定义Mux的作用域可控,避免意外的路由覆盖
- 方便在不同服务器实例间复用和测试
📖 小节
本节课我们学习了Go HTTP编程的核心内容:
| 知识点 | 掌握要点 |
|---|---|
| HTTP客户端 | http.Get/Post发送请求,resp.Body必须关闭 |
| HTTP服务器 | http.ListenAndServe启动服务,路由注册 |
| Handler接口 | ServeHTTP(ResponseWriter, *Request)方法 |
| HandlerFunc | 函数适配器,将函数转为Handler |
| ServeMux | 路由器,支持前缀匹配 |
| 中间件 | 包装Handler,在请求前后插入通用逻辑 |
| 优雅关闭 | 使用server.Shutdown和信号处理 |
核心模式:
TEXT
客户端请求 → 中间件链 → 路由匹配 → Handler处理 → 响应返回
📝 作业
练习1:实现文件上传服务
要求:
- 创建
POST /upload接口接收文件上传 - 限制文件大小为10MB
- 只允许上传图片类型(jpg, png, gif)
- 保存文件到
./uploads目录 - 返回文件的访问URL
参考答案
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认证中间件
要求:
- 实现简单的JWT生成和验证(可用Base64模拟)
- 创建
POST /login接口获取token - 创建需要认证的
GET /protected接口 - 通过
Authorization: Bearer <token>传递token
参考答案
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限流器
要求:
- 实现滑动窗口限流算法
- 支持配置每个窗口的最大请求数和窗口大小
- 提供中间件接口
- 使用Redis或内存存储(内存即可)
参考答案
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语言的测试编程——包括单元测试、表驱动测试、基准测试等,让你写出更可靠的代码。



