Skip to content

Handler 与中间件链

双层架构

HTTPC 的请求处理由两层协作完成:Layer 1 的方法 API 是薄封装,真正处理请求的引擎是 Layer 2 的 Handler 管线。每个请求的执行都归结为「组装并执行一条 Handler 链」。

text
HTTPC 双层架构
├── Layer 1  方法 API(薄封装)
│     包级函数 httpc.Get/Post/... + Client 方法 + 请求选项
│     → 内部统一走 client.Request → executeRequest

└── Layer 2  Handler 管线(请求处理引擎)
      clientImpl.middlewareChain = Chain(middlewares...)(finalHandler)
      MiddlewareFunc(Handler) 洋葱链 → 组装 → 执行

当客户端配置了中间件时,executeRequest 会把请求选项应用到一个 RequestMutator 上,再交由 clientImpl.middlewareChain 执行;若未配置中间件,则直接走引擎发请求。这条链就是 buildMiddlewareChainNew() 时一次性组装、缓存在 clientImpl.middlewareChain 字段上的 Handler。

Handler

go
type Handler func(ctx context.Context, req RequestMutator) (ResponseMutator, error)

请求处理的核心函数签名。接收上下文与请求变更器,返回响应变更器或错误。链的末端 Handler(finalHandler)负责把中间件改写后的请求字段转发给底层引擎真正发出网络请求。

MiddlewareFunc

go
type MiddlewareFunc func(Handler) Handler

中间件函数签名,接收「下一个 Handler」并返回包装后的 Handler。中间件可在调用 next 前后插入逻辑(改写请求、记录响应、捕获 panic 等),形成洋葱模型:第一个中间件最外层、最先进入最后退出。

text
请求进入方向 →

[Middleware A] → [Middleware B] → [Middleware C] → finalHandler → 引擎

响应返回方向 ←   ←   ←   ←   ←   ←   ←   ←   ←   ←   ←   ←   ←   ↓

中间件在 MiddlewareConfig.Middlewares 中按切片顺序配置,切片中靠前的中间件处于链的外层

Chain

go
func Chain(middlewares ...MiddlewareFunc) MiddlewareFunc

将多个中间件组合为单个中间件。返回的组合器接收最终 Handler,按传入顺序从外到内嵌套:切片第一个中间件包在最外层(最先执行),最后一个紧贴最终 Handler。HTTPC 内部正是用它把 MiddlewareConfig.Middlewares 组装成链。

go
// 三段等价:Chain 组合后一次性注入,与逐层手动嵌套结果相同
combined := httpc.Chain(mwA, mwB, mwC)
chain := combined(finalHandler)

// 等价于手动嵌套
chain := mwA(mwB(mwC(finalHandler)))

自定义中间件示例

一个完整的计时中间件:在请求前后记录耗时,通过 MiddlewareConfig.Middlewares 注入客户端。

go
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/cybergodev/httpc"
)

// timingMiddleware 记录每个请求的耗时
func timingMiddleware() httpc.MiddlewareFunc {
	return func(next httpc.Handler) httpc.Handler {
		return func(ctx context.Context, req httpc.RequestMutator) (httpc.ResponseMutator, error) {
			start := time.Now()
			// 调用下一个 Handler,请求继续沿链传递
			resp, err := next(ctx, req)
			fmt.Printf("%s %s -> 耗时 %v\n", req.Method(), req.URL(), time.Since(start))
			return resp, err
		}
	}
}

func main() {
	client, err := httpc.New(&httpc.Config{
		Middleware: &httpc.MiddlewareConfig{
			Middlewares: []httpc.MiddlewareFunc{
				timingMiddleware(),
			},
		},
	})
	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())
	// 输出示例:
	// GET https://httpbin.org/get -> 耗时 123.456ms
	// 200
}

TIP

中间件返回的 resp 必须原样(或经后续 next)传回,否则会泄漏引擎对象池中的响应。返回 (nil, error) 且持有未释放响应会导致池泄漏。

另见