请求选项
请求选项是函数式配置项,通过 RequestOption 类型传递给请求方法,实现细粒度的请求控制。
result, err := client.Post(url,
httpc.WithJSON(data),
httpc.WithBearerToken(token),
httpc.WithQuery("page", 1),
)所有选项可自由组合,按传入顺序依次应用。
选项总览
| 分组 | 选项 |
|---|---|
| 请求头 | WithHeader、WithHeaderMap、WithUserAgent |
| 认证 | WithBasicAuth、WithBearerToken |
| 请求体 | WithJSON、WithXML、WithForm、WithFormData、WithFile、WithBinary、WithBody |
| 查询参数 | WithQuery、WithQueryMap |
| Cookie | WithCookies、WithCookieMap、WithCookieString、WithSecureCookie |
| 请求控制 | WithContext、WithTimeout、WithNoTimeout、WithMaxRetries、WithRetryNonIdempotent、WithFollowRedirects、WithMaxRedirects、WithAllowPrivateIPs、WithStreamBody |
| 回调 | WithOnRequest、WithOnResponse |
部分选项可覆盖客户端配置(Config)中的同名默认值:
| 选项 | 覆盖的 Config 字段(默认值) |
|---|---|
WithUserAgent | Defaults.UserAgent("httpc/1.0") |
WithHeader / WithHeaderMap | Defaults.Headers 中的同名默认头 |
WithTimeout | Timeouts.Request(180s) |
WithNoTimeout | Timeouts.Request(解除该默认上限) |
WithMaxRetries | Retry.MaxRetries(3) |
WithRetryNonIdempotent | Retry.RetryNonIdempotent(false) |
WithFollowRedirects | Defaults.FollowRedirects(true) |
WithMaxRedirects | Defaults.MaxRedirects(10) |
WithAllowPrivateIPs | Security.AllowPrivateIPs(false) |
TIP
nil 选项被静默跳过,不会报错——便于条件拼装选项切片。任一选项返回错误都会中止请求(统一包装为 failed to apply request option)。
请求头
WithHeader
func WithHeader(key, value string) RequestOption设置单个请求头。键和值经过安全验证(CRLF 注入防护):键 ≤256 字符、值 ≤8192 字符,控制字符被拒绝(返回 ErrInvalidHeader 包装错误)。同名默认头(Config.Defaults.Headers)会被覆盖。
result, err := client.Get(url,
httpc.WithHeader("X-Custom", "value"),
)WithHeaderMap
func WithHeaderMap(headers map[string]string) RequestOption批量设置请求头。逐项执行与 WithHeader 相同的校验,任一键值非法即报错(错误信息含出错的键名)。
result, err := client.Get(url,
httpc.WithHeaderMap(map[string]string{
"Accept": "application/json",
"X-Request-ID": "abc123",
}),
)WithUserAgent
func WithUserAgent(userAgent string) RequestOption设置 User-Agent 头。是 WithHeader("User-Agent", ...) 的便捷包装,覆盖 Config.Defaults.UserAgent 默认值("httpc/1.0")。
认证
WithBasicAuth
func WithBasicAuth(username, password string) RequestOption设置 HTTP Basic 认证,生成 Authorization: Basic <base64> 头。用户名与密码均不能为空、≤255 字符且不含控制字符;用户名还不得包含冒号 :。
result, err := client.Get(url,
httpc.WithBasicAuth("admin", "password"),
)WithBearerToken
func WithBearerToken(token string) RequestOption设置 Authorization: Bearer <token> 头。Token 不能为空、≤2048 字符、不含空格与控制字符(遵循 RFC 6750 格式约束)。
result, err := client.Get(url,
httpc.WithBearerToken("eyJhbGciOiJIUzI1NiIs..."),
)请求体
WithJSON
func WithJSON(data any) RequestOption设置 JSON 请求体,自动添加 Content-Type: application/json。data 为 nil 时报错;等价于 WithBody(data, BodyJSON)。
result, err := client.Post(url,
httpc.WithJSON(map[string]any{
"name": "test",
"email": "[email protected]",
}),
)WithXML
func WithXML(data any) RequestOption设置 XML 请求体,自动添加 Content-Type: application/xml。data 为 nil 时报错;等价于 WithBody(data, BodyXML)。
WithForm
func WithForm(data map[string]string) RequestOption设置 URL 编码表单请求体,自动添加 Content-Type: application/x-www-form-urlencoded。每个字段的键(≤256 字符)与值(≤8192 字符)都经过控制字符与长度校验;编码时键按字母序输出,同一输入产生确定性的请求体(利于测试与请求签名)。
仅接受 map[string]string;需要提交 url.Values 时改用 WithBody(data, BodyForm)(两者的校验与编码路径完全一致)。
result, err := client.Post(url,
httpc.WithForm(map[string]string{
"username": "admin",
"password": "secret",
}),
)WithFormData
func WithFormData(data *FormData) RequestOption设置 multipart/form-data 请求体,支持文件和字段混合上传。data 为 nil 时报错;等价于 WithBody(data, BodyMultipart)。
result, err := client.Post(url,
httpc.WithFormData(&httpc.FormData{
Fields: map[string]string{"description": "upload"},
Files: map[string]*httpc.FileData{
"file": {Filename: "doc.pdf", Content: fileBytes},
},
}),
)WithFile
func WithFile(fieldName, filename string, content []byte) RequestOption便捷的单文件 multipart 上传。fieldName 与 filename 不能为空、≤256 字符,且不得包含路径分隔符(/、\)、..、引号、尖括号等危险字符;实际使用的文件名取 filepath.Base(filename)(剥离路径部分,防路径遍历),结果为 "."/".."/空时报错。
与其他请求体选项互斥
WithFile 会替换整个请求体:多次调用 WithFile、或再搭配 WithFormData/WithJSON 等请求体选项时,后应用的选项覆盖前者。需要多文件或「文件 + 字段」混合上传时,请构建单个 *FormData 并使用 WithFormData。
result, err := client.Post(url,
httpc.WithFile("upload", "report.csv", csvBytes),
)WithBinary
func WithBinary(data []byte, contentType ...string) RequestOption设置二进制请求体。data 为 nil 时报错(非 nil 的空切片允许通过,但会发送空请求体)。默认 Content-Type 为 application/octet-stream,可通过第二个参数自定义(传空字符串回退默认值)。与 WithBody(data, BodyBinary) 的区别:后者还接受 string 输入,且会拒绝空数据。
result, err := client.Post(url,
httpc.WithBinary(imageBytes, "image/png"),
)WithBody
func WithBody(data any, kind ...BodyKind) RequestOption通用请求体设置,支持自动检测和显式指定类型。
自动检测规则(默认 BodyAuto):
| 输入类型 | Content-Type |
|---|---|
string | text/plain; charset=utf-8 |
[]byte | application/octet-stream |
map[string]string | application/x-www-form-urlencoded |
*FormData | multipart/form-data |
io.Reader | 不设置(由调用方处理) |
| 其他类型 | application/json |
显式指定类型:
// 自动检测(默认)
result, _ := client.Post(url, httpc.WithBody(data))
// 强制 JSON
result, _ := client.Post(url, httpc.WithBody(data, httpc.BodyJSON))
// 强制 XML
result, _ := client.Post(url, httpc.WithBody(data, httpc.BodyXML))| 常量 | 含义 |
|---|---|
BodyAuto | 自动检测(默认) |
BodyJSON | 强制 JSON |
BodyXML | 强制 XML |
BodyForm | 强制表单 |
BodyBinary | 强制二进制 |
BodyMultipart | 强制 multipart(需要 *FormData) |
显式类型的输入约束:
| 类型 | 输入要求 |
|---|---|
BodyJSON / BodyXML | 任意非 nil 数据 |
BodyForm | map[string]string 或 url.Values(逐字段校验) |
BodyBinary | []byte 或 string,且非空 |
BodyMultipart | *FormData |
类型不匹配(如 BodyMultipart 传入非 *FormData)或 data 为 nil 时报错。
io.Reader 不参与大小校验
io.Reader 请求体绕过请求体大小校验。读取不可信来源时,请用 io.LimitReader 包裹以防资源耗尽:
limited := io.LimitReader(untrustedReader, 10<<20) // 上限 10 MB查询参数
WithQuery
func WithQuery(key string, value any) RequestOption设置单个查询参数。key 不能为空、≤256 字符,且不含控制字符与保留字符(&、=、#、?);格式化后的值 ≤8192 字符。value 为 nil 时该参数被整体跳过(不会渲染成字面量 <nil>)。支持 string/bool/整数/浮点等常用类型,其余类型走通用格式化路径(如实现了 fmt.Stringer)。
result, err := client.Get(url,
httpc.WithQuery("page", 1),
httpc.WithQuery("limit", 10),
)WithQueryMap
func WithQueryMap(params map[string]any) RequestOption批量设置查询参数。校验规则与 WithQuery 一致;nil 值条目被跳过;nil map 不产生任何参数。
result, err := client.Get(url,
httpc.WithQueryMap(map[string]any{
"page": 1,
"limit": 10,
"sort": "created_at",
}),
)Cookie
WithCookies
func WithCookies(cookies []http.Cookie) RequestOption添加一个或多个 Cookie,每个 Cookie 都经过安全验证:名称 ≤256 字符、值 ≤4096 字符,控制字符与 Cookie 非法字符被拒绝;设置 Domain(≤255)或 Path(≤1024)时也会校验。整批在单次遍历中预分配容量并完成验证。
// 单个 Cookie:传单元素切片
result, err := client.Get(url,
httpc.WithCookies([]http.Cookie{{Name: "session", Value: "abc123"}}),
)
// 多个 Cookie
cookies := []http.Cookie{
{Name: "session_id", Value: "abc123"},
{Name: "user_pref", Value: "dark_mode"},
{Name: "lang", Value: "en"},
}
result, err = client.Get("https://api.example.com",
httpc.WithCookies(cookies),
)WithCookieMap
func WithCookieMap(cookies map[string]string) RequestOption批量添加简单 Cookie。适合仅需 name-value 的场景。
result, err := client.Get(url,
httpc.WithCookieMap(map[string]string{
"session_id": "abc123",
"lang": "zh",
}),
)WithCookieString
func WithCookieString(cookieString string) RequestOption从原始 Cookie 头字符串添加 Cookie。空字符串为空操作;缺少 = 分隔符或 = 前名称为空时报错;解析出的每个 Cookie 再经过与 WithCookies 相同的校验。
result, err := client.Get(url,
httpc.WithCookieString("session=abc123; lang=zh"),
)WithSecureCookie
func WithSecureCookie(securityConfig *CookieSecurityConfig) RequestOption强制验证请求 Cookie 的安全属性(Secure、HttpOnly、SameSite)。
选项顺序
此选项仅校验应用时已存在的 Cookie。必须将 WithSecureCookie 放在所有 WithCookies/WithCookieMap/WithCookieString 之后,否则后添加的 Cookie 不会被校验。如需不受顺序限制的会话级 Cookie 安全校验,请使用 SessionManager.SetCookieSecurity。
// 正确顺序:先添加 Cookie,再校验
result, err := client.Get(url,
httpc.WithCookies(sessionCookies),
httpc.WithCookieMap(otherCookies),
httpc.WithSecureCookie(httpc.StrictCookieSecurityConfig()),
)请求控制
WithContext
func WithContext(ctx context.Context) RequestOption设置请求上下文,支持超时和取消。上下文不能为 nil;上下文的截止时间会覆盖该请求的客户端默认超时(Timeouts.Request)。
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
result, err := client.Get(url, httpc.WithContext(ctx))WithTimeout
func WithTimeout(timeout time.Duration) RequestOption设置单次请求超时,覆盖 Config.Timeouts.Request(默认 180s,覆盖含重试在内的整段请求时长)。范围:0 到 30 分钟,越界返回 ErrInvalidTimeout。
result, err := client.Get(url, httpc.WithTimeout(5*time.Second))WithNoTimeout
func WithNoTimeout() RequestOption为单次请求解除客户端级默认整体超时(Config.Timeouts.Request,默认 180s),让慢调用可以运行到服务端处理完毕为止。仅解除客户端默认,两项限制始终生效:同一请求上显式设置的 WithTimeout 优先级更高,调用方上下文(WithContext 或 ctx 参数)预设的截止时间也始终被尊重。拨号/TLS/响应头等分段超时不受影响。
// 解除默认 180s 上限,用于大文件上传、慢报表等长调用
result, err := client.Post(uploadURL,
httpc.WithFile("file", "backup.tar.gz", data),
httpc.WithNoTimeout(),
)WithMaxRetries
func WithMaxRetries(maxRetries int) RequestOption设置单次请求最大重试次数,覆盖 Config.Retry.MaxRetries(默认 3)。范围:0-10,越界返回 ErrInvalidRetry。
result, err := client.Get(url, httpc.WithMaxRetries(3))WithRetryNonIdempotent
func WithRetryNonIdempotent(allow bool) RequestOption为单次请求覆盖非幂等方法(POST、PATCH 及自定义方法)是否允许重试,双向覆盖客户端级 Config.Retry.RetryNonIdempotent(默认 false):true 可在默认禁用重试的客户端上重试 POST;false 可在默认允许的客户端上将本次请求固定为单次执行。幂等方法(GET/HEAD 等)不受影响,WithMaxRetries 的次数上限照常适用。
副作用风险
对非幂等请求启用重试,意味着超时或可重试 5xx 时请求体可能被发送不止一次(如重复下单、重复支付)。仅对可容忍重放的端点启用,或以幂等键(Idempotency-Key)保护。
// 客户端默认不重试 POST;本次调用显式允许(端点已用幂等键防重放)
result, err := client.Post(orderURL,
httpc.WithJSON(order),
httpc.WithMaxRetries(3),
httpc.WithRetryNonIdempotent(true),
)WithFollowRedirects
func WithFollowRedirects(follow bool) RequestOption控制是否跟随重定向,覆盖 Config.Defaults.FollowRedirects(默认 true)。
// 禁止跟随重定向
result, err := client.Get(url, httpc.WithFollowRedirects(false))WithMaxRedirects
func WithMaxRedirects(maxRedirects int) RequestOption设置单次请求最大重定向次数,覆盖 Config.Defaults.MaxRedirects(默认 10)。范围:0-50,负数或超过 50 报错。
0 值语义
0 不会禁用重定向。引擎将 0 视为「未显式设置」的哨兵值,回退到默认上限(10),因此 WithMaxRedirects(0) 等价于省略该选项。要完全禁用重定向跟随,请改用 WithFollowRedirects(false)。
WithAllowPrivateIPs
func WithAllowPrivateIPs(allow bool) RequestOption为单次请求覆盖客户端的 SSRF 策略(对应 Config.Security.AllowPrivateIPs,默认 false)。当 allow 为 true 时,该请求可访问 localhost 和私有/保留 IP 段(127.0.0.0/8、10.0.0.0/8、192.168.0.0/16、169.254.0.0/16 等),并可跟随到此类地址的重定向;为 false 时,即使客户端配置了 Security.AllowPrivateIPs=true,本次请求仍强制启用 SSRF 防护。
安全提示
这是 SSRF 防护的单次请求逃生舱,适用于默认安全的客户端(AllowPrivateIPs=false)偶尔需要访问内部服务、回环地址或本地开发服务器的场景。
仅在请求 URL 可信且非来自不受信任的用户输入时启用。若需整客户端访问内部服务,应直接在 Config 上设置 Security.AllowPrivateIPs=true。
// 默认客户端阻止私有 IP;本次调用逐请求放行
result, err := httpc.Get("http://localhost:8080/health",
httpc.WithAllowPrivateIPs(true),
)WithStreamBody
func WithStreamBody(stream bool) RequestOption启用流式模式,响应体不缓存到内存。
重要限制
流式模式仅通过 Download 生效。与标准请求方法(Get/Post/Put/Patch/Delete/Head/Options/Request)配合使用时,响应体会被完整读入并转换为 Result,随后底层流被关闭——返回的 Result 响应体为空,调用方无法消费该流。
要真正流式下载大文件而不缓存到内存,请使用 Download。
回调
WithOnRequest
func WithOnRequest(callback func(req RequestMutator) error) RequestOption注册请求发送前的回调。可链式注册多个,按添加顺序执行。回调返回错误会中止请求;callback 为 nil 时报错。
result, err := client.Get(url,
httpc.WithOnRequest(func(req httpc.RequestMutator) error {
log.Printf("发送 %s %s", req.Method(), req.URL())
return nil
}),
)WithOnResponse
func WithOnResponse(callback func(resp ResponseMutator) error) RequestOption注册响应接收后的回调。可链式注册多个,按添加顺序执行;任一回调返回错误会使请求以该错误失败;callback 为 nil 时报错。
result, err := client.Get(url,
httpc.WithOnResponse(func(resp httpc.ResponseMutator) error {
log.Printf("收到响应: %d %s", resp.StatusCode(), resp.Status())
return nil
}),
)