httpclient

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 6, 2026 License: MIT Imports: 20 Imported by: 0

README

HTTP Client

CI Go Reference License

github.com/ClownSketch/httpclient 是一套基于 Go net/http 构建的出站 HTTP 客户端库,面向长期运行的服务、上游 Provider、内部 API 调用和公共 SDK。

它负责把连接池、请求构建、响应读取、重试、错误分类和可观测扩展组织成稳定的公共 API,不负责业务签名、服务发现、熔断、限流或日志存储。

主要能力

  • 可长期复用、并发安全的 Client
  • 链式 Request builder 和便捷请求方法
  • JSON、XML、表单、文本、字节和 io.Reader 请求体
  • JSON/XML 自动响应解码
  • 固定请求数据重试、指数退避、抖动和 Retry-After
  • 请求级 Before / After 生命周期回调
  • 客户端级和请求级 Interceptor
  • 与具体链路库解耦的 Trace 接口
  • Cookie Jar、Basic Auth、Bearer Token、代理和 TLS
  • 请求/响应体大小限制和统一错误分类
  • 请求级 Mock
  • 配置化 factory 子包

设计边界

这个库刻意不内置以下能力:

  • 不生成或刷新签名、时间戳、Nonce
  • 不判断请求是否具备业务幂等性
  • 不提供服务发现、负载均衡、熔断和限流
  • 不绑定任何日志或 Trace 实现
  • 不提供 HTTP Server
  • 不提供流式上传或流式响应;请求和响应体会进入内存

这些边界让核心客户端保持通用,也避免把宿主业务规则固化进基础库。

环境要求

  • Go 1.23.8 或更高版本
  • 核心库没有第三方运行时依赖

安装

go get github.com/ClownSketch/httpclient

快速开始

package main

import (
	"context"
	"log"
	"time"

	clienthttp "github.com/ClownSketch/httpclient"
)

