You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Golang中封装net/http中间件并实现自定义Context的ctx.Next()?

实现自定义Context的Next()方法与net/http中间件集成

问题描述

我正尝试为Context构建一个接口,并为中间件实现ctx.Next()方法。预期的中间件形式如下:

func Test() http.HandlerFunc {
    return func(ctx http.Context) error {
        return ctx.Next()
    }
}

我尝试了以下代码,但无法正常运行:

func (c *Context) Next() error {
    // body limit middleware
    nextHandler := func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            next.ServeHTTP(w, r)
        })
    }
        // NOT SURE how to add handler and execute nextHandler
    return nil
}

需要指点如何在ctx.Next()中实现net/http中间件的逻辑。


核心思路

自定义Context的Next()方法本质是推进中间件/handler链的执行,而非直接包装标准库的http.Handler。需要先定义自定义的Handler类型,让中间件和业务逻辑都基于这个类型构建,再通过适配层对接标准库的net/http。


具体实现步骤

1. 定义自定义Context与Handler类型

首先明确自定义Context的核心结构,需要维护handler链和当前执行索引:

// 自定义Context接口,扩展标准库http.Context
type Context interface {
    http.Context
    Next() error
    ResponseWriter() http.ResponseWriter
    Request() *http.Request
}

// 自定义Handler类型,匹配预期的中间件签名
type Handler func(ctx Context) error

// Context的具体实现结构体
type context struct {
    http.Context
    w        http.ResponseWriter
    r        *http.Request
    handlers []Handler // 存储中间件+业务handler的执行链
    index    int       // 当前执行到的handler索引
}

2. 实现Next()方法

Next()的核心逻辑是移动索引,执行链中的下一个handler:

func (c *context) Next() error {
    c.index++
    if c.index < len(c.handlers) {
        // 执行下一个handler,并返回其错误
        return c.handlers[c.index](c)
    }
    return nil // 链执行完毕,返回nil
}

// 实现Context接口的其他必要方法
func (c *context) ResponseWriter() http.ResponseWriter {
    return c.w
}

func (c *context) Request() *http.Request {
    return c.r
}

3. 对接标准库net/http

需要一个包装函数,把自定义Handler链转换成标准库的http.Handler,完成请求入口的转换:

// 将自定义Handler包装为标准库http.Handler
func WrapHandler(handler Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 初始化自定义Context,传入标准库的请求上下文
        ctx := &context{
            Context:  r.Context(),
            w:        w,
            r:        r,
            handlers: []Handler{handler},
            index:    -1, // 初始索引设为-1,第一次Next()会从0开始执行
        }

        // 启动handler链,处理可能的错误
        if err := ctx.Next(); err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    })
}

4. 编写中间件与业务Handler

现在可以按照预期形式编写中间件:

// 示例:日志中间件
func LogMiddleware() func(Handler) Handler {
    return func(next Handler) Handler {
        return func(ctx Context) error {
            start := time.Now()
            // 执行下一个handler
            err := ctx.Next()
            // 后续处理:记录请求耗时
            log.Printf("请求路径: %s, 耗时: %v", ctx.Request().URL.Path, time.Since(start))
            return err
        }
    }
}

// 示例:业务Handler
func HelloHandler(ctx Context) error {
    ctx.ResponseWriter().Write([]byte("Hello World"))
    return nil
}

5. 组装并运行服务

将中间件和业务Handler组装成链,再通过WrapHandler对接标准库路由:

func main() {
    // 构建handler链:日志中间件 -> HelloHandler
    handler := LogMiddleware()(HelloHandler)
    // 注册到标准库路由
    http.Handle("/", WrapHandler(handler))
    // 启动服务
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

兼容标准库http.Handler

如果需要集成已有的标准库http.Handler,可以编写转换函数:

// 将标准库http.Handler转换为自定义Handler
func WrapStdHandler(h http.Handler) Handler {
    return func(ctx Context) error {
        h.ServeHTTP(ctx.ResponseWriter(), ctx.Request())
        return nil
    }
}

示例用法(集成静态文件服务):

fileHandler := WrapStdHandler(http.FileServer(http.Dir("./static")))
http.Handle("/static/", WrapHandler(LogMiddleware()(fileHandler)))

内容的提问来源于stack exchange,提问作者user9462447

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 01:11:07