Skip to content

请求与响应变更器

中间件不直接操作底层请求/响应对象,而是通过**变更器(Mutator)**接口读写。中间件收到的始终是完整的读写变更器(RequestMutator / ResponseMutator);下文按「读取方法」「写入方法」分组列出,仅是为了便于阅读,并非独立的导出接口。

text
RequestMutator  =  读取方法  +  写入方法
ResponseMutator =  读取方法  +  写入方法
        ↑                                    ↑
  中间件经 RequestMutator 改写请求     中间件经 ResponseMutator 读取/改写响应

Handler 的签名 func(ctx, RequestMutator) (ResponseMutator, error) 正是把这两个变更器作为中间件的入口与出口。

请求变更器

读取方法

下列方法读取请求数据。中间件仅需检视请求属性时调用这些方法。

方法返回类型说明
Method()stringHTTP 方法
URL()string请求 URL
Headers()map[string]string全部请求头(键→单值)
QueryParams()map[string]any查询参数
Body()any请求体
Timeout()time.Duration请求超时
MaxRetries()int最大重试次数
Context()context.Context请求上下文
Cookies()[]http.Cookie请求 Cookie
FollowRedirects()*bool是否跟随重定向(nil 表示用默认值)
MaxRedirects()*int最大重定向次数(nil 表示用默认值)
StreamBody()bool是否流式传输请求体

写入方法

下列方法修改请求数据。中间件仅需修改请求属性时调用这些方法。

方法说明
SetMethod(string)设置 HTTP 方法
SetURL(string)设置 URL
SetHeaders(map[string]string)设置全部请求头(整体替换)
SetHeader(key, value string)设置单个请求头(增/改)
SetQueryParams(map[string]any)设置查询参数
SetBody(any)设置请求体
SetTimeout(time.Duration)设置超时
SetMaxRetries(int)设置最大重试次数
SetContext(context.Context)设置上下文
SetCookies([]http.Cookie)设置 Cookie
SetFollowRedirects(*bool)设置是否跟随重定向
SetMaxRedirects(*int)设置最大重定向次数
SetStreamBody(bool)设置是否流式传输

RequestMutator

RequestMutator 是 httpc 暴露的读写请求变更器接口,涵盖上方「读取方法」与「写入方法」两张表的全部方法。其内部的读/写分体接口位于 internal/types 包,未单独导出,外部统一以 RequestMutator 引用。中间件在请求发出前经它检视并改写请求属性。

中间件中 RequestMutator 的典型操作

操作场景方法组合说明
修改请求头SetHeader(key, val) / Headers() + SetHeader注入认证头、追踪 ID、API 版本号
修改查询参数QueryParams() → 增删 → SetQueryParams追加公共查询参数
修改请求体Body() → 转换 → SetBody请求体压缩、签名注入
设置超时SetTimeout(d)按请求路径动态调整超时
设置上下文SetContext(ctx)中间件级超时(TimeoutMiddleware 的工作原理)
go
// 典型:读取已有请求头,追加自定义头后回写
headers := req.Headers()
headers["X-Trace-ID"] = generateTraceID()
req.SetHeaders(headers)

// 等价写法(更简洁)
req.SetHeader("X-Trace-ID", generateTraceID())

响应变更器

读取方法

下列方法读取响应数据。