func main() {
	client := clienthttp.New(
		clienthttp.WithDefaultTimeout(5*time.Second),
		clienthttp.WithDefaultHeader("X-Service", "resource-service"),
	)
	defer client.Close()

	resp, err := client.Get(context.Background(), "https://example.com/health")
	if err != nil {
		log.Fatal(err)
	}

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

Client 应在应用启动时创建并长期复用,不应为每个请求重复创建。

JSON 请求与响应

type CreateResourceRequest struct {
	TenantID string `json:"tenant_id"`
	Name     string `json:"name"`
}

type CreateResourceResponse struct {
	ResourceID string `json:"resource_id"`
	Status     string `json:"status"`
}

resp, err := client.Post(
	ctx,
	"https://api.example.com/resources",
	clienthttp.Header("X-Request-ID", "request-10001"),
	clienthttp.JSONBody(CreateResourceRequest{
		TenantID: "TENANT001",
		Name:     "document",
	}),
)
if err != nil {
	return err
}

var result CreateResourceResponse
if err := resp.Decode(&result); err != nil {
	return err
}

Decode 根据响应 Content-Type 选择 JSON 或 XML;JSON 始终按 JSON 解码。

Builder

复杂请求可以使用链式 builder:

resp, err := client.R().
	Method(http.MethodPost).
	URL("https://api.example.com/resources").
	Header("X-Request-ID", "request-10001").
	Query("expand", "owner").
	Bearer(token).
	JSON(payload).
	Timeout(3 * time.Second).
	Do(ctx)

每次调用 Client.R() 都会创建独立的请求构建器。Request 不允许多个 goroutine 同时修改或执行。

重试语义

默认 MaxAttempts=1,所有 HTTP Method 都只执行一次。

client := clienthttp.New(
	clienthttp.WithRetryPolicy(clienthttp.RetryPolicy{
		MaxAttempts: 3,
		BaseDelay:   100 * time.Millisecond,
		MaxDelay:    2 * time.Second,
	}),
)

配置重试后,库会使用相同的 Method、URL、Header、Query 和 Body 重新构建网络请求。它不会更新业务签名、时间戳或 Nonce。

对具有副作用的写操作启用重试前,调用方必须确认上游接口支持幂等,并提供稳定的业务幂等键。

默认策略会考虑:

  • 429 Too Many Requests
  • 503 Service Unavailable 的 Retry-After
  • 除 501 Not Implemented 外的 5xx
  • 明确可识别的临时网络错误

上下文取消和超时不会继续重试。

请求生命周期

Before 在请求进入拦截器链前执行一次,After 在一次逻辑请求最终结束后执行一次:

resp, err := client.Post(
	ctx,
	url,
	clienthttp.JSONBody(payload),
	clienthttp.Before(func(ctx context.Context, event *clienthttp.BeforeEvent) error {
		return validateRequest(event.Request)
	}),
	clienthttp.After(func(ctx context.Context, event *clienthttp.AfterEvent) {
		if event.Failed() {
			notifyFailure(ctx, event)
		}
	}),
)

生命周期事件包含独立快照,回调修改快照不会改变真实请求或返回给调用方的响应。回调由当前 goroutine 同步执行;异步通知应由调用方自行投递。

Interceptor

func requestIDInterceptor(next clienthttp.Handler) clienthttp.Handler {
	return func(ctx context.Context, req *clienthttp.Request) (*clienthttp.Response, error) {
		req.Header("X-Request-ID", newRequestID())
		return next(ctx, req)
	}
}

client := clienthttp.New(
	clienthttp.WithInterceptor(requestIDInterceptor),
)

客户端级拦截器按注册顺序进入、按相反顺序退出。请求级拦截器通过 Request.Use 添加。

请求级拦截器包裹客户端级拦截器。使用 WithRoundTripper 后,自定义传输器完整接管 TLS、代理和连接池行为。

Trace 集成

核心包只定义 Tracer、Span 和 TracePropagator 接口,不直接依赖任何 Trace 库:

client := clienthttp.New(
	clienthttp.WithTrace(clienthttp.TraceConfig{
		Tracer:     tracerAdapter,
		Mode:       clienthttp.TraceModeAuto,
		Propagator: propagatorAdapter,
	}),
)

TraceModeAuto 会优先复用父 Span,没有父 Span 时创建 Client Span。库本身不执行脱敏;需要脱敏时由主项目提供 DataSanitizer。

Transport 配置

WithTransportConfig 是整体替换语义。只修改部分字段时,应从默认配置开始:

transport := clienthttp.DefaultTransportConfig()
transport.MaxIdleConns = 512
transport.MaxIdleConnsPerHost = 128
transport.MaxConnsPerHost = 128

client := clienthttp.New(
	clienthttp.WithTransportConfig(transport),
)

默认限制:

  • 请求体最大 1 MiB
  • 响应体最大 10 MiB
  • HTTP 调用超时 30s
  • 最大重定向次数 10

配置值 0 的具体含义以 TransportConfig 字段注释为准。

Factory

需要统一加载超时、重试、认证、TLS 和 Trace 时,可使用 factory:

client := factory.Must(factory.Config{
	Timeout: 5 * time.Second,
	DefaultHeaders: map[string]string{
		"X-Service": "resource-service",
	},
	RetryPolicy: clienthttp.RetryPolicy{
		MaxAttempts: 2,
		BaseDelay:   100 * time.Millisecond,
	},
})

文档

验证

go test ./...
go test -race ./...
go vet ./...
go test -run '^$' -bench . -benchmem

当前核心包测试覆盖率约为 78.7%,Factory 包约为 55.1%。

Documentation

Overview

Package httpclient provides a reusable outbound HTTP client built on top of net/http.

Client is safe for concurrent use and should normally be created once during application startup. Request is a mutable builder for one logical call and must not be modified or executed concurrently.

The package provides buffered request and response handling, retries, interceptors, request lifecycle callbacks, typed errors, cookie jars and tracing abstractions without depending on a concrete observability library. Business signing, idempotency, service discovery, circuit breaking and notification delivery remain the caller's responsibility.

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrNilContext 表示请求没有提供上下文。
	ErrNilContext = errors.New("http client: nil context")
	// ErrMissingURL 表示请求未设置 URL。
	ErrMissingURL = errors.New("http client: missing request url")
	// ErrMissingMethod 表示请求未设置 Method。
	ErrMissingMethod = errors.New("http client: missing request method")
)

Functions

func IsKind

func IsKind(err error, kind ErrorKind) bool

IsKind 判断错误是否属于指定分类。

Types

type AfterEvent

type AfterEvent struct {
	Request     *RequestSnapshot
	HTTPRequest *http.Request
	Response    *Response
	Err         error
	Attempts    int
	StartedAt   time.Time
	FinishedAt  time.Time
	Duration    time.Duration
}

AfterEvent 保存请求最终执行结果。

func (*AfterEvent) Failed

