登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go cookiejar 怎样让测试客户端保持会话 Cookie

来源:17golang原创

时间:2026-11-15 23:43:13 193浏览 收藏

给测试用的 http.Client 设置非空 Jar,再让同一用户的请求复用这个客户端,就能自动接收响应里的 Cookie,并在匹配的后续请求中携带。我的选择是把“客户端与独立 Jar”作为一个测试用户的整体,而不是在每次请求中手工拼 Cookie。

官方文档:https://pkg.go.dev/net/http/cookiejar;客户端接口:https://pkg.go.dev/net/http#Client。

先把 Cookie 状态绑定到客户端

这里需要的能力是状态容器,不是浏览器窗口。Jar 保存 Cookie;http.Client 将它绑定到请求和响应。Jar 为空时,Client 不会自动记住服务器下发的 Cookie,只发送请求上显式设置的值。直接调用包级 http.Get 也不会自动使用你另建的客户端。

本例的六个对象分属三个边界:测试用户包含 Client、Jar;HTTP 数据包含 Set-Cookie、Cookie;测试服务包含 /session、/profile。接口负责串起它们,连接复用与会话存储则是两回事。

客户端Jar与会话Cookie的静态关系
结构图:测试用户持有客户端和Jar;两类HTTP头与服务接口对应,连线表示静态关联。

用一个完整测试表达会话约定

将下面代码保存为已有 Go 模块中的 session_test.go,并将 golang.org/x/net/publicsuffix 加入模块依赖;依赖版本应与项目工具链要求相容。它属于 golang.org/x/net 模块,不是标准库包。此处给出可复用的测试写法,不附本机运行记录。

package session_test

import (
    "io"
    "net/http"
    "net/http/cookiejar"
    "net/http/httptest"
    "testing"
    "time"

    "golang.org/x/net/publicsuffix"
)

func TestClientKeepsSession(t *testing.T) {
    mux := http.NewServeMux()
    mux.HandleFunc("/session", func(w http.ResponseWriter, r *http.Request) {
        // 只提供测试会话标记,不代表真实认证或密码校验。
        if r.Method != http.MethodPost {
            w.WriteHeader(http.StatusMethodNotAllowed)
            return
        }
        http.SetCookie(w, &http.Cookie{
            Name: "session", Value: "test-user",
            Path: "/", HttpOnly: true,
        })
        w.WriteHeader(http.StatusNoContent)
    })
    mux.HandleFunc("/profile", func(w http.ResponseWriter, r *http.Request) {
        // 后续请求必须携带预期标记,才能通过测试约定。
        c, err := r.Cookie("session")
        if err != nil || c.Value != "test-user" {
            w.WriteHeader(http.StatusUnauthorized)
            return
        }
        w.WriteHeader(http.StatusNoContent)
    })
    server := httptest.NewServer(mux)
    t.Cleanup(server.Close)

    jar, err := cookiejar.New(&cookiejar.Options{
        PublicSuffixList: publicsuffix.List,
    })
    if err != nil {
        t.Fatal(err)
    }
    client := &http.Client{Jar: jar, Timeout: 3 * time.Second}
    t.Cleanup(client.CloseIdleConnections)

    // 读取并关闭每个响应体;不要把 defer 堆积在请求循环里。
    requestStatus := func(method, path string) int {
        t.Helper()
        req, err := http.NewRequest(method, server.URL+path, nil)
        if err != nil {
            t.Fatal(err)
        }
        resp, err := client.Do(req)
        if err != nil {
            t.Fatal(err)
        }
        _, readErr := io.Copy(io.Discard, resp.Body)
        closeErr := resp.Body.Close()
        if readErr != nil {
            t.Fatal(readErr)
        }
        if closeErr != nil {
            t.Fatal(closeErr)
        }
        return resp.StatusCode
    }
    if got := requestStatus(http.MethodGet, "/profile"); got != http.StatusUnauthorized {
        t.Fatalf("会话建立前状态码=%d", got)
    }
    if got := requestStatus(http.MethodPost, "/session"); got != http.StatusNoContent {
        t.Fatalf("设置会话状态码=%d", got)
    }
    if got := requestStatus(http.MethodGet, "/profile"); got != http.StatusNoContent {
        t.Fatalf("保持会话后状态码=%d", got)
    }
}

测试约定是:没有会话时访问资料接口得到401;POST建立标记得到204;再次访问资料接口得到204。这里刻意不手工复制响应 Cookie,才能让最后一个断言覆盖 Jar 与 Client 的绑定。若每次请求都新建 Jar,最后的请求没有此前的状态。

测试服务使用普通 HTTP,因此示例没有设置 Secure;这只是隔离测试的传输选择。它的固定值也只用于说明客户端保存能力,不能拿来充当实际用户凭据。

公共后缀列表决定域名边界

cookiejar.New(nil) 可以创建 Jar,但没有公共后缀保护。需要接触真实域名的客户端,我会显式传入 publicsuffix.List。官方包地址:https://pkg.go.dev/golang.org/x/net/publicsuffix。

公共后缀不等于简单取最后一个域名片段:例如 co.uk 本身就是公共后缀。列表帮助 Jar 约束服务器能设置的域范围;它不负责登录、不替应用授权,也不自动让任意子域共享会话。该包提供编译进依赖版本的数据快照,更新依赖才会更新快照。

从配置角度看,三个边界是“创建配置”(Options、PublicSuffixList)、“规则依赖”(publicsuffix.List、公共后缀数据)、“状态容器”(Jar、Domain)。Options 关联 PublicSuffixList 和 Jar,PublicSuffixList 关联实现,实现关联数据;Jar 对 Domain 的处理受这些规则约束。

公共后缀配置与Jar域名策略依赖
结构图:公共后缀列表是Jar的域名策略依赖,不是用户认证或域名共享开关。

保持会话时容易忽略的限制

现象优先检查调整方向
设置接口成功,后续仍是401是否复用了同一Jar让同一测试用户复用带Jar的Client
换路径就没有CookieCookie的Path范围按接口实际范围设置;示例明确使用/
换主机名后丢失会话实际URL主机与Domain不要混用localhost和127.0.0.1
不同用户相互影响是否共享了Jar每个用户分别创建Jar
HTTP请求不带Secure CookieURL协议需要该属性的测试使用HTTPS服务

共享连接池不等于共享用户身份;Cookie 也不按端口隔离,因此给两个测试服务分配不同端口,不足以保证共享 Jar 时的用户隔离。对于重定向,Client 会为它跟随的每个目标咨询 Jar;不能把一次登录理解为对所有目标都发送同一份头。

这套内存 Jar 不会自动保存到磁盘,也不是完整浏览器环境。若需求是浏览器脚本、跨站上下文或页面策略行为,应在对应浏览器环境中另外测试。本文只覆盖 HTTP 客户端会话 Cookie。

常见问题

只调用 Request.AddCookie 可以吗?

可以给单次请求添加值,但服务器更新 Cookie 后,手工方式还要自行同步。要表达连续会话,我更愿意把状态交给 Jar。

HttpOnly 会阻止Go客户端发送Cookie吗?

不会。它主要限制浏览器脚本访问,不能用来解释这里的请求为何缺少Cookie。

同一个客户端适合多个测试用户吗?

同一Jar意味着共享会话容器。需要独立身份时,创建各自的Client与Jar;可以根据测试设计另行考虑底层Transport的复用。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>