FunTester 从零开始掌握 Go HTTP 路由与处理器

FunTester · 2025年07月22日 · 489 次阅读

如果你熟悉 MVC 架构,可以把处理程序看作是控制器的角色。它们负责执行应用程序的核心逻辑,并生成 HTTP 响应的头部和主体。而 Servemux(也叫路由器)则像是一个调度员,负责管理 URL 路径和处理程序之间的映射关系。通常,一个应用程序会使用一个 Servemux 来统一管理所有路由。

Go 的 net/http 包提供了一个简单但功能强大的 http.ServeMux,同时还包含一些用于生成常见处理程序的函数,例如 http.FileServer()http.NotFoundHandler()http.RedirectHandler()

使用 ServeMux 和内置处理程序

下面我们通过一个简单的示例来了解它们的使用方式:

$ mkdir example
$ cd example
$ go mod init example.com
$ touch main.go

文件:main.go

package main

import (
    "log"
    "net/http"
)

func main() {
    // 使用 http.NewServeMux() 创建一个空的 Servemux
    mux := http.NewServeMux()

    // 使用 http.RedirectHandler() 创建一个处理程序,将所有请求重定向到 http://example.org
    rh := http.RedirectHandler("http://example.org", 307)

    // 使用 mux.Handle() 将处理程序注册到 Servemux,并映射到路径 /foo
    mux.Handle("/foo", rh)

    log.Print("FunTester 监听中...") // 修改日志信息,增加 FunTester 相关内容

    // 创建一个服务器并开始监听传入的请求,同时将 Servemux 作为参数传递
    http.ListenAndServe(":3000", mux)
}

运行该程序:

$ go run main.go
2025/06/24 15:09:43 FunTester 监听中...

如果你向 http://localhost:3000/foo 发出请求,会发现它被成功重定向:

$ curl -IL localhost:3000/foo
HTTP/1.1 307 Temporary Redirect
Content-Type: text/html; charset=utf-8
Location: http://example.org
Date: Mon, 06 Dec 2021 14:10:18 GMT

而其他路径的请求会返回 404 Not Found 错误:

$ curl -IL localhost:3000/bar
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
Date: Mon, 06 Dec 2021 14:22:51 GMT
Content-Length: 19

自定义处理程序

虽然 net/http 提供了一些内置处理程序,但在实际开发中,我们通常需要创建自定义处理程序来满足具体需求。

在 Go 中,任何满足 http.Handler 接口的对象都可以作为处理程序。该接口定义如下:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

简单来说,处理程序必须实现一个 ServeHTTP() 方法,其签名为:

ServeHTTP(http.ResponseWriter, *http.Request)

以下是一个自定义处理程序的示例,它以特定格式返回当前时间:

type timeHandler struct {
    format string
}

func (th timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(th.format)
    w.Write([]byte("FunTester 当前时间是: " + tm)) // 修改返回内容,增加 FunTester 相关内容
}

在这个例子中,我们定义了一个 timeHandler 结构体,并实现了 ServeHTTP() 方法。这样,timeHandler 就满足了 http.Handler 接口,可以作为处理程序使用。

让我们通过一个完整的示例来演示:

package main

import (
    "log"
    "net/http"
    "time"
)

type timeHandler struct {
    format string
}

func (th timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(th.format)
    w.Write([]byte("FunTester 当前时间是: " + tm)) // 修改返回内容,增加 FunTester 相关内容
}

func main() {
    mux := http.NewServeMux()

    // 初始化 timeHandler,并设置时间格式
    th := timeHandler{format: time.RFC1123}

    // 将 timeHandler 注册到 Servemux
    mux.Handle("/time", th)

    log.Print("FunTester 监听中...") // 修改日志信息,增加 FunTester 相关内容
    http.ListenAndServe(":3000", mux)
}

运行该程序后,访问 http://localhost:3000/time,你会收到包含当前时间的响应,例如:

$ curl localhost:3000/time
FunTester 当前时间是: Mon, 06 Dec 2021 15:33:21 CET

函数作为处理程序

对于简单的场景,定义一个结构体来实现处理程序可能显得过于繁琐。幸运的是,我们可以直接使用函数作为处理程序:

func timeHandler(w http.ResponseWriter, r *http.Request) {
    tm := time.Now().Format(time.RFC1123)
    w.Write([]byte("当前时间是: " + tm))
}

虽然这个函数本身并不满足 http.Handler 接口,但我们可以通过将其转换为 http.HandlerFunc 类型来实现这一点。http.HandlerFunc 是一个内置类型,它可以将任何具有签名 func(http.ResponseWriter, *http.Request) 的函数转换为处理程序。

以下是使用 http.HandlerFunc 的示例:

func main() {
    mux := http.NewServeMux()

    // 将 timeHandler 转换为 http.HandlerFunc 类型
    th := http.HandlerFunc(timeHandler)

    // 注册到 Servemux
    mux.Handle("/time", th)

    log.Print("监听中...")
    http.ListenAndServe(":3000", mux)
}

实际上,Go 提供了一个更简洁的方式来注册函数处理程序:使用 mux.HandleFunc() 方法。示例如下:

func main() {
    mux := http.NewServeMux()

    // 使用 HandleFunc 注册函数处理程序
    mux.HandleFunc("/time", timeHandler)

    log.Print("监听中...")
    http.ListenAndServe(":3000", mux)
}

将变量传递给处理程序

大多数情况下,像这样使用函数作为处理程序效果很好。但是,当情况变得更加复杂时,就会有一些限制。

你可能已经注意到,与之前的方法不同,我们必须在 timeHandler 函数中硬编码时间格式。当你想将信息或变量从 main() 传递给处理程序时,该怎么办?

一种巧妙的方法是将我们的处理程序逻辑放入闭包中,并封闭我们想要使用的变量,如下所示:

package main

import (
    "log"
    "net/http"
    "time"
)

func timeHandler(format string) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        tm := time.Now().Format(format)
        w.Write([]byte("FunTester 当前时间是: " + tm)) // 修改返回内容,增加 FunTester 相关内容
    }
    return http.HandlerFunc(fn)
}

func main() {
    mux := http.NewServeMux()

    // 使用闭包将变量传递给处理程序
    th := timeHandler(time.RFC1123)
    mux.Handle("/time", th)

    log.Print("FunTester 监听中...") // 修改日志信息,增加 FunTester 相关内容
    http.ListenAndServe(":3000", mux)
}

FunTester 原创精华
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册