func (e *AfterEvent) Failed() bool

Failed 判断请求是否未成功返回 2xx 响应。

func (*AfterEvent) Succeeded

func (e *AfterEvent) Succeeded() bool

Succeeded 判断请求是否成功返回 2xx 响应。

type AfterHandler

type AfterHandler func(ctx context.Context, event *AfterEvent)

AfterHandler 在一次逻辑请求结束后运行。 回调同步执行一次;需要异步处理时,由调用方自行投递。

type BeforeEvent

type BeforeEvent struct {
	Request *RequestSnapshot
}

BeforeEvent 保存请求执行前的数据。

type BeforeHandler

type BeforeHandler func(ctx context.Context, event *BeforeEvent) error

BeforeHandler 在请求通过本地校验后、进入执行链前运行。 返回错误会终止请求,AfterHandler 仍会收到最终结果。

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client 是一个不可变、并发安全的 HTTP 客户端。 运行期不会原地修改共享配置,适合在高并发环境中长期复用。

func New

func New(opts ...Option) *Client

New 创建一个新的高并发 HTTP 客户端。

func (*Client) Clone

func (c *Client) Clone(opts ...Option) *Client

Clone 基于当前配置返回一个新客户端。 没有传入传输层选项时,新旧客户端共享底层连接池。

func (*Client) Close

func (c *Client) Close() error

Close 释放空闲连接。 共享连接池的 Client 或 Clone 调用 Close 时,会同时关闭该连接池中的空闲连接。

func (*Client) Delete

func (c *Client) Delete(ctx context.Context, rawURL string, opts ...RequestOption) (*Response, error)

Delete 是便捷方法。

func (*Client) Do

func (c *Client) Do(ctx context.Context, req *Request) (resp *Response, err error)

Do 执行一个请求。

func (*Client) Get

func (c *Client) Get(ctx context.Context, rawURL string, opts ...RequestOption) (*Response, error)

Get 是便捷方法。

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"time"

	clienthttp "github.com/ClownSketch/httpclient"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(`{"status":"ok"}`))
	}))
	defer server.Close()

	client := clienthttp.New(
		clienthttp.WithDefaultTimeout(2*time.Second),
		clienthttp.WithDefaultHeader("X-Service", "resource"),
	)

	resp, err := client.Get(context.Background(), server.URL)
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println(resp.StatusCode, resp.Text())
}
Output:
200 {"status":"ok"}

func (*Client) Post

func (c *Client) Post(ctx context.Context, rawURL string, opts ...RequestOption) (*Response, error)

Post 是便捷方法。

func (*Client) PostForm

func (c *Client) PostForm(ctx context.Context, rawURL string, values url.Values, opts ...RequestOption) (*Response, error)

PostForm 是表单请求便捷方法。

func (*Client) Put

func (c *Client) Put(ctx context.Context, rawURL string, opts ...RequestOption) (*Response, error)

Put 是便捷方法。

func (*Client) R

func (c *Client) R() *Request

R 创建一个带默认值的请求构建器。

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"time"

	clienthttp "github.com/ClownSketch/httpclient"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_ = r.ParseForm()
		fmt.Println(r.Method, r.Header.Get("Content-Type"), r.Form.Get("name"))
		w.WriteHeader(http.StatusCreated)
	}))
	defer server.Close()

	client := clienthttp.New()

	resp, err := client.R().
		Method(http.MethodPost).
		URL(server.URL).
		Form(url.Values{"name": []string{"alice"}}).
		Timeout(2 * time.Second).
		Do(context.Background())
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println(resp.StatusCode)
}
Output:
POST application/x-www-form-urlencoded alice
201

func (*Client) Request

func (c *Client) Request(ctx context.Context, method, rawURL string, opts ...RequestOption) (*Response, error)

Request 是通用请求入口。

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"

	clienthttp "github.com/ClownSketch/httpclient"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println(r.Method, r.URL.Query().Get("page"), r.Header.Get("X-Request-ID"))
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := clienthttp.New()

	_, _ = client.Request(
		context.Background(),
		http.MethodPatch,
		server.URL,
		clienthttp.Query("page", "1"),
		clienthttp.Header("X-Request-ID", "req-001"),
		clienthttp.StringBody("patched"),
	)

}
Output:
PATCH 1 req-001

type DataSanitizer

type DataSanitizer interface {
	SanitizeRequest(req *Request) *Request
	SanitizeResponse(resp *Response) *Response
}

