Go HTTP 服务器:http 包创建 Web 服务
Go 语言内置的 net/http 包提供了构建 HTTP 服务器和客户端的完整功能。这个包设计简洁、功能强大,足以应对从简单 API 到复杂 Web 服务的各种场景。无需额外依赖,几行代码即可启动一个可运行的 HTTP 服务器。
一、最简服务器:五秒启动
Go 的 HTTP 服务器门槛极低。最基础的服务器只需三行代码即可运行。
创建一个 main.go 文件,写入以下内容:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, World!")
})
http.ListenAndServe(":8080", nil)
}
运行上述程序:
go run main.go
启动成功后,打开浏览器访问 http://localhost:8080,页面会显示 "Hello, World!"。
这段代码的核心逻辑如下:http.HandleFunc 注册了一个根路径 / 的处理函数,当有请求到达时,该函数会向响应体写入 "Hello, World!"。http.ListenAndServe 负责在指定端口监听请求,:8080 表示监听所有网络接口的 8080 端口。
二、请求与响应:理解处理函数签名
HTTP 处理函数接收两个关键参数:http.ResponseWriter 和 http.Request。
http.ResponseWriter 是服务器向客户端发送响应的工具。通过它可以设置状态码、响应头和响应体。http.Request 则封装了客户端发来的请求信息,包括请求方法、URL、请求头、请求体以及查询参数等。
以下示例展示了如何获取请求信息并构造多样化响应:
func handler(w http.ResponseWriter, r *http.Request) {
// 设置响应头
w.Header().Set("Content-Type", "application/json")
// 获取请求方法
method := r.Method
// 获取查询参数
name := r.URL.Query().Get("name")
if name == "" {
name = "Guest"
}
// 设置状态码并返回 JSON 响应
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{"method":"%s","name":"%s"}`, method, name)
}
关键操作总结:
| 操作 | 方法 | 示例 |
|---|---|---|
| 读取请求头 | r.Header.Get() |
contentType := r.Header.Get("Content-Type") |
| 读取请求体 | io.ReadAll(r.Body) |
body, _ := io.ReadAll(r.Body) |
| 设置响应头 | w.Header().Set() |
w.Header().Set("X-Custom", "value") |
| 写入响应体 | fmt.Fprint(w, ...) |
fmt.Fprint(w, "text") |
| 设置状态码 | w.WriteHeader() |
w.WriteHeader(http.StatusNotFound) |
三、路由管理:构建清晰的 URL 结构
简单的应用可以用 http.HandleFunc 为每个路径单独注册,但随着应用规模扩大,这种方式会变得难以维护。Go 提供了多种路由管理方案。
3.1 使用 ServeMux
http.ServeMux 是 Go 标准库中的多路复用器,负责将请求 URL 映射到对应的处理函数。它是线程安全的,可以安全地在多个 goroutine 中注册处理器。
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/users", listUsers)
mux.HandleFunc("/users/", getUser)
mux.HandleFunc("/products", listProducts)
mux.HandleFunc("/health", healthCheck)
http.ListenAndServe(":8080", mux)
}
func listUsers(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "用户列表")
}
func getUser(w http.ResponseWriter, r *http.Request) {
// 提取路径中的用户 ID
userID := strings.TrimPrefix(r.URL.Path, "/users/")
fmt.Fprintf(w, "用户详情: %s", userID)
}
func listProducts(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "产品列表")
}
func healthCheck(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, `{"status":"ok"}`)
}
ServeMux 的匹配规则遵循最长前缀匹配。请求 /users/123 会优先匹配 /users/ 处理器,而非 /users 处理器。这种设计使得构建 RESTful 风格的 URL 结构变得自然且直观。
3.2 第三方路由库
标准库 ServeMux 的功能较为基础,不支持路由参数、路径正则匹配或中间件。生产环境中,通常会选择功能更丰富的第三方路由库。
gorilla/mux 是目前最流行的 Go 路由库之一:
package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/users/{id}", getUserByID).Methods("GET")
r.HandleFunc("/users", createUser).Methods("POST")
r.HandleFunc("/users/{id}", updateUser).Methods("PUT")
r.HandleFunc("/users/{id}", deleteUser).Methods("DELETE")
r.HandleFunc("/products/{category}/{id}", getProduct).PathPrefix("/products")
http.ListenAndServe(":8080", r)
}
func getUserByID(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userID := vars["id"]
fmt.Fprintf(w, "获取用户: %s", userID)
}
func createUser(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "创建用户")
}
func updateUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
fmt.Fprintf(w, "更新用户: %s", vars["id"])
}
func deleteUser(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
fmt.Fprintf(w, "删除用户: %s", vars["id"])
}
func getProduct(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
category := vars["category"]
productID := vars["id"]
fmt.Fprintf(w, "分类: %s, 产品: %s", category, productID)
}
gorilla/mux 提供的功能包括:基于 HTTP 方法的路由限制、路径变量提取、正则表达式匹配、域名限制以及自动选项请求处理。这些特性对于构建规范的 API 服务至关重要。
四、中间件:统一处理横切关注点
中间件是位于请求入口和处理函数之间的逻辑层,常用于日志记录、身份验证、请求限流、CORS 处理等场景。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()
// 继续处理请求
next.ServeHTTP(w, r)
// 请求完成后记录日志
duration := time.Since(start)
log.Printf("%s %s %s %d %v",
r.Method,
r.URL.Path,
r.RemoteAddr,
http.StatusOK,
duration)
})
}
// 添加响应头的中间件
func headerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-XSS-Protection", "1; mode=block")
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")
if token == "" {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, `{"error":"missing token"}`)
return
}
// 验证 token 的逻辑(示例省略)
if token != "Bearer valid-token" {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, `{"error":"invalid token"}`)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
// 需要认证的路由
protected := mux.PathPrefix("/api").Subrouter()
protected.Use(authMiddleware)
protected.HandleFunc("/data", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, `{"data":"protected content"}`)
})
// 应用多个中间件
wrappedMux := loggingMiddleware(headerMiddleware(mux))
http.ListenAndServe(":8080", wrappedMux)
}
中间件的执行顺序很重要。上述代码中,loggingMiddleware 和 headerMiddleware 会先执行,最后才到达具体处理函数。如果中间件调用 next.ServeHTTP,请求会继续传递;如果直接返回,则请求处理在此中断。
五、静态文件服务
Web 应用通常需要提供 CSS、JavaScript、图片等静态文件。Go 的 http.FileServer 可以快速实现这一功能。
func main() {
mux := http.NewServeMux()
// 方式一:将指定目录作为根目录
fs := http.FileServer(http.Dir("./static"))
mux.Handle("/static/", http.StripPrefix("/static/", fs))
// 方式二:直接托管整个目录
mux.Handle("/", http.FileServer(http.Dir("./public")))
http.ListenAndServe(":8080", mux)
}
http.StripPrefix 用于去除 URL 中的前缀路径。例如,访问 /static/css/style.css 时,实际请求的是 ./static/css/style.css 文件。如果不添加 StripPrefix,则需要将文件放在 ./static/static/css/style.css 中,这显然不符合预期。
六、请求体解析:处理表单和 JSON
Web 服务常常需要接收客户端提交的数据。Go 提供了便捷的解析工具。
6.1 表单数据
func handleForm(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
// 解析表单数据
if err := r.ParseForm(); err != nil {
http.Error(w, "解析失败", http.StatusBadRequest)
return
}
username := r.FormValue("username")
password := r.FormValue("password")
fmt.Fprintf(w, "用户名: %s, 密码: %s", username, password)
}
}
6.2 JSON 数据
type User struct {
Username string `json:"username"`
Email string `json:"email"`
Age int `json:"age"`
}
func handleJSON(w http.ResponseWriter, r *http.Request) {
var user User
// 解析 JSON 请求体
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&user); err != nil {
http.Error(w, "JSON 格式错误", http.StatusBadRequest)
return
}
// 验证数据
if user.Username == "" {
http.Error(w, "用户名不能为空", http.StatusBadRequest)
return
}
// 返回 JSON 响应
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
}
七、完整项目结构
实际开发中,建议采用清晰的项目结构。以下是一个典型的 Go Web 项目布局:
myapp/
├── main.go # 程序入口
├── go.mod # 依赖管理
├── config/
│ └── config.go # 配置文件
├── handlers/
│ ├── user.go # 用户相关处理器
│ └── product.go # 产品相关处理器
├── middleware/
│ ├── auth.go # 认证中间件
│ └── logger.go # 日志中间件
├── models/
│ └── user.go # 数据模型
├── repository/
│ └── user_repo.go # 数据访问层
└── services/
└── user_svc.go # 业务逻辑层
main.go 的典型写法:
package main
import (
"log"
"net/http"
"myapp/handlers"
"myapp/middleware"
)
func main() {
// 初始化路由
mux := http.NewServeMux()
// 注册处理器
mux.HandleFunc("/health", handlers.HealthCheck)
mux.HandleFunc("/api/users", handlers.ListUsers)
mux.HandleFunc("/api/users/", handlers.GetUser)
// 应用中间件
handler := middleware.Logging(middleware.CORS(mux))
// 启动服务器
server := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Println("服务器启动在 :8080")
if err := server.ListenAndServe(); err != nil {
log.Fatalf("服务器错误: %v", err)
}
}
八、生产环境最佳实践
构建生产级 HTTP 服务器需要关注稳定性、性能和可观测性。
超时控制是防止资源泄漏的关键。长耗时的请求会占用连接池资源,导致新请求无法处理。应为读操作、写操作和空闲连接分别设置合理超时。上面示例中的 ReadTimeout、WriteTimeout 和 IdleTimeout 参数即用于此目的。
优雅关闭允许服务器在接收到终止信号时,先处理完正在进行的请求,再平滑退出。实现方式如下:
func main() {
mux := http.NewServeMux()
// ... 路由配置
server := &http.Server{Addr: ":8080", Handler: mux}
// 在 goroutine 中启动服务器
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务器启动失败: %v", err)
}
}()
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("正在关闭服务器...")
// 给予 5 秒时间完成正在处理的请求
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("服务器关闭错误: %v", err)
}
log.Println("服务器已关闭")
}
Go 的 net/http 包以其简洁的设计和强大的功能,成为构建 HTTP 服务的理想选择。从最简服务器到生产级应用,这一包提供了所需的全部基础设施。掌握其核心概念——处理器、路由、中间件和服务器配置——即可应对绝大多数 Web 开发场景。

暂无评论,快来抢沙发吧!