Golang中如何清理HTTP处理程序的资源

Golang中如何清理HTTP处理程序的资源 我是Go语言的新手。我使用了一些基于Go的http包的处理程序。Go版本是1.6.x。在处理程序执行之前,有一些过滤器操作,可能会执行诸如速率限制之类的操作。速率限制会进行漏桶检查,因此有一个类似列表的队列,每个处理程序都会检查并将其时间戳注册到Redis中。

现在我知道,如[1]所述,每个处理程序都由Go协程处理。因此我在想,当处理程序完成其执行后,我可以从Redis中清理其相关信息。然而,我在网上查阅后,没有找到文档说明如何执行后置操作。

到目前为止,我找到的唯一信息是[2],其中似乎可以通过启动一个Go协程来使用通道接收通知以进行清理。这是唯一的方法吗?如果是这样,我似乎需要在我所有的端点/处理程序中插入这样的清理函数。如果有100个端点/处理程序,我需要在所有这些处理程序/端点的末尾插入100行代码。有更好的方法吗?谢谢

[1]. https://stackoverflow.com/questions/34386232/why-isnt-this-go-http-server-spawning-a-goroutine-per-request-in-chrome-47/34386358#34386358 [2]. https://gianarb.it/blog/go-http-cleanup-http-connection-terminated


更多关于Golang中如何清理HTTP处理程序的资源的实战教程也可以访问 https://www.itying.com/category-94-b0.html

5 回复

在处理器执行之前,有一些过滤操作可能会执行诸如速率限制之类的操作。

你是如何在处理器执行之前执行这些操作的?如果是通过某种包装器包裹 goroutine 的方式,你能否在处理器被调用后,添加一个调用来清理 Redis 中的相关数据?

更多关于Golang中如何清理HTTP处理程序的资源的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我不知道这些处理器资源是如何创建的,但通常这是中间件模式的经典案例:

func middleware(handler http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("Pre-Hooks")
		handler(w,r)
		fmt.Println("Post-Hooks")
	}
}

除了使用Redis,还有另一种方法可以实现此功能,例如令牌桶算法。你可以使用这个包:

https://github.com/didip/tollbooth

或者,一个更简单但需要手动实现的方案,可以参考这里: https://www.alexedwards.net/blog/how-to-rate-limit-http-requests

如果有一百个端点/处理器,我需要在所有这些处理器/端点的末尾插入一百行代码。有没有更好的方法来实现这一点?谢谢

在我的网站上,我使用了一种方法。可以说是动态的方式。这个方法已经为一个静态网站服务了几年。端点变量是根据 r.URL.Path 创建的,如果没有对应的 HTML 页面,它会返回一个 404 页面。Web 服务器 这段代码服务大约 50 个端点。

func main() {
    fmt.Println("hello world")
}

在Go中清理HTTP处理程序的资源,可以通过中间件模式统一处理。以下是一个示例,展示如何创建中间件来执行后置清理操作:

package main

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

// 模拟Redis清理函数
func cleanupFromRedis(requestID string) {
    fmt.Printf("清理Redis中的请求记录: %s\n", requestID)
    // 这里实现实际的Redis清理逻辑
}

// 清理中间件
func cleanupMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 生成请求ID用于跟踪
        requestID := fmt.Sprintf("%d", time.Now().UnixNano())
        
        // 创建自定义ResponseWriter来捕获状态码
        rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
        
        // 在处理程序执行前可以执行预处理
        fmt.Printf("开始处理请求: %s\n", requestID)
        
        // 调用下一个处理程序
        next.ServeHTTP(rw, r)
        
        // 在处理程序执行后执行清理
        cleanupFromRedis(requestID)
        fmt.Printf("完成清理请求: %s\n", requestID)
    })
}

// 自定义ResponseWriter
type responseWriter struct {
    http.ResponseWriter
    statusCode int
}

func (rw *responseWriter) WriteHeader(code int) {
    rw.statusCode = code
    rw.ResponseWriter.WriteHeader(code)
}

// 示例处理程序
func helloHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello, World!"))
}

// 另一个处理程序示例
func apiHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("API Response"))
}

func main() {
    // 创建路由器
    mux := http.NewServeMux()
    
    // 注册处理程序
    mux.HandleFunc("/hello", helloHandler)
    mux.HandleFunc("/api", apiHandler)
    
    // 包装路由器使用清理中间件
    handler := cleanupMiddleware(mux)
    
    // 启动服务器
    fmt.Println("服务器启动在 :8080")
    http.ListenAndServe(":8080", handler)
}

对于更复杂的场景,可以使用context来传递清理函数:

package main

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

// 清理函数类型
type CleanupFunc func()

// 上下文键类型
type contextKey string

const cleanupKey contextKey = "cleanupFunc"

// 带清理功能的中间件
func withCleanup(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 创建清理函数列表
        var cleanupFuncs []CleanupFunc
        
        // 将清理函数添加到上下文
        ctx := context.WithValue(r.Context(), cleanupKey, &cleanupFuncs)
        
        // 创建自定义ResponseWriter
        rw := &trackingResponseWriter{
            ResponseWriter: w,
            statusCode:     http.StatusOK,
        }
        
        // 调用处理程序
        next.ServeHTTP(rw, r.WithContext(ctx))
        
        // 执行所有清理函数
        if funcs, ok := ctx.Value(cleanupKey).(*[]CleanupFunc); ok {
            for _, cleanup := range *funcs {
                cleanup()
            }
        }
    })
}

// 注册清理函数
func registerCleanup(r *http.Request, cleanup CleanupFunc) {
    if funcs, ok := r.Context().Value(cleanupKey).(*[]CleanupFunc); ok {
        *funcs = append(*funcs, cleanup)
    }
}

// 跟踪ResponseWriter
type trackingResponseWriter struct {
    http.ResponseWriter
    statusCode int
    written    bool
}

func (rw *trackingResponseWriter) WriteHeader(code int) {
    if !rw.written {
        rw.statusCode = code
        rw.written = true
        rw.ResponseWriter.WriteHeader(code)
    }
}

func (rw *trackingResponseWriter) Write(b []byte) (int, error) {
    if !rw.written {
        rw.WriteHeader(http.StatusOK)
    }
    return rw.ResponseWriter.Write(b)
}

// 示例处理程序使用清理注册
func rateLimitedHandler(w http.ResponseWriter, r *http.Request) {
    // 模拟速率限制检查
    requestID := fmt.Sprintf("req_%d", time.Now().UnixNano())
    
    // 注册Redis清理函数
    registerCleanup(r, func() {
        fmt.Printf("清理Redis记录: %s\n", requestID)
        // 实际Redis清理逻辑
    })
    
    // 注册其他资源清理
    registerCleanup(r, func() {
        fmt.Printf("清理其他资源: %s\n", requestID)
    })
    
    w.Write([]byte("处理完成"))
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/limited", rateLimitedHandler)
    
    handler := withCleanup(mux)
    
    fmt.Println("服务器启动在 :8080")
    http.ListenAndServe(":8080", handler)
}

对于需要确保清理操作执行的情况,可以使用defer在中间件中处理:

func ensureCleanupMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 使用defer确保清理函数执行
        defer func() {
            if r := recover(); r != nil {
                // 即使发生panic也执行清理
                cleanupFromRedis("panic_recovery")
                panic(r) // 重新抛出panic
            }
        }()
        
        // 正常处理流程
        next.ServeHTTP(w, r)
        
        // 正常清理
        cleanupFromRedis("normal_completion")
    })
}

这些方法避免了在每个处理程序中重复编写清理代码,通过中间件统一管理资源清理。

回到顶部