DataSanitizer 用于按主项目规则处理 trace 请求与响应快照。 httpclient 本身只负责调用,不内置任何脱敏策略。

type Error

type Error struct {
	Kind       ErrorKind
	Op         string
	Method     string
	URL        string
	Attempt    int
	StatusCode int
	Err        error
	Message    string
}

Error 是新客户端的统一错误模型。 它保留了 method/url/attempt 等关键上下文,方便业务层分类处理。

func (*Error) Error

func (e *Error) Error() string

Error 返回包含请求上下文的错误文本。 @return result string 错误文本

func (*Error) Timeout

func (e *Error) Timeout() bool

Timeout 让 *Error 能和 net.Error 风格一起工作。

func (*Error) Unwrap

func (e *Error) Unwrap() error

Unwrap 返回底层错误,支持 errors.Is 和 errors.As。 @return err error 底层错误

type ErrorKind

type ErrorKind string

ErrorKind 表示 HTTP 客户端错误类别。

const (
	ErrorKindInvalidRequest       ErrorKind = "invalid_request"
	ErrorKindBeforeHandler        ErrorKind = "before_handler"
	ErrorKindInterceptor          ErrorKind = "interceptor"
	ErrorKindBuildRequest         ErrorKind = "build_request"
	ErrorKindRequestBodyTooLarge  ErrorKind = "request_body_too_large"
	ErrorKindResponseBodyTooLarge ErrorKind = "response_body_too_large"
	ErrorKindRedirect             ErrorKind = "redirect"
	ErrorKindNetwork              ErrorKind = "network"
	ErrorKindTimeout              ErrorKind = "timeout"
	ErrorKindCanceled             ErrorKind = "canceled"
	ErrorKindRetryExhausted       ErrorKind = "retry_exhausted"
	ErrorKindResponseRead         ErrorKind = "response_read"
	ErrorKindDecodeResponse       ErrorKind = "decode_response"
)

type HTTPAttemptRecord

type HTTPAttemptRecord struct {
	Attempt    int
	Method     string
	StatusCode int
	Status     string
	Headers    http.Header
	Body       []byte
	Duration   time.Duration
	FinishedAt time.Time
	Error      string
	Success    bool
}

HTTPAttemptRecord 保存一次实际网络尝试的结果。

type HTTPCallRecord

type HTTPCallRecord struct {
	ServiceName string
	SpanID      string
	TraceID     string
	CallerName  string
	Request     *HTTPRequestRecord
	Attempts    []*HTTPAttemptRecord
	Success     bool
	Duration    time.Duration
	Error       string
	StartedAt   time.Time
	FinishedAt  time.Time
}

HTTPCallRecord 保存一次逻辑 HTTP 调用及其全部尝试。 该结构只描述 HTTP 事实,不包含具体 tracer 的事件字段约定。

func (*HTTPCallRecord) AddAttempt

func (r *HTTPCallRecord) AddAttempt(attempt *HTTPAttemptRecord)

AddAttempt 追加一次实际请求尝试。

func (*HTTPCallRecord) EventData

func (r *HTTPCallRecord) EventData() map[string]any

EventData 返回兼容 tracer external.call 事件结构的数据快照。 自定义追踪系统可以通过 TraceConfig.EventBuilder 使用自己的事件格式。

type HTTPRequestRecord

type HTTPRequestRecord struct {
	Method    string
	URL       string
	Query     string
	Headers   http.Header
	Body      []byte
	Timeout   time.Duration
	UserAgent string
	Duration  time.Duration
	StartedAt time.Time
}

HTTPRequestRecord 保存逻辑请求快照。

type Handler

type Handler func(ctx context.Context, req *Request) (*Response, error)

Handler 是拦截器链的下游处理函数。

type Interceptor

type Interceptor func(next Handler) Handler

Interceptor 用于在发送请求前后扩展行为。

func NewTraceInterceptor

func NewTraceInterceptor(cfg TraceConfig) Interceptor

NewTraceInterceptor 创建一个支持单 span / 多 span 的追踪拦截器。

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"

	clienthttp "github.com/ClownSketch/httpclient"
)

func main() {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println(r.Header.Get("X-Trace-ID") != "")
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	tracer := &exampleTracer{}
	client := clienthttp.New(
		clienthttp.WithTrace(clienthttp.TraceConfig{
			Tracer: tracer,
			Mode:   clienthttp.TraceModeAuto,
		}),
	)

	_, _ = client.Get(context.Background(), server.URL)

}