方法返回类型说明
StatusCode()int状态码
Status()string状态文本(如 "200 OK"
Proto()string协议版本(如 "HTTP/1.1"
Headers()http.Header响应头
Body()string响应体(字符串)
RawBody()[]byte响应体(字节)
ContentLength()int64内容长度
Duration()time.Duration请求耗时
Attempts()int尝试次数(含重试)
Cookies()[]*http.Cookie响应 Cookie
RedirectChain()[]string重定向链(每跳的 URL)
RedirectCount()int重定向次数
RequestHeaders()http.Header实际发送的请求头
RequestURL()string实际请求 URL(含重定向后的最终 URL)
RequestMethod()string请求方法

写入方法

下列方法修改响应数据。

方法说明
SetStatusCode(int)设置状态码
SetStatus(string)设置状态文本
SetProto(string)设置协议版本
SetHeaders(http.Header)设置响应头(整体替换)
SetBody(string)设置响应体
SetRawBody([]byte)设置响应体(字节)
SetContentLength(int64)设置内容长度
SetDuration(time.Duration)设置耗时
SetAttempts(int)设置尝试次数
SetCookies([]*http.Cookie)设置 Cookie
SetRedirectChain([]string)设置重定向链
SetRedirectCount(int)设置重定向次数
SetRequestHeaders(http.Header)设置请求头
SetRequestURL(string)设置请求 URL
SetRequestMethod(string)设置请求方法
SetHeader(key string, values ...string)设置单个响应头(增/改)

ResponseMutator

ResponseMutator 是 httpc 暴露的读写响应变更器接口,涵盖上方「读取方法」与「写入方法」两张表的全部方法。其内部的读/写分体接口位于 internal/types 包,未单独导出,外部统一以 ResponseMutator 引用。中间件在请求完成后经它读取或改写响应,常用于响应缓存、内容转换(如 JSON 美化)、编解码与响应过滤。

中间件中 ResponseMutator 的典型操作

操作场景方法组合说明
读取状态码StatusCode()条件日志、错误分类
读取响应头Headers()提取 X-Request-IDContent-Type
计算指标Duration() + Attempts()上报耗时、重试次数
追踪重定向RedirectChain() + RedirectCount()审计重定向路径
修改响应头SetHeader(key, vals...)追加追踪头、安全头

类型断言:访问引擎特有方法

中间件收到的 RequestMutator 在运行时实际是 *engine.Request 类型(引擎的具体请求结构)。finalHandler 通过类型断言从它读取三个不在接口上的引擎特有钩子。自定义中间件如需访问这些钩子,同样需要类型断言。

接口边界

OnRequest/OnResponse 回调和 AllowPrivateIPs 不在 RequestMutator 接口上——它们的签名引用内部包 engine 的类型(*engine.Request/*engine.Response),暴露到公开接口会造成循环导入。因此只能经 *engine.Request 类型断言访问。

这些引擎特有方法包括:

方法(仅在 *engine.Request 上)说明
OnRequest() func(*engine.Request) error请求发出前回调
OnResponse() func(*engine.Response) error响应收到后回调
AllowPrivateIPs() *bool每请求 SSRF 覆盖
SetOnRequest(func) / SetOnResponse(func)设置回调
SetAllowPrivateIPs(*bool)设置 SSRF 覆盖

绝大多数中间件不需要类型断言——RequestMutator/ResponseMutator 接口已涵盖所有常用读写操作。仅在需要回调或 SSRF 覆盖时才需断言到具体类型。

SanitizedURL 缓存

多个中间件可能都需要记录脱敏 URL(移除凭据信息的 URL)。为避免重复计算,HTTPC 在请求对象上缓存脱敏结果,供同一请求的多个中间件共享。

text
getOrComputeSanitizedURL(req):
  ① req 是否实现了 sanitizedURLer 接口(SanitizedURL/SetSanitizedURL)?
     - *engine.Request 实现了此接口
  ② 已缓存? → 直接返回缓存值
  ③ 未缓存? → 计算 SanitizeURL(req.URL()),缓存后返回

内置的 LoggingMiddlewareMetricsMiddlewareAuditMiddleware 都使用 getOrComputeSanitizedURL 共享脱敏结果,使 URL 脱敏在整个链路中只计算一次。自定义中间件记录 URL 时也应使用此机制,而非直接调用 req.URL()(可能含凭据)。

URL 脱敏

日志/指标中间件中记录 URL 时,切勿直接使用 req.URL()——如果 URL 含有 user:pass@host 形式的凭据,会泄漏到日志中。内置中间件经 getOrComputeSanitizedURL 自动移除凭据部分。

示例:经变更器读写请求响应

一个认证中间件:经 RequestMutatorSetHeader 方法注入认证头,经 ResponseMutatorStatusCode 方法读取响应状态码。

go
package main

import (
	"context"
	"fmt"

	"github.com/cybergodev/httpc"
)

// authMiddleware 经 RequestMutator 注入认证头,并经 ResponseMutator 读取状态码
func authMiddleware(token string) httpc.MiddlewareFunc {
	return func(next httpc.Handler) httpc.Handler {
		return func(ctx context.Context, req httpc.RequestMutator) (httpc.ResponseMutator, error) {
			// 写:经 RequestMutator 设置请求头
			req.SetHeader("Authorization", "Bearer "+token)
			// 读:经 RequestMutator 检查请求方法
			fmt.Printf("发送 %s 请求\n", req.Method())

			resp, err := next(ctx, req)
			if err != nil {
				return nil, err
			}
			// 读:经 ResponseMutator 读取状态码
			fmt.Printf("收到状态码 %d\n", resp.StatusCode())
			return resp, nil
		}
	}
}

func main() {
	cfg := httpc.DefaultConfig()
	cfg.Middleware.Middlewares = []httpc.MiddlewareFunc{
		authMiddleware("my-secret-token"),
	}
	client, err := httpc.New(cfg)
	if err != nil {
		panic(err)
	}
	defer client.Close()

	result, err := client.Get("https://httpbin.org/get")
	if err != nil {
		panic(err)
	}
	fmt.Println(result.IsSuccess())
	// 输出示例:
	// 发送 GET 请求
	// 收到状态码 200
	// true
}

实战示例:请求/响应日志中间件

一个完整的日志中间件,同时展示 RequestMutatorResponseMutator 的读写能力——经变更器读取请求方法/URL 和响应状态码/耗时/重试信息,统一格式化输出。

go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/cybergodev/httpc"
)

// loggingMiddleware 经变更器读取请求与响应的完整信息并格式化输出
func loggingMiddleware() httpc.MiddlewareFunc {
	return func(next httpc.Handler) httpc.Handler {
		return func(ctx context.Context, req httpc.RequestMutator) (httpc.ResponseMutator, error) {
			start := time.Now()

			// 请求阶段:读取请求信息
			log.Printf("[REQ] %s %s", req.Method(), req.URL())

			resp, err := next(ctx, req)
			duration := time.Since(start)

			if err != nil {
				// 错误响应:读取不到状态码
				log.Printf("[ERR] %s %s -> %v (%v)",
					req.Method(), req.URL(), err, duration)
				return nil, err
			}

			// 响应阶段:读取状态码、耗时、重试次数、重定向链
			log.Printf("[RESP] %s %s -> %d (%v, attempts=%d, redirects=%d)",
				req.Method(),
				req.URL(),
				resp.StatusCode(),
				duration,
				resp.Attempts(),
				resp.RedirectCount(),
			)
			return resp, nil
		}
	}
}

func main() {
	cfg := httpc.DefaultConfig()
	cfg.Middleware.Middlewares = []httpc.MiddlewareFunc{
		loggingMiddleware(),
	}
	client, err := httpc.New(cfg)
	if err != nil {
		panic(err)
	}
	defer client.Close()

	result, err := client.Get("https://httpbin.org/get")
	if err != nil {
		panic(err)
	}
	fmt.Println("状态码:", result.StatusCode())
	// 输出示例:
	// [REQ] GET https://httpbin.org/get
	// [RESP] GET https://httpbin.org/get -> 200 (123.456ms, attempts=1, redirects=0)
	// 状态码: 200
}

另见