首页
/ Fiber v3 HTTP 客户端实战:Basic Auth、TLS、复用 fasthttp 传输层与 Cookie Jar

Fiber v3 HTTP 客户端实战:Basic Auth、TLS、复用 fasthttp 传输层与 Cookie Jar

2026-09-05 22:56:59作者:郜逊炳

本文围绕 Fiber v3 官方文档 examples 展开,系统讲解 client 包的四类实战场景:使用 Basic Auth 访问受保护接口、通过自定义证书池完成 TLS 互信、复用已有的 fasthttp.HostClient / LBClient 传输层(连接池、负载均衡),以及基于 RFC 6265 的 Cookie Jar 存取规则。读完本文,你可以掌握 Fiber 客户端从创建、配置到请求执行的完整用法,并能对照 client/client.goclient/cookiejar.go 等源码理解每一行为背后的实现细节。

一、Basic Auth:客户端与 basicauth 中间件配合

客户端通过在请求头中携带 Base64 编码的 user:pass 凭证完成认证,服务端则由 Fiber 自带的 basicauth 中间件 校验。中间件的 Users 映射支持明文密码,也支持 {SHA256} 等前缀标记的哈希密码。

客户端代码:

package main

import (
    "encoding/base64"
    "fmt"

    "github.com/gofiber/fiber/v3/client"
)

func main() {
    cc := client.New()

    out := base64.StdEncoding.EncodeToString([]byte("john:doe"))
    resp, err := cc.Get("http://localhost:3000", client.Config{
        Header: map[string]string{
            "Authorization": "Basic " + out,
        },
    })
    if err != nil {
        panic(err)
    }

    fmt.Print(string(resp.Body()))
}

服务端代码:

package main

import (
    "github.com/gofiber/fiber/v3"
    "github.com/gofiber/fiber/v3/middleware/basicauth"
)

func main() {
    app := fiber.New()
    app.Use(
        basicauth.New(basicauth.Config{
            Users: map[string]string{
                // "doe" hashed using SHA-256
                "john": "{SHA256}eZ75KhGvkY4/t0HfQpNPO1aO0tk6wd908bjUGieTKm8=",
            },
        }),
    )

    app.Get("/", func(c fiber.Ctx) error {
        return c.SendString("Hello, World!")
    })

    app.Listen(":3000")
}

从源码看,cc.Get(url, cfg ...Config) 的实现位于 client/client.go#L711-L715:它从对象池取出一个 Request,通过 setConfigToRequestConfig.Header 写入请求级 Header,再调用 req.Get(url) 发送。Config 的完整字段定义见 client/client.go#L829-L843,包括 CtxBodyHeaderParamCookiePathParamFormDataUserAgentRefererFileTimeoutMaxRedirectsDisablePathNormalizing。其中 Body 的序列化优先级为 Body > FormData > File,且非 string / []byte 类型的 Body 默认按 JSON 序列化(见 setConfigToRequestclient/client.go#L846-L913)。

二、TLS:自定义根证书池

当服务端使用自签证书(或私有 CA)时,客户端需要把该 CA 证书追加到信任池中。文档示例从系统证书池出发,再追加本地 ssl.cert,然后通过 SetTLSConfig 覆盖整个 TLS 配置:

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "os"

    "github.com/gofiber/fiber/v3/client"
)

func main() {
    cc := client.New()

    certPool, err := x509.SystemCertPool()
    if err != nil {
        panic(err)
    }

    cert, err := os.ReadFile("ssl.cert")
    if err != nil {
        panic(err)
    }

    certPool.AppendCertsFromPEM(cert)
    cc.SetTLSConfig(&tls.Config{
        RootCAs: certPool,
    })

    resp, err := cc.Get("https://localhost:3000")
    if err != nil {
        panic(err)
    }

    fmt.Print(string(resp.Body()))
}

服务端则通过 fiber.ListenConfigCertFile / CertKeyFile 直接监听 HTTPS:

package main

import (
    "github.com/gofiber/fiber/v3"
)

func main() {
    app := fiber.New()

    app.Get("/", func(c fiber.Ctx) error {
        return c.SendString("Hello, World!")
    })

    err := app.Listen(":3000", fiber.ListenConfig{
        CertFile:    "ssl.cert",
        CertKeyFile: "ssl.key",
    })
    if err != nil {
        panic(err)
    }
}