type exampleTracer struct{}

func (exampleTracer) Start(ctx context.Context, name string, opts ...clienthttp.TracerOption) (context.Context, clienthttp.Span) {
	return ctx, exampleSpan{id: "trace-123"}
}

func (exampleTracer) GetSpanFromContext(ctx context.Context) clienthttp.Span {
	return nil
}

type exampleSpan struct {
	id string
}

func (s exampleSpan) AddEvent(name, eventType string, eventData func() map[string]any) {}

func (s exampleSpan) WithError(err error, message string) {}

func (s exampleSpan) End() {}

func (s exampleSpan) GetSpanTraceID() string {
	return s.id
}
Output:
true

func RecoverInterceptor

func RecoverInterceptor(handler func(ctx context.Context, req *Request, panicErr error, stack []byte)) Interceptor

RecoverInterceptor 捕获拦截器链中的 panic。

func TraceInterceptor

func TraceInterceptor(tracer Tracer, sanitizer DataSanitizer) Interceptor

TraceInterceptor 使用默认配置创建一个追踪拦截器。 默认模式为 TraceModeAuto,会优先复用父 span,没有父 span 时新建 client span。

type MockResponder

type MockResponder func(ctx context.Context, req *Request) (*Response, error)

MockResponder 用于请求级 mock。 返回的 Response 会被当前请求链继续消费,因此 tracing / recover 等仍然可用。

type Option

type Option interface {
	// contains filtered or unexported methods
}

Option 用于构建 Client。 公开构造函数返回 Option,调用方不需要自行实现。

func WithBasicAuth

func WithBasicAuth(username, password string) Option

WithBasicAuth 配置默认 basic auth。

func WithBearerToken

func WithBearerToken(token string) Option

WithBearerToken 配置默认 bearer token。

func WithCookie

func WithCookie(cookie *http.Cookie) Option

WithCookie 配置默认 cookie。

func WithCookieJar

func WithCookieJar(jar http.CookieJar) Option

WithCookieJar 配置由 net/http 管理的 Cookie Jar。 Jar 保存会话状态,Client.Clone 默认共享同一个实例。

func WithDefaultHeader

func WithDefaultHeader(key, value string) Option

WithDefaultHeader 配置默认 header。

func WithDefaultHeaders

func WithDefaultHeaders(headers map[string]string) Option

WithDefaultHeaders 批量配置默认 header。

func WithDefaultQuery

func WithDefaultQuery(key, value string) Option

WithDefaultQuery 配置默认 query 参数。

func WithDefaultTimeout

func WithDefaultTimeout(timeout time.Duration) Option

WithDefaultTimeout 配置默认请求超时。

func WithInterceptor

func WithInterceptor(interceptor Interceptor) Option

WithInterceptor 配置客户端级拦截器。

func WithMaxRedirects

func WithMaxRedirects(maxRedirects int) Option

WithMaxRedirects 配置最大重定向次数;0 表示不跟随重定向。

func WithProxy

func WithProxy(proxy *url.URL) Option

WithProxy 配置代理。

func WithRetryCondition

func WithRetryCondition(condition RetryFunc) Option

WithRetryCondition 配置默认重试判定函数。

func WithRetryPolicy

func WithRetryPolicy(policy RetryPolicy) Option

WithRetryPolicy 配置默认重试策略。

func WithRoundTripper

func WithRoundTripper(rt http.RoundTripper) Option

WithRoundTripper 配置自定义 RoundTripper。 设置后由 RoundTripper 完整接管 TLS、代理和连接池行为,其他传输配置不会应用到它。

func WithTLSConfig

func WithTLSConfig(cfg *tls.Config) Option

WithTLSConfig 配置 TLS。

func WithTrace

func WithTrace(cfg TraceConfig) Option

WithTrace 使用通用 TraceConfig 注册 tracing 拦截器。 具体 tracing 库的适配逻辑应由主项目完成,再通过这里注入。

func WithTransportConfig

func WithTransportConfig(cfg TransportConfig) Option

WithTransportConfig 使用传入值整体替换底层传输配置。 需要保留默认值时,应从 DefaultTransportConfig 开始修改。

type Request

type Request struct {
	// contains filtered or unexported fields
}

Request 是可链式构建的单次请求对象。 Request 构建器不是并发安全对象;每次调用 Client.R 创建独立请求后再执行。

