Documentation
¶
Overview ¶
Package httpx 提供 JSON HTTP 客户端。
所有 enjoydream 后端服务共享此包,统一 HTTP 调用方式。
核心特性 ¶
- JSON 请求/响应自动序列化
- Prometheus 指标采集
- 熔断器(gobreaker)
- 指数退避重试
- OpenTelemetry 追踪
- 文件上传(multipart/form-data)
- DoDetail:status + 原始 body(2xx 也返回)
- net/http 桥接层:NewRoundTripper/StdClient——存量标准库调用方一行 Transport 即接入熔断/重试/指标/链路(重试缺省关、显式开)
- WithDialFunc/WithHertzOption:拨号层拦截与 Hertz 原生选项透传(SSRF 防护)
- WithStream:流式响应预设(关读超时、保留拨号超时)
- DoOnce:client 可注入的低阶一次性请求原语(构造→发出→读尽→返回 status/body;无重试/熔断,弹性语义单点在引擎层)
快速接入 ¶
创建客户端(构造失败显式返回 error,与 gormx.Open 等家族构造器一致, #83):
client, err := ehttp.NewClient("user-service",
ehttp.WithBaseURL("http://user-service:8080"),
ehttp.WithTimeout(5*time.Second),
ehttp.WithTracePropagation(),
)
if err != nil {
return err
}
构造参数静态合法时可用 MustNewClient 一行接入。
GET 请求:
var users []User err = client.GetJSON(ctx, "/api/users", &users)
POST 请求:
var result CreateUserResp err := client.PostJSON(ctx, "/api/users", req, &result)
需要状态码与原始响应体时用 DoDetail(2xx 也返回 body;非 2xx 额外返回 *ServiceError):
status, body, err := client.DoDetail(ctx, http.MethodPost, "/api/users", req)
流式响应与超时语义 ¶
默认配置注入 30s 拨号/读超时——**只适合短请求**。读超时是「响应必须 在 30s 内读完」的硬顶,对流式响应(SSE 转发、k8s proxy 长连接、WS 升级、chunked 大响应)会静默掐断长流(症状是连接被远端/代理切断, 极难归因到客户端读超时)。流式场景必须显式关读超时:
client, err := ehttp.NewClient("k8s-proxy",
ehttp.WithStream(), // 关读超时 + 保留拨号超时(#87)
)
超时语义族:WithTimeout(d) 同时覆盖拨号与读取(各以 d 为独立上限, 非全生命周期总预算);仅调读/拨号单项经 WithHertzOption (client.WithClientReadTimeout / WithDialTimeout);写超时经 WithHertzOption(client.WithWriteTimeout)。流式的总时长预算由调用方经 ctx 控制。
出站 SSRF 防护 ¶
调用方 URL 来自不受信输入时做两层检查:URL 层字面量拒绝内网 IP/主机名; 拨号层用 WithDialFunc 在建连前校验解析后的 IP(防 DNS rebinding)。 详见 README「出站 SSRF 防护套路」。
监控集成 ¶
使用 WithMetrics 启用 Prometheus 指标:
client, err := ehttp.NewClient("user-service",
ehttp.WithMetrics(metrics.Registry, "myservice"),
)
指标包括(namespace 未显式指定时前缀为 http_client):
- {ns}_requests_total{service, target, method, status_code}
- {ns}_request_duration_seconds{service, target, method}
- {ns}_requests_in_flight{service, target, method}
同一 Registry + namespace 下创建多个 Client 是安全的:同名指标自动复用 已注册的 collector。禁止 host/IP/port/path 等高基数 label。
熔断器 ¶
使用 WithCircuitBreaker 启用熔断保护:
client := ehttp.NewClient("user-service",
ehttp.WithCircuitBreaker("user-service", gobreaker.Settings{
MaxRequests: 3,
Interval: 10 * time.Second,
Timeout: 30 * time.Second,
}),
)
熔断器状态变化会自动记录到日志。
错误处理 ¶
返回 ServiceError 类型,包含服务名、操作、状态码和响应体:
var svcErr *ehttp.ServiceError
if errors.As(err, &svcErr) {
log.Printf("service: %s, action: %s, code: %d, message: %s",
svcErr.Service, svcErr.Action, svcErr.Code, svcErr.Message)
}
Index ¶
- Constants
- Variables
- func DoOnce(ctx context.Context, hc *http.Client, req *http.Request) (status int, body []byte, err error)
- func NewRoundTripper(service string, opts ...ClientOption) http.RoundTripper
- func NewRoundTripperE(service string, opts ...ClientOption) (http.RoundTripper, error)
- func NodesFromEndpoints(endpoints []string) []loadbalance.Node
- func SafeDialFunc(next network.DialFunc) network.DialFunc
- func SafeDialFuncWith(opts []pkgurlguard.Option, next network.DialFunc) network.DialFunc
- func SelectorNodes(insts []registry.Instance) []loadbalance.Node
- func StdClient(service string, opts ...ClientOption) *http.Client
- func WithPriority(ctx context.Context, priority Priority) context.Context
- type Client
- func (c *Client) Close()
- func (c *Client) DoCodec(ctx context.Context, codecName, method, path string, body any, out any) error
- func (c *Client) DoDetail(ctx context.Context, method, path string, body any) (int, []byte, error)
- func (c *Client) DoJSON(ctx context.Context, method, path string, body any, out any) error
- func (c *Client) DoRaw(ctx context.Context, method, path string, body any) ([]byte, error)
- func (c *Client) GetJSON(ctx context.Context, path string, out any) error
- func (c *Client) Hertz() *client.Client
- func (c *Client) PostJSON(ctx context.Context, path string, body any, out any) error
- func (c *Client) SetTarget(target string)
- func (c *Client) UploadBytes(ctx context.Context, path string, data []byte, fieldName, fileName string, ...) error
- func (c *Client) UploadFile(ctx context.Context, path, filePath, fieldName string, ...) error
- type ClientOption
- func WithBaseURL(u string) ClientOption
- func WithBasicAuth(user, password string) ClientOption
- func WithBearerToken(token string) ClientOption
- func WithCircuitBreaker(name string, settings gobreaker.Settings) ClientOption
- func WithCookieJar(jar http.CookieJar) ClientOption
- func WithDebug() ClientOption
- func WithDialFunc(fn network.DialFunc, dialers ...network.Dialer) ClientOption
- func WithEndpoints(endpoints ...string) ClientOption
- func WithHertzOption(opts ...config.ClientOption) ClientOption
- func WithMetrics(registry prom.Registerer, namespace string) ClientOption
- func WithNNS(serviceName string, nnsClient NNSClient) ClientOption
- func WithOnBeforeRequest(fn func(ctx context.Context, req *protocol.Request) error) ClientOption
- func WithPriorityQueue(capacity int) ClientOption
- func WithRefreshInterval(d time.Duration) ClientOption
- func WithRetryConfig(maxAttempts uint, initDelay, maxDelay, maxJitter time.Duration) ClientOption
- func WithRetryIfFunc(fn func(req *protocol.Request, resp *protocol.Response, err error) bool) ClientOption
- func WithSafeDial(opts ...pkgurlguard.Option) ClientOption
- func WithSelector(sel loadbalance.Selector) ClientOption
- func WithServiceResolver(resolver ServiceResolver) ClientOption
- func WithStream() ClientOption
- func WithTLSSkipVerify() ClientOption
- func WithTarget(target string) ClientOption
- func WithTimeout(d time.Duration) ClientOption
- func WithTracePropagation() ClientOption
- type ConsistentHashOption
- type ConsistentHashSelector
- type EndpointManager
- func (m *EndpointManager) GetNodes() []loadbalance.Node
- func (m *EndpointManager) Pick(ctx context.Context, key string) (string, loadbalance.DoneFunc, error)
- func (m *EndpointManager) SetEndpoints(endpoints []string)
- func (m *EndpointManager) SetNodes(nodes []loadbalance.Node)
- func (m *EndpointManager) Stop()
- type InstanceResolver
- type NNSClient
- type NNSResolver
- type Priority
- type PriorityQueue
- type ServiceError
- type ServiceResolver
- type StaticResolver
Constants ¶
const MetadataKeyScheme = "scheme"
MetadataKeyScheme 是实例协议的元数据键(#90):注册中心实例经此声明 对外协议,取值 "http"/"https"(大小写不敏感);缺省或取值不认识时 回落 "http"(与历史 endpoint 形态一致)。修复前地址硬编码 "http://", https 服务无法经 WithNNS 接入(无论元数据如何都被以明文 http 拨号)。
Variables ¶
var ErrCircuitOpen = breaker.ErrCircuitOpen
ErrCircuitOpen 表示熔断器处于打开状态,请求被拒绝。
规范哨兵定义于 concurrency/breaker(错误消息逐字为 "circuit breaker is open"), httpx 此处仅别名它:gobreaker 熔断路径与 adaptive 熔断器(concurrency/breaker) 拒绝时返回同一 sentinel,errors.Is 全链统一判定。判定一律用 errors.Is。
Functions ¶
func DoOnce ¶ added in v0.8.12
func DoOnce(ctx context.Context, hc *http.Client, req *http.Request) (status int, body []byte, err error)
DoOnce 一次性低阶请求原语:发出请求 → 读尽响应体 → 返回 (status, body, err)(#91)。
定位:服务里「共享 *http.Client(自定义 Transport:SSRF 拨号/链路透传) + 轻量请求骨架」的形态此前没有库级原语,各服务自收敛一份,收敛点又因 client 不可注入被绕开(openclaw 实录:需要不挂 urlguard 的 loopback 客户端时整份 NewRequest/Do/ReadAll/状态码判定重新手搓)。
语义与边界:
- client 由调用方注入:hc 为 nil 时用 http.DefaultClient。挂不挂 SSRF 防护(Transport 包 urlguard)、什么连接池、什么超时,都是宿主决策—— 本原语不做任何拨号/传输假设;
- 不做重试/熔断/负载均衡:弹性语义单点在 NewClient 引擎层,本原语与 引擎层组合会造成复合重试;需要弹性时直接用 Client;
- 非 2xx 不是错误(标准库语义):调用方自行判定 status;body 仅在 err == nil 时有效(读 body 中途出错时 status 仍返回、body 为 nil);
- ctx 非 nil 时覆盖 req 自带的 context(req.WithContext);总时长 预算经 ctx 控制;
- 响应体总是读尽并关闭(body Close 后返回),适合 JSON/文本等可控 体量的响应;超大响应的流式消费不要经本原语缓冲。
func NewRoundTripper ¶ added in v0.6.0
func NewRoundTripper(service string, opts ...ClientOption) http.RoundTripper
NewRoundTripper 把 httpx Client 装进标准 http.RoundTripper(#8)。 service 与 opts 语义同 NewClient。
构造失败(含静态端点与动态 resolver 互斥等构造期冲突)直接 panic—— 桥接层为「一行 Transport 接入」保留无错误签名,panic 发生在初始化期、 携带完整原因,不是请求路径上的运行期 panic。配置来自外部输入、需要 显式判错的调用方用 NewRoundTripperE。
func NewRoundTripperE ¶ added in v0.8.12
func NewRoundTripperE(service string, opts ...ClientOption) (http.RoundTripper, error)
NewRoundTripperE 是 NewRoundTripper 的显式错误版本(#83):构造失败 返回 error 而非 panic。语义同 NewRoundTripper。
func NodesFromEndpoints ¶ added in v0.4.0
func NodesFromEndpoints(endpoints []string) []loadbalance.Node
NodesFromEndpoints 把字符串端点列表合成为等权节点(权重 1、无元数据)。
func SafeDialFunc ¶ added in v0.7.0
SafeDialFunc 包装 network.DialFunc:出站 SSRF 防护(缺省配置,全阻断)。 next 为 nil 时使用 standard 拨号器裸拨(30s 超时;#74:不用 DefaultDialer—— netpoll 构建下它是 netpoll 拨号器,返回的连接不支持 TLS 后续包装,https 目标会在补握手一步 fail-fast)。需要豁免(测试打本机 mock、内网 webhook 端点)用 SafeDialFuncWith / WithSafeDial 传构造态 option。
https 目标受支持:经 httpx.WithDialFunc/WithSafeDial 装配时拨号器是 TLS 感知的(见 tlsAwareDialer)——裸拨后按 tlsConfig 补握手。直接用 hertz 原生 client.WithDialFunc 装配本函数会踩 hertz customDialer 丢 tlsConfig 的坑 (明文打 443),应经本仓装配。
func SafeDialFuncWith ¶ added in v0.8.0
SafeDialFuncWith 同 SafeDialFunc,但接受 pkg/urlguard 的构造态 Option:
httpx.SafeDialFuncWith([]urlguard.Option{urlguard.WithAllowPrivate()}, next) // 测试打本机 mock
httpx.SafeDialFuncWith([]urlguard.Option{urlguard.WithAllowCIDRs("10.20.0.0/16")}, next) // 内网端点豁免
CIDR 文法非法时返回的拨号函数一律报错(fail-fast,不带病运行)。
func SelectorNodes ¶ added in v0.4.0
func SelectorNodes(insts []registry.Instance) []loadbalance.Node
SelectorNodes 把注册中心实例切片适配为 Selector 节点(字段闭包映射, 地址 scheme 从实例 Metadata 取(键 MetadataKeyScheme),缺省 "http")。
func StdClient ¶ added in v0.6.0
func StdClient(service string, opts ...ClientOption) *http.Client
StdClient 返回装好 Transport 的标准 http.Client(Timeout 留零—— 超时边界由 httpx 层管理:缺省 dial/read 30s,可经 WithTimeout/WithHertzOption 调整)。 构造失败语义同 NewRoundTripper(panic);需显式判错用 NewRoundTripperE。
Types ¶
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client 是封装了 Hertz HTTP 客户端的高层 HTTP 调用器,支持服务发现、负载均衡、熔断和指标。
func MustNewClient ¶ added in v0.8.12
func MustNewClient(service string, opts ...ClientOption) *Client
MustNewClient 是 NewClient 的 panic 版本:构造失败直接 panic(携带 NewClient 返回的完整错误信息)。仅用于构造参数静态合法的场景(硬编码 配置、初始化期组装);配置来自外部输入时一律用 NewClient 显式判错。
func NewClient ¶
func NewClient(service string, opts ...ClientOption) (*Client, error)
NewClient 创建 HTTP 客户端实例,应用指定的配置选项。
构造失败显式返回 error(#83):与家族同级构造器契约一致(gormx.Open、 core/config.LoadFile 均为 error 显式返回),出站 HTTP 客户端用在 webhook/工具调用等场景,构造失败不应升级为进程死亡。失败来源:
- WithHertzOption 逃生舱透传的非法 Hertz client 配置项;
- 构造期配置冲突(如 WithEndpoints 与 WithNNS/WithServiceResolver 互斥,#89)。
BREAKING(0.x minor 改签名,先例 gormx v0.9.0 参数序 breaking):签名从 `NewClient(...) *Client` 改为 `NewClient(...) (*Client, error)`。旧代码
c := httpx.NewClient("svc", ...)
迁移为
c, err := httpx.NewClient("svc", ...)
if err != nil {
return err
}
确认构造参数静态合法、不愿逐处判错的调用方用 MustNewClient。
func (*Client) Close ¶
func (c *Client) Close()
Close 关闭客户端,释放资源:停止 EndpointManager 刷新循环,并关闭底层 Hertz client 的空闲连接池(hostClient 连接与清理协程随之收敛,#84)。
说明:钉住的 hertz v0.10.6 *client.Client 没有 Close() error,可用的 释放口是 CloseIdleConnections()——关闭全部 hostClient 的空闲连接后, hertz 的连接清理协程按 ShouldRemove 逐个回收 hostClient 并退出。 Close 幂等,可安全重复调用。
func (*Client) DoCodec ¶ added in v0.4.0
func (c *Client) DoCodec(ctx context.Context, codecName, method, path string, body any, out any) error
DoCodec 使用指定编解码器发送请求:body 经 codec.Marshal 序列化并设置 对应 Content-Type;响应按其 Content-Type 反查 codec 反序列化到 out (查不到回退请求 codec)。
func (*Client) DoDetail ¶
DoDetail 使用指定的 HTTP 方法发送请求,返回状态码与原始响应体。 与 DoJSON/DoRaw 不同:2xx 也返回原始 body(DoJSON 只把 body 解到 out, DoRaw 仅在成功时返回 body),非 2xx 同时返回 status、body 与 *ServiceError,调用方可自行判定如何处理响应内容。 传输层失败时 status 为 0、body 为 nil。
func (*Client) UploadBytes ¶
func (c *Client) UploadBytes(ctx context.Context, path string, data []byte, fieldName, fileName string, extraFields map[string]string, out any) error
UploadBytes 上传字节数据 (multipart/form-data)。
func (*Client) UploadFile ¶
func (c *Client) UploadFile(ctx context.Context, path, filePath, fieldName string, extraFields map[string]string, out any) error
UploadFile 上传本地文件 (multipart/form-data):读取 filePath 全部内容 作为文件 part(filename 取 filepath.Base(filePath)——不带本地目录, 避免泄漏服务器目录结构),额外表单字段经 extraFields 透传(#86)。 内存中的字节用 UploadBytes。
type ClientOption ¶
type ClientOption func(*Client)
ClientOption 是 Client 的配置函数类型。
func WithBasicAuth ¶
func WithBasicAuth(user, password string) ClientOption
WithBasicAuth 为所有请求设置 Basic Auth 认证。
func WithBearerToken ¶
func WithBearerToken(token string) ClientOption
WithBearerToken 为所有请求设置 Bearer Token 认证。
func WithCircuitBreaker ¶
func WithCircuitBreaker(name string, settings gobreaker.Settings) ClientOption
WithCircuitBreaker 为客户端启用熔断器,使用指定的名称和配置。
func WithCookieJar ¶
func WithCookieJar(jar http.CookieJar) ClientOption
WithCookieJar 为客户端设置 Cookie Jar。 通过 base 中间件在请求前注入 Cookie、响应后回收 Set-Cookie, 对全部请求路径生效(含裸 Hertz().Do)。
func WithDebug ¶
func WithDebug() ClientOption
WithDebug 开启调试日志。 Hertz client 不支持运行时调试开关(日志由 hlog 全局控制),此选项留作占位。
func WithDialFunc ¶
func WithDialFunc(fn network.DialFunc, dialers ...network.Dialer) ClientOption
WithDialFunc 注入自定义拨号函数(#74)。 典型用途是出站 SSRF 防护的拨号层拦截:在真正建连前校验 addr 解析出的 IP 是否落在内网/黑名单(URL 层字面量检查挡不住 DNS rebinding)。 dialers 可选,用于覆盖基础拨号器(缺省 DefaultDialer())。
不再透传 Hertz 的 client.WithDialFunc:hertz 的 customDialer 在有 dialFunc 时 **完全丢弃 tlsConfig**(pkg/app/client/option.go 的 DialConnection 直接 `return m.dialFunc(address)`)——https 目标的 TLS 握手链路就此断裂, WithSafeDial 形态把明文请求静默打到 443 端口(症状是服务端 400,极难排障)。 本仓改为自行装配 TLS 感知拨号器 tlsAwareDialer:dialFunc 拨通裸连接后按 hertz 传入的 tlsConfig 是否为 nil 决定是否补 TLS 握手(见 ssrf.go 实现说明)。
装配时机是 NewClient 的**末位兜底**(不再入 hzOpts):hertz 的 client.WithTLSConfig 会无条件覆盖 o.Dialer——WithTLSSkipVerify 排在本选项 之后时自定义拨号函数会被静默丢弃(SSRF 防护一并失效)。末位 append 保证 WithDialFunc/WithSafeDial 的装配面赢。逃生舱 WithHertzOption 自带 client.WithDialer 的例外:本选项未使用时不装配、不覆盖逃生舱的拨号器。
func WithEndpoints ¶
func WithEndpoints(endpoints ...string) ClientOption
WithEndpoints 设置静态 endpoint 列表。
与动态服务发现(WithNNS/WithServiceResolver)互斥(#89):组合使用时 静态端点会被动态刷新循环在下一个刷新周期覆盖(或静态端点被静默丢弃), 行为随 option 顺序漂移且无告警——组合直接构造期报错(NewClient 返回 error)。一个 Client 只能有一种端点来源;需要静态兜底 + 动态发现的 组合语义由 EndpointManager 的缓存降级提供(resolver 失败时保留快照)。
func WithHertzOption ¶
func WithHertzOption(opts ...config.ClientOption) ClientOption
WithHertzOption 透传任意 Hertz client 配置项(逃生舱)。 优先使用语义化选项(WithTimeout/WithDialFunc/WithRetryConfig 等); 仅当某项 Hertz 能力尚未封装时使用,按传入顺序追加到 client.NewClient 的选项列表尾部。
func WithMetrics ¶
func WithMetrics(registry prom.Registerer, namespace string) ClientOption
WithMetrics 为客户端启用 Prometheus 指标采集。
func WithNNS ¶
func WithNNS(serviceName string, nnsClient NNSClient) ClientOption
WithNNS 启用 NNS 服务发现。
func WithOnBeforeRequest ¶
WithOnBeforeRequest 注册请求前置钩子。 ⚠️ Breaking: 签名从 func(*req.Client, *req.Request) 改为 func(ctx context.Context, req *protocol.Request)(hertz protocol 类型)。
func WithPriorityQueue ¶
func WithPriorityQueue(capacity int) ClientOption
WithPriorityQueue 启用请求优先级队列。 capacity 是每级准入容量,必须 >= 1——0/负数在构造期 panic 并指明原因 (0 会造成所有请求阻塞到 ctx 取消的死锁,见 NewPriorityQueue,#88); 配置值缺省时请显式传 1。
func WithRefreshInterval ¶
func WithRefreshInterval(d time.Duration) ClientOption
WithRefreshInterval 设置 endpoint 刷新间隔。
func WithRetryConfig ¶
func WithRetryConfig(maxAttempts uint, initDelay, maxDelay, maxJitter time.Duration) ClientOption
WithRetryConfig 配置请求重试策略,走 Hertz 官方 client.WithRetryConfig。 参数语义:
- maxAttempts:总尝试次数(>=1,含首次请求);0 = 走 Hertz 默认 1 次
- initDelay:首次重试前的等待时间(指数退避基数,对应 retry.Config.Delay)
- maxDelay:重试延迟上限(避免退避无界增长,retry.Delay 内部封顶)
- maxJitter:随机抖动上限(避免雪崩,对应 retry.Config.MaxJitter)
DelayPolicy 必须显式设置:hertz WithRetryConfig 的默认 DelayPolicy 是 retry.DefaultDelayPolicy(恒返回 0,参见 hertz pkg/app/client/retry/retry.go), 不设置的话 initDelay/maxDelay/maxJitter 三个参数全部无效、重试零等待。 ekit 固定注入 CombineDelay(BackOffDelayPolicy, RandomDelayPolicy): 每次重试延迟 = initDelay<<attempts + [0, maxJitter) 随机抖动,MaxDelay 封顶。
⚠️ 重试触发条件(与 Hertz 默认不同,带幂等方法门 #85):
Hertz 官方 DefaultRetryIf 仅在幂等方法 + 网络错误时重试,不重试 5xx。 ekit WithRetryConfig 默认重试「幂等方法/无 body 请求」上的 5xx + network error——比 Hertz 宽(补 5xx),比旧版「不看方法一律重试」严。 旧版默认对 POST 等**非幂等请求**的 5xx 无条件重放,网关恰好超时但 后端已实际写入时会重复下单/重复创建——「保持兼容」不等于「安全默认」。 需要放开旧语义(任何方法都重试)时,经 WithRetryIfFunc 显式声明。
官方依据:https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/client/ (Client 配置表 WithRetryConfig 示例明确列出 retry.WithDelayPolicy; 退避/抖动语义见 hertz pkg/app/client/retry 包:BackOffDelayPolicy = Delay<<attempts, RandomDelayPolicy = fastrand.Int63n(MaxJitter),CombineDelay 求和)
用法:
cli, err := httpx.NewClient("my-svc",
httpx.WithRetryConfig(3, 100*time.Millisecond, 1*time.Second, 50*time.Millisecond),
)
func WithRetryIfFunc ¶
func WithRetryIfFunc(fn func(req *protocol.Request, resp *protocol.Response, err error) bool) ClientOption
WithRetryIfFunc 覆盖 ekit 默认重试触发条件(幂等方法门 + 5xx + network error),由用户传自定义 predicate(更严格:只对特定方法/状态码; 更宽松:放开非幂等请求的重放)。 不调 WithRetryIfFunc 时,WithRetryConfig 注入默认条件。
**显式谓词永远赢,与 option 顺序无关**(#41):即便本 option 写在 WithRetryConfig 之前也不会被默认谓词覆盖 ⇒ 不会再出现「以为收窄了重试、 实际 POST 仍被重放」这种静默失效。
func WithSafeDial ¶ added in v0.7.0
func WithSafeDial(opts ...pkgurlguard.Option) ClientOption
WithSafeDial 一步启用出站 SSRF 防护,等价 WithDialFunc(SafeDialFuncWith(opts, nil))。 URL 层字面量校验请另调 pkg/urlguard 的 IsBlockedURL / Guard.CheckURL。 无参调用即缺省全阻断;豁免传 pkg/urlguard 的 Option(可变参,向后兼容无参形态)。
https 目标受支持(#74):WithDialFunc 装配的是 TLS 感知拨号器,拨通后按 tlsConfig 补握手——防护在 TLS 之前生效(先校验解析 IP 再建连),握手在受 拨号超时约束下完成。
func WithSelector ¶ added in v0.4.0
func WithSelector(sel loadbalance.Selector) ClientOption
WithSelector 设置节点选择器(roundrobin/random/weighted/一致性哈希等, 经 loadbalance.NewSelector 或 NewConsistentHashSelector 构造)。 v0.4.0 起替代 WithBalance。
func WithServiceResolver ¶
func WithServiceResolver(resolver ServiceResolver) ClientOption
WithServiceResolver 设置自定义服务发现解析器。
func WithStream ¶ added in v0.8.12
func WithStream() ClientOption
WithStream 流式响应预设(#87):关闭读超时(默认 30s 只适合短请求—— 读超时与流式语义直接冲突,30s 硬顶会把任何长流掐死,症状是连接被远端/ 代理静默切断、极难归因到客户端),保留拨号超时(建连阶段仍需兜底)。
适用面:SSE 转发、k8s proxy/长连接代理、WS 升级、chunked 大响应。 等价于 WithHertzOption(client.WithClientReadTimeout(0)) 的固化预设—— 把「流式要关什么」的知识固化进库,不必每个消费方自己拼逃生舱选项。
语义与边界:
- 与 WithTimeout 按 option 顺序后者覆盖(都是 append 到 hzOpts): 「预算拨号 + 不限读」写 WithTimeout(d), WithStream(); 「都不限」只写 WithStream();
- 流的总时长预算由调用方经 ctx 控制(context.WithTimeout),引擎层 不替流式请求做整段超时;
- 引擎层 Client 的响应体是全量缓冲语义(GetJSON/DoRaw 返回时 body 已读尽);真正的逐块消费经 net/http 桥接(StdClient + 本预设), 或直接用注入低阶 client 的 DoOnce。
func WithTimeout ¶
func WithTimeout(d time.Duration) ClientOption
WithTimeout 设置 HTTP 请求超时预算,同时覆盖**拨号**与**响应读取**两个 阶段(#90/#92):`WithTimeout(3s)` 对不可达主机(黑洞路由/半开连接)在 拨号阶段即 3s 超时——修复前仅设读超时,拨号沿用 hertz 默认 30s,调用方 以为的 3s 实际可等 33s。
语义边界(超时语义族,另见 #87 的流式说明):
- 覆盖面 = DialTimeout + ReadTimeout,各以 d 为独立上限(不是串行叠加 的总预算;与标准库 http.Client{Timeout} 的全生命周期覆盖不同—— TLS 握手计入拨号阶段,写超时不单独设,可经逃生舱 WithHertzOption(client.WithWriteTimeout(d)) 补充);
- 仅想调读超时、保留 30s 拨号上限:WithHertzOption(client.WithClientReadTimeout(d));
- 仅想调拨号:WithHertzOption(client.WithDialTimeout(d));
- 流式响应(SSE/代理长连接/WS 升级)不能用整段读超时掐流,用 WithStream。
func WithTracePropagation ¶
func WithTracePropagation() ClientOption
WithTracePropagation 启用 OpenTelemetry 上下文透传(traceparent / tracestate / baggage), 走 Hertz client.UseAsLast 注入,在所有自定义中间件之后执行,
避免 trace 头被其他中间件覆盖或重复。
propagator 通过 otel.GetTextMapPropagator() 读取(默认 W3C TraceContext + Baggage), 与 ekit observability/tracing 全局配置保持一致 — 调用方只需先初始化 tracer + propagator。
官方依据:https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/client/ (UseAsLast 章节:"中间件设置在 ctx.lastMiddleware 中,使用 Use 设置的中间件链存放在 ctx.mws 中,两者相对独立,只是在执行客户端中间件链的最后才执行 ctx.lastMiddleware")
type ConsistentHashOption ¶
type ConsistentHashOption func(*ConsistentHashSelector)
ConsistentHashOption ConsistentHashSelector 的可选参数。
func WithConsistentHashFunc ¶
func WithConsistentHashFunc(fn func([]byte) uint64) ConsistentHashOption
WithConsistentHashFunc 自定义哈希函数(默认 FNV-1a 64)。nil 被忽略。
func WithConsistentReplicas ¶
func WithConsistentReplicas(n int) ConsistentHashOption
WithConsistentReplicas 设置每个节点的虚拟副本数(默认 100)。 n <= 0 被忽略。
type ConsistentHashSelector ¶ added in v0.4.0
type ConsistentHashSelector struct {
// contains filtered or unexported fields
}
ConsistentHashSelector 一致性哈希选择器:同一 key 稳定路由到同一节点, 节点增减仅引起相邻键的重新映射。适合有状态粘滞路由(同用户/同租户 粘住同实例)且实例列表动态变化的场景。
key 取 SelectOption WithKey 值(httpx EndpointManager 默认传请求 PATH); 节点权重按比例折算虚拟副本数(等权均值归一,clamp 到 [1, 4×base]), 注册中心权重配置照常生效。
v0.4.0 起替代 ConsistentHashBalance(BalanceStrategy 形态已删除)。
func NewConsistentHashSelector ¶ added in v0.4.0
func NewConsistentHashSelector(opts ...ConsistentHashOption) (*ConsistentHashSelector, error)
NewConsistentHashSelector 创建一致性哈希选择器。
func (*ConsistentHashSelector) Select ¶ added in v0.4.0
func (b *ConsistentHashSelector) Select(ctx context.Context, nodes []loadbalance.Node, opts ...loadbalance.SelectOption) (loadbalance.Node, loadbalance.DoneFunc, error)
Select 按一致性哈希选择节点。
type EndpointManager ¶
type EndpointManager struct {
// contains filtered or unexported fields
}
EndpointManager endpoint 管理器:动态刷新节点快照 + 缓存降级, 选择经 loadbalance.Selector 完成。
func NewEndpointManager ¶
func NewEndpointManager(resolver ServiceResolver, selector loadbalance.Selector, refreshInterval time.Duration) *EndpointManager
NewEndpointManager 创建 endpoint 管理器。 selector 为 nil 时回退 roundrobin;refreshInterval > 0 且 resolver 非空时 启动刷新循环。
func (*EndpointManager) GetNodes ¶ added in v0.4.0
func (m *EndpointManager) GetNodes() []loadbalance.Node
GetNodes 获取节点快照(优先最新,降级缓存)。
func (*EndpointManager) Pick ¶
func (m *EndpointManager) Pick(ctx context.Context, key string) (string, loadbalance.DoneFunc, error)
Pick 选择一个节点地址,返回地址与结果回写句柄(调用结束后以最终 错误调用 done;done 为 nil 表示无需回写)。 key 用于一致性选点类 Selector(httpx 以请求 PATH 为默认键)。
func (*EndpointManager) SetEndpoints ¶
func (m *EndpointManager) SetEndpoints(endpoints []string)
SetEndpoints 设置静态端点列表(等权合成,停止动态刷新语义不变)。
func (*EndpointManager) SetNodes ¶ added in v0.4.0
func (m *EndpointManager) SetNodes(nodes []loadbalance.Node)
SetNodes 直接设置节点快照。
type InstanceResolver ¶ added in v0.4.0
type InstanceResolver interface {
// ResolveInstances 解析服务名称,返回实例列表。
ResolveInstances(ctx context.Context) ([]registry.Instance, error)
}
InstanceResolver 是 ServiceResolver 的可选增强接口:提供完整实例视图 (含权重与元数据)。EndpointManager 检测到 resolver 实现本接口时, 实例的权重与 Metadata 贯通到 Selector(加权策略与 WithFilter 生效); 仅实现 ServiceResolver 的 resolver 等权合成(权重 1、无元数据)。
type NNSClient ¶
type NNSClient interface {
GetInstances(ctx context.Context, serviceName string) ([]registry.Instance, error)
}
NNSClient NNS 客户端接口,与顶层 nns.NNSClient.GetInstances 签名一致。
type NNSResolver ¶
type NNSResolver struct {
// contains filtered or unexported fields
}
NNSResolver NNS 服务发现解析器(实现 ServiceResolver 与 InstanceResolver)。
func NewNNSResolver ¶
func NewNNSResolver(serviceName string, nnsClient NNSClient) *NNSResolver
NewNNSResolver 创建 NNS 解析器。
func (*NNSResolver) Resolve ¶
func (r *NNSResolver) Resolve(ctx context.Context) ([]string, error)
Resolve 解析服务名称,返回地址列表(丢弃权重与元数据;地址 scheme 从 实例 Metadata 取,缺省 "http",#90)。
func (*NNSResolver) ResolveInstances ¶ added in v0.4.0
ResolveInstances 解析服务名称,返回实例列表(地址 scheme 从实例 Metadata 取,键 MetadataKeyScheme,缺省 "http",#90)。 透传 context 到 NNS,保留超时和 trace_id 语义。
type PriorityQueue ¶
type PriorityQueue struct {
// contains filtered or unexported fields
}
PriorityQueue 分级准入控制队列。
注意语义:这是分级准入控制而非严格优先级队列——high/normal/low 三级 各自独立计数(同级 FIFO),不保证跨级抢占;高优先级请求不会抢占已获准的 低优先级请求,仅在自身级别容量内准入。未知优先级按 PriorityNormal 处理。
func NewPriorityQueue ¶
func NewPriorityQueue(capacity int) *PriorityQueue
NewPriorityQueue 创建优先级队列。
capacity 是每级(high/normal/low)的准入容量,必须 >= 1(#88):
- capacity == 0(配置值缺省的典型形态)会得到无缓冲 channel—— Acquire 的发送必须等 Release 接收,首个请求即死锁,所有请求卡在 Acquire 直到 ctx 超时/取消,症状是客户端全量超时,极难定位;
- capacity < 0 直接 panic(make 的运行期 panic,无原因说明)。
非法值在构造期 panic 并指明原因,不等到运行期静默阻塞。
func (*PriorityQueue) Acquire ¶
func (q *PriorityQueue) Acquire(ctx context.Context, priority Priority) bool
Acquire 获取许可(按优先级)。
func (*PriorityQueue) Release ¶
func (q *PriorityQueue) Release(priority Priority)
Release 释放许可。
priority 必须与 Acquire 时一致;未知优先级按 PriorityNormal 释放 (与 Acquire 的 default 分支对齐),避免静默泄漏许可。
type ServiceError ¶
ServiceError 表示 HTTP 服务调用返回的业务错误。
type ServiceResolver ¶
type ServiceResolver interface {
// Resolve 解析服务名称,返回可用的 endpoint 列表。
Resolve(ctx context.Context) ([]string, error)
}
ServiceResolver 服务发现解析器接口。
type StaticResolver ¶
type StaticResolver struct {
// contains filtered or unexported fields
}
StaticResolver 静态 endpoint 解析器。
func NewStaticResolver ¶
func NewStaticResolver(endpoints []string) *StaticResolver
NewStaticResolver 创建静态解析器。
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package codec 提供请求/响应编解码器的注册制抽象(参照 go-kratos encoding)。
|
Package codec 提供请求/响应编解码器的注册制抽象(参照 go-kratos encoding)。 |
|
Package loadbalance 提供与具体服务发现实现解耦的节点选择抽象(Selector, 参照 go-kratos selector)。
|
Package loadbalance 提供与具体服务发现实现解耦的节点选择抽象(Selector, 参照 go-kratos selector)。 |