源码层面,SetTLSConfig 的实现见 client/client.go#L296-L303——它把配置透传给底层 httpClientTransport,因此无论客户端底层是 fasthttp.ClientHostClient 还是 LBClient,TLS 配置都能正确落位。另外两个便捷入口值得注意:

  • SetRootCertificate(path) / SetRootCertificateFromString(pem):直接向 RootCAs 追加证书,失败时返回 ErrFailedToAppendCertclient/client.go#L305-L355);
  • 未显式设置过 TLS 配置时,TLSConfig() 会初始化一个 MinVersion: tls.VersionTLS12 的默认配置(client/client.go#L282-L294)。

三、复用 fasthttp 传输层:HostClient 与 LBClient

Fiber 客户端可以在创建时包裹既有的 fasthttp 客户端,从而复用已经调优过的连接池、自定义 Dialer 或负载均衡逻辑。三个构造函数的对应关系:

构造函数 底层传输 适用场景
client.New() 新建 fasthttp.Client 默认用法(client/client.go#L925-L931
client.NewWithClient(c) 已有 *fasthttp.Client 复用通用客户端
client.NewWithHostClient(c) 已有 *fasthttp.HostClient 固定单主机连接池
client.NewWithLBClient(c) 已有 *fasthttp.LBClient 多后端负载均衡

三个变体实现于 client/client.go#L933-L955,内部均为 nil 校验后包装成对应 transport。

3.1 HostClient:固定主机的连接池

package main

import (
    "log"
    "time"

    "github.com/gofiber/fiber/v3/client"
    "github.com/valyala/fasthttp"
)

func main() {
    hc := &fasthttp.HostClient{
        Addr:                "api.internal:443",
        IsTLS:               true,
        MaxConnDuration:     30 * time.Second,
        MaxIdleConnDuration: 10 * time.Second,
    }

    cc := client.NewWithHostClient(hc)

    resp, err := cc.Get("https://api.internal:443/status")
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("status=%d body=%s", resp.StatusCode(), resp.Body())
}

HostClient 对单一地址长驻连接,MaxConnDuration / MaxIdleConnDuration 控制连接寿命与空闲回收,适合面向单个内部服务的高频调用。

3.2 LBClient:多后端负载均衡

package main

import (
    "log"
    "time"

    "github.com/gofiber/fiber/v3/client"
    "github.com/valyala/fasthttp"
)

func main() {
    lb := &fasthttp.LBClient{
        Timeout: 2 * time.Second,
        Clients: []fasthttp.BalancingClient{
            &fasthttp.HostClient{Addr: "edge-1.internal:8080"},
            &fasthttp.HostClient{Addr: "edge-2.internal:8080"},
        },
    }

    cc := client.NewWithLBClient(lb)

    // Per-request overrides such as redirects, retries, TLS, and proxy dialers
    // are shared across every host client managed by the load balancer.
    resp, err := cc.Get("http://service.internal/api")
    if err != nil {
        log.Fatal(err)
    }

    log.Printf("status=%d body=%s", resp.StatusCode(), resp.Body())
}

client/transport.golbClientTransport 实现可以确认文档注释的含义:SetTLSConfigSetDial 都会通过 forEachHostClient 递归遍历负载均衡器下的所有 HostClient(包括嵌套的 LBClient),因此对 cc 的一次配置等于作用于每一台后端(client/transport.go#L208-L218client/transport.go#L247-L253)。

四、Cookie Jar:RFC 6265 语义与使用示例

客户端可以通过挂载 Cookie Jar 在多次请求间存储并复用 Cookie。Jar 基于 sync.Pool 池化,用 AcquireCookieJar / ReleaseCookieJar 获取和归还(client/cookiejar.go#L64-L84),再经 cc.SetCookieJar(jar) 挂到客户端上。

4.1 存储与检索规则(RFC 6265)

  • 默认 Path 作用域:不带 Path 属性的 Set-Cookie 只作用于“发起该请求的目录”,而不是整个主机。对 /api/login 的响应设置的 Cookie 默认 Path=/api,不会发到 /。要跨全站生效需显式下发 Path=/。对应实现是 defaultCookiePathForclient/cookiejar.go#L483-L496),它按 RFC 6265 第 5.1.4 节取请求路径的父目录。

  • Cookie 三元组标识:Cookie 以 (name, domain, path) 唯一标识,同名 Cookie 可以在多个路径同时存储;命中多个时最长路径优先发送。排序逻辑在 cookiesForRequest 中,先按 path 长度降序,再按写入序号稳定排序(client/cookiejar.go#L238-L251)。

  • 容量上限与驱逐策略:常量定义见 client/cookiejar.go#L20-L50——

    • 最多 1024 个存储键(maxCookieJarHosts);
    • 每个键下最多 64 个 Cookie(maxCookiesPerHost);
    • 单个请求最多携带 64 个 Cookie(maxCookiesPerRequest),按“最具体优先”截断,防止主机通过在父域标签上分散 Cookie 来膨胀 Cookie 头;
    • host-only Cookie 与带 Domain= 的 Cookie 存储在不同的键下,因此一个主机可占用多个键;
    • 键满时先淘汰已过期条目,再淘汰“最久未写入”的条目(enforceHostCookieLimitLockedclient/cookiejar.go#L623-L657)。因此服务器每次响应都重新下发的会话 Cookie 不会因一次性 Cookie 的洪流而被挤出。
  • 重定向下的 Cookie 行为:Cookie 在每个请求发出前(跟随任何重定向之前)一次性附加,因此重定向链携带的是为原始 URL 选中的 Cookie。跳到无关主机时丢弃,跳到子域时保留——与 net/http 允许 foo.com 的 Cookie 发到 sub.foo.com 的行为一致;这一点比 Jar 自身规则更宽:不带 Domain= 的 Cookie 是 host-only,Jar 本来不会把它发给 sub.foo.com,但重定向过去时会带上。这种差异只在“跟随重定向”时才出现,而跟随重定向并非默认行为:保持 MaxRedirects 不设置,或通过 Request.SetMaxRedirects / Config{MaxRedirects: 0} 设为 0,然后逐跳手动发起请求,让 Jar 对每一跳做判断。

    源码印证:核心执行管线只在 maxRedirects > 0 且方法为 GET / HEAD / QUERY 时才走 DoRedirects,否则直接 Doclient/core.go#L96-L101);Config.MaxRedirects 为 0 时不会调用 SetMaxRedirectsclient/client.go#L884-L886)。重定向循环本身的默认上限是 16(defaultRedirectLimit),超出返回 fasthttp.ErrTooManyRedirects;HTTPS 到 HTTP 的降级跳转会被拒绝并返回 ErrRedirectDowngrade;当目标离开初始来源(子域除外)时,Authorization 等敏感头会被移除,见 client/transport.go#L313-L399

4.2 请求示例:主动塞入 Cookie

func main() {
    jar := client.AcquireCookieJar()
    defer client.ReleaseCookieJar(jar)

    cc := client.New()
    cc.SetCookieJar(jar)

    jar.SetKeyValueBytes("httpbin.org", []byte("john"), []byte("doe"))

    resp, err := cc.Get("https://httpbin.org/cookies")
    if err != nil {
        panic(err)
    }

    fmt.Println(string(resp.Body()))
}

预期返回:

{
  "cookies": {
    "john": "doe"
  }
}

其中 SetKeyValueBytes 使用字节切片写入以避免重复字符串分配,内部经 SetByHost 按主机存储(client/cookiejar.go#L411-L433)。

4.3 响应示例:直接读取 Jar 中的 Cookie

func main() {
    jar := client.AcquireCookieJar()
    defer client.ReleaseCookieJar(jar)

    cc := client.New()
    cc.SetCookieJar(jar)

    _, err := cc.Get("https://httpbin.org/cookies/set/john/doe")
    if err != nil {
        panic(err)
    }

    uri := fasthttp.AcquireURI()
    defer fasthttp.ReleaseURI(uri)

    uri.SetHost("httpbin.org")
    uri.SetPath("/cookies")
    fmt.Println(jar.Get(uri))
}

预期输出:

[john=doe; path=/]

jar.Get(uri) 按 host、path 与 scheme(https 才允许 Secure Cookie)过滤并返回副本,因此调用方用完后可以安全释放返回的 Cookie(client/cookiejar.go#L126-L138)。注意 httpbin 在 /cookies/set/john/doe 上未带 Path 属性,按 5.1.4 规则其默认路径为 /,所以这里输出 path=/

4.4 响应示例:后续请求自动携带

func main() {
    jar := client.AcquireCookieJar()
    defer client.ReleaseCookieJar(jar)

    cc := client.New()
    cc.SetCookieJar(jar)

    _, err := cc.Get("https://httpbin.org/cookies/set/john/doe")
    if err != nil {
        panic(err)
    }

    resp, err := cc.Get("https://httpbin.org/cookies")
    if err != nil {
        panic(err)
    }

    fmt.Println(resp.String())
}

预期返回:

{
  "cookies": {
    "john": "doe"
  }
}

实现上,响应阶段的 Cookie 解析在 parseCookiesFromResp 中完成(client/cookiejar.go#L521-L613),请求阶段的注入在 dumpCookiesToReq 中完成——它按最长路径优先取前 64 个、同名去重后写入请求头(client/cookiejar.go#L435-L471)。

五、小结

  • client.New() 之外,NewWithHostClient / NewWithLBClient 让你把 Fiber 客户端无缝接入已有的 fasthttp 连接池与负载均衡体系;
  • TLS 场景下 SetTLSConfig 是全传输层通用的入口,SetRootCertificate 系列方法提供更轻量的追加证书方式;
  • Cookie Jar 严格实现 RFC 6265 的路径作用域、三元组去重、容量上限与 LRU 驱逐,配合“默认不跟随重定向”的策略,使每次跳转的 Cookie 边界都可预期;
  • 所有示例代码均可对照 client/ 目录下的实现与测试(如 client/cookiejar_test.goclient/transport_test.go)进一步验证行为细节。
登录后查看全文
热门项目推荐
相关项目推荐