func (*Request) AddHeader

func (r *Request) AddHeader(key, value string) *Request

AddHeader 追加请求头。

func (*Request) After

func (r *Request) After(handler AfterHandler) *Request

After 设置请求结束回调。 成功、非 2xx 响应及执行错误都会同步调用一次。

func (*Request) Apply

func (r *Request) Apply(opts ...RequestOption) error

Apply 应用 request option。

func (*Request) BasicAuth

func (r *Request) BasicAuth(username, password string) *Request

BasicAuth 设置 basic auth。

func (*Request) Bearer

func (r *Request) Bearer(token string) *Request

Bearer 设置 bearer token。

func (*Request) Before

func (r *Request) Before(handler BeforeHandler) *Request

Before 设置请求执行前守卫。 守卫返回错误时终止请求,After 仍会接收失败结果。

func (*Request) Bytes

func (r *Request) Bytes(data []byte) *Request

Bytes 设置字节请求体。

func (*Request) Cookie

func (r *Request) Cookie(cookie *http.Cookie) *Request

Cookie 追加 cookie。

func (*Request) Do

func (r *Request) Do(ctx context.Context) (*Response, error)

Do 使用关联的 Client 执行请求。

func (*Request) Form

func (r *Request) Form(values url.Values) *Request

Form 设置表单请求体。

func (*Request) Header

func (r *Request) Header(key, value string) *Request

Header 设置请求头。

func (*Request) HeaderMap

func (r *Request) HeaderMap() http.Header

HeaderMap 返回可变的请求头映射,供 TracePropagator 等扩展直接写入。

func (*Request) Headers

func (r *Request) Headers(headers map[string]string) *Request

Headers 批量设置请求头。

func (*Request) JSON

func (r *Request) JSON(v any) *Request

JSON 设置 JSON 请求体。

func (*Request) Method

func (r *Request) Method(method string) *Request

Method 设置请求方法。

func (*Request) Mock

func (r *Request) Mock(responder MockResponder) *Request

Mock 为当前请求设置 mock responder。

Example
package main

import (
	"context"
	"fmt"
	"net/http"

	clienthttp "github.com/ClownSketch/httpclient"
)

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

	resp, err := client.R().
		Method(http.MethodPost).
		URL("https://gateway.example.com/pay").
		JSON(map[string]any{"amount": 100}).
		Mock(func(ctx context.Context, req *clienthttp.Request) (*clienthttp.Response, error) {
			return &clienthttp.Response{
				StatusCode: http.StatusOK,
				Status:     "200 OK",
				Body:       []byte(`{"code":"SUCCESS"}`),
			}, nil
		}).
		Do(context.Background())
	if err != nil {
		fmt.Println("error:", err)
		return
	}

	fmt.Println(resp.StatusCode, resp.Text())
}
Output:
200 {"code":"SUCCESS"}

func (*Request) Queries

func (r *Request) Queries(values map[string]string) *Request

Queries 批量设置查询参数。

func (*Request) Query

func (r *Request) Query(key, value string) *Request

Query 设置查询参数。

func (*Request) Reader

func (r *Request) Reader(reader io.Reader) *Request

Reader 从 io.Reader 读取请求体内容。

func (*Request) Retry

func (r *Request) Retry(policy RetryPolicy) *Request

Retry 设置请求级重试策略。

func (*Request) SkipTrace

func (r *Request) SkipTrace() *Request

SkipTrace 跳过当前请求的 tracing interceptor。

func (*Request) String

func (r *Request) String(data string) *Request

String 设置文本请求体。

func (*Request) Timeout

func (r *Request) Timeout(timeout time.Duration) *Request

Timeout 设置请求超时。

func (*Request) URL

func (r *Request) URL(rawURL string) *Request

URL 设置请求地址。

func (*Request) Use

func (r *Request) Use(interceptor Interceptor) *Request

Use 追加请求级拦截器。

type RequestOption

type RequestOption func(*Request) error

RequestOption 用于便捷方法中的单次请求配置。

func After

func After(handler AfterHandler) RequestOption

After 配置请求结束回调。

Example
package main

import (
	"context"
	"fmt"
	"net/http"

	clienthttp "github.com/ClownSketch/httpclient"
)

