跳转到主要内容
模块 10 · 第 5 课(共 5)课程中的50/50~18 min
模块课程(5/5)

迷你项目:HTTP 服务器

net/http stdlib 包足以构建一个生产就绪的 HTTP 服务器:路由、处理程序、中间件、正常关闭。无需框架。对于典型的 REST API,这是基础。

你好世界

Go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "hello %s", r.URL.Path[1:])
    })
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

http.ListenAndServe 会阻塞并且仅在出错时返回。 nil 作为第二个参数 =“使用 DefaultServeMux”。

处理程序签名

所有处理程序共享相同的签名:

Go
func(w http.ResponseWriter, r *http.Request)
  • w http.ResponseWriter — 输出:标头、状态代码、正文。
  • r *http.Request — 输入:方法、URL、标题、正文、上下文。

典型模式:

Go
func ping(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK) // optional, defaults to 200
    fmt.Fprintln(w, `{"ok":true}`)
}

使用 ServeMux 进行路由

*http.ServeMux 是基本路由器。从 Go 1.22 开始,它支持带有方法和路径参数的模式:

Go
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintln(w, "user", id)
})
mux.HandleFunc("POST /users", createUser)

http.ListenAndServe(":8080", mux)

对于更复杂的路由器(组、集成中间件、正则表达式),有 chi、gorilla/mux、gin、echo。 stdlib 涵盖了 90% 的情况。

中间件作为装饰器

中间件是一个函数 func(http.Handler) http.Handler:

Go
func logMW(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
    })
}

http.ListenAndServe(":8080", logMW(mux))

链接它们:logMW(authMW(rateLimitMW(mux)))。每个中间件都会装饰下一个中间件。

http.Server 用于精细控制

http.ListenAndServe(addr, h) 是一个快捷方式。对于超时、TLS 和正常关闭,您需要实例化 http.Server:

Go
srv := &http.Server{
    Addr:              ":8080",
    Handler:           mux,
    ReadHeaderTimeout: 5 * time.Second,
    ReadTimeout:       30 * time.Second,
    WriteTimeout:      30 * time.Second,
    IdleTimeout:       2 * time.Minute,
}
log.Fatal(srv.ListenAndServe())

根据上下文优雅关闭

当进程收到 SIGINT/SIGTERM 时,您希望在关闭之前完成正在进行的请求:

Go
ctx, stop := signal.NotifyContext(context.Background(),
    os.Interrupt, syscall.SIGTERM)
defer stop()

srv := &http.Server{Addr: ":8080", Handler: mux}
go func() {
    if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        log.Fatal(err)
    }
}()

<-ctx.Done() // wait for the signal

shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
    log.Println("shutdown:", err)
}

srv.Shutdown 停止接受新连接并等待活动连接完成(直到传入的截止日期为止)。

读取 JSON 正文

Go
type CreateUserReq struct {
    Name string `json:"name"`
}

func createUser(w http.ResponseWriter, r *http.Request) {
    var req CreateUserReq
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // ... use req.Name
    w.WriteHeader(http.StatusCreated)
}

r.Body 是一个 io.ReadCloser:服务器会自动关闭它,但 json.NewDecoder 不会读取第一个 JSON 对象(对于验证输入很有用)。

练习

锻炼#go.m10.l5.e1
尝试:0加载中...

注册l'handler你好苏拉路线'/'usando http.HandleFunc。

正在加载编辑器...

3 次尝试后可用的解决方案

锻炼#go.m10.l5.e2
尝试:0加载中...

Avvia il server su :8080 con http.ListenAndServe e stampa l'errore se non è nil.

正在加载编辑器...

3 次尝试后可用的解决方案

Quiz#go.m10.l5.e3
准备好

Go 中 HTTP 处理程序的惯用语是什么?

Go
func H(???) { ... }
答案选项