3.1.3 HTTP 服务端开发 #
HTTP 服务端开发是 Web 应用的核心。Go 语言凭借其出色的并发性能和简洁的 API 设计,使得构建高性能 HTTP 服务器变得简单而高效。本节将深入探讨如何使用 Go 构建功能完整、性能优异的 HTTP 服务器。
基础服务器搭建 #
最简单的 HTTP 服务器 #
让我们从最基本的 HTTP 服务器开始:
package main
import (
"fmt"
"net/http"
"log"
)
func main() {
// 注册处理函数
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World! 你访问的路径是: %s", r.URL.Path)
})
// 启动服务器
log.Println("服务器启动在 http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
使用自定义 ServeMux #
虽然默认的 DefaultServeMux
很方便,但在生产环境中建议使用自定义的 ServeMux
:
package main
import (
"fmt"
"net/http"
"log"
)
func homeHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
fmt.Fprint(w, "欢迎来到首页!")
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "关于我们页面")
}
func main() {
// 创建自定义 ServeMux
mux := http.NewServeMux()
// 注册路由
mux.HandleFunc("/", homeHandler)
mux.HandleFunc("/about", aboutHandler)
// 启动服务器
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
log.Println("服务器启动在 http://localhost:8080")
log.Fatal(server.ListenAndServe())
}
路由处理详解 #
路由模式匹配 #
Go 的 ServeMux
支持两种路由模式:
package main
import (
"fmt"
"net/http"
"strings"
)
func main() {
mux := http.NewServeMux()
// 1. 精确匹配 - 只匹配 /users
mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "用户列表页面")
})
// 2. 前缀匹配 - 匹配 /api/ 开头的所有路径
mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/api/")
fmt.Fprintf(w, "API 端点: %s", path)
})
// 3. 根路径匹配 - 匹配所有未匹配的路径
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
fmt.Fprint(w, "首页")
} else {
http.NotFound(w, r)
}
})
http.ListenAndServe(":8080", mux)
}
路径参数提取 #
由于标准库的路由功能有限,我们需要手动提取路径参数:
package main
import (
"fmt"
"net/http"
"strconv"
"strings"
)
func userHandler(w http.ResponseWriter, r *http.Request) {
// 提取用户ID
path := strings.TrimPrefix(r.URL.Path, "/users/")
if path == "" {
http.Error(w, "用户ID不能为空", http.StatusBadRequest)
return
}
userID, err := strconv.Atoi(path)
if err != nil {
http.Error(w, "无效的用户ID", http.StatusBadRequest)
return
}
// 根据HTTP方法处理不同操作
switch r.Method {
case http.MethodGet:
fmt.Fprintf(w, "获取用户信息: ID=%d", userID)
case http.MethodPut:
fmt.Fprintf(w, "更新用户信息: ID=%d", userID)
case http.MethodDelete:
fmt.Fprintf(w, "删除用户: ID=%d", userID)
default:
http.Error(w, "不支持的HTTP方法", http.StatusMethodNotAllowed)
}
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/users/", userHandler)
http.ListenAndServe(":8080", mux)
}
更强大的路由解决方案 #
对于复杂的路由需求,可以实现一个简单的路由器:
package main
import (
"fmt"
"net/http"
"regexp"
"strconv"
)
type Route struct {
Method string
Pattern *regexp.Regexp
Handler http.HandlerFunc
}
type Router struct {
routes []Route
}
func NewRouter() *Router {
return &Router{}
}
func (r *Router) AddRoute(method, pattern string, handler http.HandlerFunc) {
regex := regexp.MustCompile(pattern)
route := Route{
Method: method,
Pattern: regex,
Handler: handler,
}
r.routes = append(r.routes, route)
}
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
for _, route := range r.routes {
if route.Method == req.Method && route.Pattern.MatchString(req.URL.Path) {
route.Handler(w, req)
return
}
}
http.NotFound(w, req)
}
// 参数提取辅助函数
func extractID(path, prefix string) (int, error) {
idStr := path[len(prefix):]
return strconv.Atoi(idStr)
}
func main() {
router := NewRouter()
// 添加路由
router.AddRoute("GET", "^/users$", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "用户列表")
})
router.AddRoute("GET", "^/users/([0-9]+)$", func(w http.ResponseWriter, r *http.Request) {
id, _ := extractID(r.URL.Path, "/users/")
fmt.Fprintf(w, "用户详情: ID=%d", id)
})
router.AddRoute("POST", "^/users$", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "创建用户")
})
http.ListenAndServe(":8080", router)
}
请求处理 #
处理不同的 HTTP 方法 #
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
var users = []User{
{ID: 1, Name: "张三", Email: "[email protected]"},
{ID: 2, Name: "李四", Email: "[email protected]"},
}
func usersHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
handleGetUsers(w, r)
case http.MethodPost:
handleCreateUser(w, r)
default:
w.Header().Set("Allow", "GET, POST")
http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
}
}
func handleGetUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func handleCreateUser(w http.ResponseWriter, r *http.Request) {
// 检查 Content-Type
if r.Header.Get("Content-Type") != "application/json" {
http.Error(w, "Content-Type 必须是 application/json", http.StatusBadRequest)
return
}
// 读取请求体
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "读取请求体失败", http.StatusBadRequest)
return
}
defer r.Body.Close()
// 解析 JSON
var newUser User
if err := json.Unmarshal(body, &newUser); err != nil {
http.Error(w, "JSON 格式错误", http.StatusBadRequest)
return
}
// 分配 ID
newUser.ID = len(users) + 1
users = append(users, newUser)
// 返回创建的用户
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newUser)
}
func main() {
http.HandleFunc("/users", usersHandler)
fmt.Println("服务器启动在 http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
处理查询参数 #
package main
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
var users = []User{
{ID: 1, Name: "张三", Age: 25},
{ID: 2, Name: "李四", Age: 30},
{ID: 3, Name: "王五", Age: 35},
}
func searchUsersHandler(w http.ResponseWriter, r *http.Request) {
// 获取查询参数
query := r.URL.Query()
// 分页参数
page := 1
if p := query.Get("page"); p != "" {
if parsed, err := strconv.Atoi(p); err == nil && parsed > 0 {
page = parsed
}
}
limit := 10
if l := query.Get("limit"); l != "" {
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
limit = parsed
}
}
// 过滤参数
name := query.Get("name")
minAge := 0
if age := query.Get("min_age"); age != "" {
if parsed, err := strconv.Atoi(age); err == nil {
minAge = parsed
}
}
// 过滤用户
var filteredUsers []User
for _, user := range users {
if name != "" && user.Name != name {
continue
}
if minAge > 0 && user.Age < minAge {
continue
}
filteredUsers = append(filteredUsers, user)
}
// 分页
start := (page - 1) * limit
end := start + limit
if start >= len(filteredUsers) {
filteredUsers = []User{}
} else if end > len(filteredUsers) {
filteredUsers = filteredUsers[start:]
} else {
filteredUsers = filteredUsers[start:end]
}
// 构建响应
response := map[string]interface{}{
"data": filteredUsers,
"page": page,
"limit": limit,
"total": len(users),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func main() {
http.HandleFunc("/users/search", searchUsersHandler)
fmt.Println("服务器启动在 http://localhost:8080")
fmt.Println("示例: http://localhost:8080/users/search?name=张三&min_age=20&page=1&limit=5")
http.ListenAndServe(":8080", nil)
}
处理表单数据 #
package main
import (
"fmt"
"html/template"
"net/http"
)
const formHTML = `
<!DOCTYPE html>
<html>
<head>
<title>用户注册</title>
<meta charset="UTF-8">
</head>
<body>
<h2>用户注册</h2>
<form method="POST" action="/register">
<p>
<label>姓名:</label>
<input type="text" name="name" required>
</p>
<p>
<label>邮箱:</label>
<input type="email" name="email" required>
</p>
<p>
<label>年龄:</label>
<input type="number" name="age" min="1" max="120" required>
</p>
<p>
<label>性别:</label>
<input type="radio" name="gender" value="male"> 男
<input type="radio" name="gender" value="female"> 女
</p>
<p>
<label>爱好:</label>
<input type="checkbox" name="hobbies" value="reading"> 阅读
<input type="checkbox" name="hobbies" value="sports"> 运动
<input type="checkbox" name="hobbies" value="music"> 音乐
</p>
<p>
<input type="submit" value="注册">
</p>
</form>
</body>
</html>
`
func formHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.New("form").Parse(formHTML))
tmpl.Execute(w, nil)
}
func registerHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "只支持 POST 方法", http.StatusMethodNotAllowed)
return
}
// 解析表单数据
if err := r.ParseForm(); err != nil {
http.Error(w, "解析表单失败", http.StatusBadRequest)
return
}
// 获取表单值
name := r.FormValue("name")
email := r.FormValue("email")
age := r.FormValue("age")
gender := r.FormValue("gender")
hobbies := r.Form["hobbies"] // 多选值
// 构建响应
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<h2>注册成功!</h2>")
fmt.Fprintf(w, "<p>姓名: %s</p>", name)
fmt.Fprintf(w, "<p>邮箱: %s</p>", email)
fmt.Fprintf(w, "<p>年龄: %s</p>", age)
fmt.Fprintf(w, "<p>性别: %s</p>", gender)
fmt.Fprintf(w, "<p>爱好: %v</p>", hobbies)
fmt.Fprintf(w, "<a href='/'>返回表单</a>")
}
func main() {
http.HandleFunc("/", formHandler)
http.HandleFunc("/register", registerHandler)
fmt.Println("服务器启动在 http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
处理文件上传 #
package main
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
)
const uploadHTML = `
<!DOCTYPE html>
<html>
<head>
<title>文件上传</title>
<meta charset="UTF-8">
</head>
<body>
<h2>文件上传</h2>
<form method="POST" action="/upload" enctype="multipart/form-data">
<p>
<label>选择文件:</label>
<input type="file" name="file" required>
</p>
<p>
<label>描述:</label>
<input type="text" name="description">
</p>
<p>
<input type="submit" value="上传">
</p>
</form>
</body>
</html>
`
func uploadFormHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, uploadHTML)
}
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()
// 获取描述
description := r.FormValue("description")
// 创建上传目录
uploadDir := "./uploads"
if err := os.MkdirAll(uploadDir, 0755); err != nil {
http.Error(w, "创建上传目录失败", http.StatusInternalServerError)
return
}
// 创建目标文件
filename := filepath.Join(uploadDir, header.Filename)
dst, err := os.Create(filename)
if err != nil {
http.Error(w, "创建文件失败", http.StatusInternalServerError)
return
}
defer dst.Close()
// 复制文件内容
if _, err := io.Copy(dst, file); err != nil {
http.Error(w, "保存文件失败", http.StatusInternalServerError)
return
}
// 返回成功响应
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, "<h2>上传成功!</h2>")
fmt.Fprintf(w, "<p>文件名: %s</p>", header.Filename)
fmt.Fprintf(w, "<p>文件大小: %d 字节</p>", header.Size)
fmt.Fprintf(w, "<p>描述: %s</p>", description)
fmt.Fprintf(w, "<a href='/'>返回上传页面</a>")
}
func main() {
http.HandleFunc("/", uploadFormHandler)
http.HandleFunc("/upload", uploadHandler)
fmt.Println("服务器启动在 http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
中间件实现 #
基础中间件框架 #
package main
import (
"fmt"
"log"
"net/http"
"time"
)
// 中间件类型定义
type Middleware func(http.Handler) http.Handler
// 日志中间件
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 包装 ResponseWriter 以捕获状态码
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
// 调用下一个处理器
next.ServeHTTP(wrapped, r)
// 记录日志
duration := time.Since(start)
log.Printf("%s %s %d %v %s",
r.Method,
r.URL.Path,
wrapped.statusCode,
duration,
r.RemoteAddr,
)
})
}
// ResponseWriter 包装器
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// 恢复中间件
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 recovered: %v", err)
http.Error(w, "内部服务器错误", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
// 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)
})
}
// 认证中间件
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
// 简单的 token 验证
if token != "Bearer secret-token" {
http.Error(w, "未授权", http.StatusUnauthorized)
return
}
next.ServeHTTP(w, r)
})
}
// 中间件链
func Chain(middlewares ...Middleware) Middleware {
return func(final http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
final = middlewares[i](final)
}
return final
}
}
func main() {
// 业务处理器
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, World!")
})
// 需要认证的处理器
protectedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "这是受保护的资源")
})
// 可能出现 panic 的处理器
panicHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
panic("模拟 panic")
})
// 应用中间件
http.Handle("/", Chain(
LoggingMiddleware,
RecoveryMiddleware,
CORSMiddleware,
)(handler))
http.Handle("/protected", Chain(
LoggingMiddleware,
RecoveryMiddleware,
CORSMiddleware,
AuthMiddleware,
)(protectedHandler))
http.Handle("/panic", Chain(
LoggingMiddleware,
RecoveryMiddleware,
)(panicHandler))
log.Println("服务器启动在 http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
限流中间件 #
package main
import (
"fmt"
"net/http"
"sync"
"time"
)
// 令牌桶限流器
type TokenBucket struct {
capacity int64 // 桶容量
tokens int64 // 当前令牌数
rate int64 // 令牌生成速率(每秒)
lastTime time.Time // 上次更新时间
mutex sync.Mutex
}
func NewTokenBucket(capacity, rate int64) *TokenBucket {
return &TokenBucket{
capacity: capacity,
tokens: capacity,
rate: rate,
lastTime: time.Now(),
}
}
func (tb *TokenBucket) Allow() bool {
tb.mutex.Lock()
defer tb.mutex.Unlock()
now := time.Now()
elapsed := now.Sub(tb.lastTime).Seconds()
// 添加新令牌
tb.tokens += int64(elapsed * float64(tb.rate))
if tb.tokens > tb.capacity {
tb.tokens = tb.capacity
}
tb.lastTime = now
// 检查是否有可用令牌
if tb.tokens > 0 {
tb.tokens--
return true
}
return false
}
// 限流中间件
func RateLimitMiddleware(bucket *TokenBucket) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !bucket.Allow() {
http.Error(w, "请求过于频繁", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
}
func main() {
// 创建令牌桶:容量10,每秒生成2个令牌
bucket := NewTokenBucket(10, 2)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "请求成功! 时间: %s", time.Now().Format("15:04:05"))
})
http.Handle("/", RateLimitMiddleware(bucket)(handler))
fmt.Println("服务器启动在 http://localhost:8080")
fmt.Println("限流配置: 容量10,每秒生成2个令牌")
http.ListenAndServe(":8080", nil)
}
错误处理 #
统一错误处理 #
package main
import (
"encoding/json"
"fmt"
"net/http"
)
// 错误响应结构
type ErrorResponse struct {
Error string `json:"error"`
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
// 自定义错误类型
type AppError struct {
Code int
Message string
Details string
}
func (e *AppError) Error() string {
return e.Message
}
// 错误处理函数
func handleError(w http.ResponseWriter, err error) {
var appErr *AppError
var statusCode int
var message string
var details string
// 类型断言检查是否为自定义错误
if e, ok := err.(*AppError); ok {
appErr = e
statusCode = e.Code
message = e.Message
details = e.Details
} else {
// 默认错误处理
statusCode = http.StatusInternalServerError
message = "内部服务器错误"
details = err.Error()
}
// 构建错误响应
errorResp := ErrorResponse{
Error: http.StatusText(statusCode),
Code: statusCode,
Message: message,
Details: details,
}
// 发送 JSON 错误响应
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(errorResp)
}
// 业务逻辑函数
func getUser(id string) (*User, error) {
if id == "" {
return nil, &AppError{
Code: http.StatusBadRequest,
Message: "用户ID不能为空",
Details: "请提供有效的用户ID",
}
}
if id == "999" {
return nil, &AppError{
Code: http.StatusNotFound,
Message: "用户不存在",
Details: fmt.Sprintf("ID为 %s 的用户未找到", id),
}
}
// 模拟数据库错误
if id == "error" {
return nil, fmt.Errorf("数据库连接失败")
}
return &User{ID: 1, Name: "张三"}, nil
}
type User struct {
ID int `json:"id"`
Name string `json:"name"`
}
func userHandler(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get("id")
user, err := getUser(id)
if err != nil {
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
func main() {
http.HandleFunc("/user", userHandler)
fmt.Println("服务器启动在 http://localhost:8080")
fmt.Println("测试URL:")
fmt.Println(" http://localhost:8080/user?id=1 (正常)")
fmt.Println(" http://localhost:8080/user?id= (400错误)")
fmt.Println(" http://localhost:8080/user?id=999 (404错误)")
fmt.Println(" http://localhost:8080/user?id=error (500错误)")
http.ListenAndServe(":8080", nil)
}
服务器配置和优化 #
生产环境服务器配置 #
package main
import (
"context"
"crypto/tls"
"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("Hello, Production Server!"))
})
// 配置 TLS
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
PreferServerCipherSuites: true,
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
},
}
// 配置服务器
server := &http.Server{
Addr: ":8443",
Handler: mux,
TLSConfig: tlsConfig,
ReadTimeout: 15 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1 << 20, // 1MB
ErrorLog: log.New(os.Stderr, "SERVER: ", log.LstdFlags),
}
// 启动服务器
go func() {
log.Println("HTTPS 服务器启动在 :8443")
if err := server.ListenAndServeTLS("server.crt", "server.key"); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务器启动失败: %v", err)
}
}()
// 优雅关闭
gracefulShutdown(server)
}
func gracefulShutdown(server *http.Server) {
// 创建信号通道
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
// 等待信号
<-quit
log.Println("正在关闭服务器...")
// 创建超时上下文
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// 关闭服务器
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("服务器强制关闭: %v", err)
}
log.Println("服务器已关闭")
}
性能监控 #
package main
import (
"encoding/json"
"fmt"
"net/http"
"runtime"
"sync/atomic"
"time"
)
// 性能指标
type Metrics struct {
RequestCount int64 `json:"request_count"`
ErrorCount int64 `json:"error_count"`
AverageResponse float64 `json:"average_response_ms"`
Uptime string `json:"uptime"`
MemoryUsage string `json:"memory_usage"`
Goroutines int `json:"goroutines"`
}
var (
requestCount int64
errorCount int64
totalResponse int64
startTime = time.Now()
)
// 监控中间件
func MetricsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 包装 ResponseWriter
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
// 处理请求
next.ServeHTTP(wrapped, r)
// 更新指标
duration := time.Since(start)
atomic.AddInt64(&requestCount, 1)
atomic.AddInt64(&totalResponse, duration.Nanoseconds()/1000000) // 转换为毫秒
if wrapped.statusCode >= 400 {
atomic.AddInt64(&errorCount, 1)
}
})
}
func metricsHandler(w http.ResponseWriter, r *http.Request) {
var m runtime.MemStats
runtime.ReadMemStats(&m)
reqCount := atomic.LoadInt64(&requestCount)
totalResp := atomic.LoadInt64(&totalResponse)
var avgResponse float64
if reqCount > 0 {
avgResponse = float64(totalResp) / float64(reqCount)
}
metrics := Metrics{
RequestCount: reqCount,
ErrorCount: atomic.LoadInt64(&errorCount),
AverageResponse: avgResponse,
Uptime: time.Since(startTime).String(),
MemoryUsage: fmt.Sprintf("%.2f MB", float64(m.Alloc)/1024/1024),
Goroutines: runtime.NumGoroutine(),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(metrics)
}
func main() {
mux := http.NewServeMux()
// 业务处理器
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 模拟处理时间
time.Sleep(time.Duration(10+r.URL.Query().Get("delay")) * time.Millisecond)
fmt.Fprint(w, "Hello, World!")
})
// 错误处理器
mux.HandleFunc("/error", func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "模拟错误", http.StatusInternalServerError)
})
// 监控端点
mux.HandleFunc("/metrics", metricsHandler)
// 应用监控中间件
handler := MetricsMiddleware(mux)
fmt.Println("服务器启动在 http://localhost:8080")
fmt.Println("监控端点: http://localhost:8080/metrics")
http.ListenAndServe(":8080", handler)
}
小结 #
本节深入介绍了 Go HTTP 服务端开发的核心技术:
- 基础服务器搭建:从简单服务器到自定义 ServeMux 的使用
- 路由处理:路由模式匹配、参数提取和自定义路由器实现
- 请求处理:处理不同 HTTP 方法、查询参数、表单数据和文件上传
- 中间件实现:日志、认证、CORS、限流等中间件的设计和实现
- 错误处理:统一错误处理机制和自定义错误类型
- 服务器配置:生产环境配置、TLS 设置和优雅关闭
- 性能监控:请求指标收集和监控端点实现
这些技术为构建高性能、可维护的 HTTP 服务器提供了坚实的基础。在下一节中,我们将学习 HTTP 客户端开发,了解如何发送 HTTP 请求和处理响应。