func main() {
	client := clienthttp.New()
	resp, _ := client.Get(
		context.Background(),
		"https://gateway.example.com/pay",
		clienthttp.Mock(func(context.Context, *clienthttp.Request) (*clienthttp.Response, error) {
			return &clienthttp.Response{StatusCode: http.StatusBadGateway}, nil
		}),
		clienthttp.After(func(_ context.Context, event *clienthttp.AfterEvent) {
			fmt.Println("failed", event.Failed(), event.Response.StatusCode)
		}),
	)
	fmt.Println("result", resp.StatusCode)

}
Output:
failed true 502
result 502

func Before

func Before(handler BeforeHandler) RequestOption

Before 配置请求执行前守卫。

func BytesBody

func BytesBody(data []byte) RequestOption

BytesBody 是一个 RequestOption。

func FormBody

func FormBody(values url.Values) RequestOption

FormBody 是一个 RequestOption。

func Header(key, value string) RequestOption

Header 是一个 RequestOption。

func JSONBody

func JSONBody(v any) RequestOption

JSONBody 是一个 RequestOption。

func Mock

func Mock(responder MockResponder) RequestOption

Mock 是一个 RequestOption,用于为当前请求设置 mock responder。

func Query

func Query(key, value string) RequestOption

Query 是一个 RequestOption。

func ReaderBody

func ReaderBody(reader io.Reader) RequestOption

ReaderBody 是一个 RequestOption。

func Retry

func Retry(policy RetryPolicy) RequestOption

Retry 是一个 RequestOption。

func RetryCondition

func RetryCondition(condition RetryFunc) RequestOption

RetryCondition 是一个 RequestOption,用于覆盖请求级重试判定。

func SkipTrace

func SkipTrace() RequestOption

SkipTrace 是一个 RequestOption,用于跳过本次请求的 tracing interceptor。

func StringBody

func StringBody(data string) RequestOption

StringBody 是一个 RequestOption。

func Timeout

func Timeout(timeout time.Duration) RequestOption

Timeout 是一个 RequestOption。

type RequestSnapshot

type RequestSnapshot struct {
	Method    string
	URL       string
	Headers   http.Header
	Query     url.Values
	Cookies   []*http.Cookie
	Body      []byte
	Timeout   time.Duration
	Retry     RetryPolicy
	Mock      bool
	SkipTrace bool
}

RequestSnapshot 保存生命周期事件中的请求快照。 快照与真实请求相互独立,回调修改快照不会影响请求执行。

type Response

type Response struct {
	StatusCode int
	Status     string
	Headers    http.Header
	Cookies    []*http.Cookie
	Body       []byte
	Request    *http.Request
	Duration   time.Duration
	Attempt    int
}

Response 是对外标准响应。

func (*Response) Decode

func (r *Response) Decode(target any) error

Decode 根据响应 Content-Type 将响应体解析到目标值。 Content-Type 缺失或不准确时,会根据 JSON/XML 的首字符进行识别。

Example
package main

import (
	"fmt"
	"net/http"

	clienthttp "github.com/ClownSketch/httpclient"
)

func main() {
	resp := &clienthttp.Response{
		StatusCode: http.StatusOK,
		Headers:    http.Header{"Content-Type": []string{"application/json"}},
		Body:       []byte(`{"status":"ok"}`),
	}
	var result struct {
		Status string `json:"status"`
	}
	if err := resp.Decode(&result); err != nil {
		fmt.Println("error:", err)
		return
	}
	fmt.Println(result.Status)

}
Output:
ok

func (*Response) IsClientError

func (r *Response) IsClientError() bool

IsClientError 判断是否为 4xx。

func (*Response) IsServerError

func (r *Response) IsServerError() bool

IsServerError 判断是否为 5xx。

func (*Response) IsSuccess

func (r *Response) IsSuccess() bool

IsSuccess 判断是否为 2xx。

func (*Response) JSON

func (r *Response) JSON(v any) error

JSON 将响应体解码为 JSON。

func (*Response) Text

func (r *Response) Text() string

Text 返回响应体文本。

type RetryBackoffFunc

type RetryBackoffFunc func(attempt int, resp *Response, err error) time.Duration

RetryBackoffFunc 计算下一次重试前的等待时间。 attempt 表示刚刚失败的尝试次数,从 1 开始。

type RetryFunc

type RetryFunc func(resp *Response, err error) bool

RetryFunc 决定响应或错误是否应该重试。

type RetryPolicy

