文章目录

Go HTTP 服务器:http 包创建 Web 服务

发布于 2026-04-05 06:40:49 · 浏览 15 次 · 评论 0 条

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.ResponseWriterhttp.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)
}

中间件的执行顺序很重要。上述代码中,loggingMiddlewareheaderMiddleware 会先执行,最后才到达具体处理函数。如果中间件调用 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 服务器需要关注稳定性、性能和可观测性。

超时控制是防止资源泄漏的关键。长耗时的请求会占用连接池资源,导致新请求无法处理。应为读操作、写操作和空闲连接分别设置合理超时。上面示例中的 ReadTimeoutWriteTimeoutIdleTimeout 参数即用于此目的。

优雅关闭允许服务器在接收到终止信号时,先处理完正在进行的请求,再平滑退出。实现方式如下:

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 开发场景。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文