type RetryPolicy struct {
	MaxAttempts int              // 最大尝试次数,包含首次请求;小于等于 0 时只请求一次
	BaseDelay   time.Duration    // 基础延迟时间
	MaxDelay    time.Duration    // 最大退避时间;小于等于 0 时使用 30 秒
	ShouldRetry RetryFunc        // 重试条件
	Backoff     RetryBackoffFunc // 自定义退避策略
}

RetryPolicy 定义重试策略。

type Span

type Span interface {
	AddEvent(name, eventType string, eventData func() map[string]any)
	WithError(err error, message string)
	End()
	GetSpanTraceID() string
}

Span 用于记录事件与错误。

type SpanKind

type SpanKind int

SpanKind 表示 span 类型。

const (
	SpanKindInternal SpanKind = iota
	SpanKindServer
	SpanKindClient
	SpanKindProducer
	SpanKindConsumer
)

func SpanKindFromTracerOption

func SpanKindFromTracerOption(opt TracerOption) (SpanKind, bool)

SpanKindFromTracerOption 提取 TracerOption 中的 SpanKind。

type TraceConfig

type TraceConfig struct {
	Tracer          Tracer
	Mode            TraceMode
	Sanitizer       DataSanitizer
	Propagator      TracePropagator
	EventName       string
	EventType       string
	EventBuilder    func(*HTTPCallRecord) map[string]any
	OperationNamer  func(*Request) string
	ServiceResolver func(*Request) string
	CallerName      string
	CallerResolver  func(*Request) string
}

TraceConfig 定义 TraceInterceptor 的行为。

type TraceMode

type TraceMode int

TraceMode 定义 TraceInterceptor 的 span 管理策略。

const (
	// TraceModeAuto 会优先复用上下文中的 span;如果没有可用 span,则创建新的 client span。
	TraceModeAuto TraceMode = iota
	// TraceModeSingle 只复用上下文中的 span,不会为 HTTP 请求创建新 span。
	TraceModeSingle
	// TraceModeMulti 总是为 HTTP 请求创建新的 client span。
	TraceModeMulti
)

type TracePropagator

type TracePropagator interface {
	Inject(ctx context.Context, req *Request, span Span)
}

TracePropagator 由主项目实现,用于把 trace 上下文注入到请求中。 这让 http 客户端无需直接依赖任何具体 tracing 库。

type Tracer

type Tracer interface {
	Start(ctx context.Context, name string, opts ...TracerOption) (context.Context, Span)
	GetSpanFromContext(ctx context.Context) Span
}

Tracer 用于抽象追踪系统。

type TracerOption

type TracerOption interface{}

TracerOption 定义追踪选项。

func WithSpanKind

func WithSpanKind(kind SpanKind) TracerOption

WithSpanKind 创建一个 span kind 选项。

type TransportConfig

type TransportConfig struct {
	MaxRedirects        int               // 最大重定向次数;0 禁止重定向,负数使用 net/http 默认策略
	MaxIdleConns        int               // 全局最大空闲连接数
	MaxIdleConnsPerHost int               // 单主机最大空闲连接数
	MaxConnsPerHost     int               // 单主机最大连接数;0 不限制
	IdleConnTimeout     time.Duration     // 空闲连接保留时间
	TLSHandshakeTimeout time.Duration     // TLS 握手超时;0 不限制
	DisableKeepAlives   bool              // 是否禁用连接复用
	DisableCompression  bool              // 是否禁用自动压缩协商
	MaxResponseBodySize int64             // 最大响应体字节数;0 不限制
	MaxRequestBodySize  int64             // 最大请求体字节数;0 不限制
	TLSConfig           *tls.Config       // TLS 配置
	Proxy               *url.URL          // 固定代理地址
	RoundTripper        http.RoundTripper // 自定义底层传输器;设置后由其完整接管 TLS、代理和连接池行为
	Timeout             time.Duration     // 整个 HTTP 调用超时;0 不限制
}

TransportConfig 用于配置底层连接池与 TLS 行为。

func DefaultTransportConfig

func DefaultTransportConfig() TransportConfig

DefaultTransportConfig 返回库的默认传输配置。 调用方需要整体调整传输层时,应先获取默认值,再修改需要的字段。

Directories

Path Synopsis
examples
provider command
Command provider demonstrates a retryable provider request with caller-supplied data.
Command provider demonstrates a retryable provider request with caller-supplied data.
Package factory builds reusable httpclient clients from structured configuration and optional TLS certificate files.
Package factory builds reusable httpclient clients from structured configuration and optional TLS certificate files.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL