2

我目前正在维护一些基于标准库和 gorilla mux 并在 kubernetes (GKE) 中运行的 HTTP API。

我们采用http.TimeoutHandler“标准”方式来进行一致的超时错误管理。典型的端点实现将使用以下“链”:

MonitoringMiddleware => TimeoutMiddleware => … => handler   

这样我们就可以监控每个端点的一些关键指标。

我们的一个 API 通常用于“即发即弃”模式,这意味着客户端将推送一些数据而不关心 API 响应。我们面临的问题是

  1. 当客户端连接不再活动时,Golang 标准 HTTP 服务器将取消请求上下文(godoc
  2. TimeoutHandler只要请求上下文完成 ,就会返回一个“超时”响应(参见代码

这意味着当客户端断开连接时我们不会处理完成的请求,这不是我们想要的,因此我正在寻找解决方案。

我能找到的唯一与我的问题有关的讨论是https://github.com/golang/go/issues/18527;然而

Handler解决方法是您的应用程序可以忽略Request.Context()

将意味着监控中间件不会报告“正确”状态,因为Handler它将在其 goroutine 中执行请求处理,但TimeoutHandler将强制执行状态并且可观察性将被破坏。

目前,我不考虑删除我们的中间件,因为它们有助于我们的 API 在行为和可观察性方面保持一致性。到目前为止,我的结论是,TimeoutHandler当处理程序不应依赖于等待响应的客户端时,我需要“分叉”并使用自定义上下文。

我目前想法的要点是:

type TimeoutHandler struct {
    handler Handler
    body    string
    dt      time.Duration

    // BaseContext optionally specifies a function that returns
    // the base context for controling if the server request processing.
    // If BaseContext is nil, the default is req.Context().
    // If non-nil, it must return a non-nil context.
    BaseContext func(*http.Request) context.Context
}
func (h *TimeoutHandler) ServeHTTP(w ResponseWriter, r *Request) {
    reqCtx := r.Context()
    if h.BaseContext != nil {
        reqCtx = h.BaseContext(r)
    }
    
    ctx, cancelCtx := context.WithTimeout(reqCtx, h.dt)
    defer cancelCtx()
    
    r = r.WithContext(ctx)
   ...
    case <-reqCtx.Done():
        tw.mu.Lock()
        defer tw.mu.Unlock()
        w.WriteHeader(499) // write status for monitoring;
        // no need to write a body since no client is listening.
    
    case <-ctx.Done():
        tw.mu.Lock()
        defer tw.mu.Unlock()
        w.WriteHeader(StatusServiceUnavailable)
        io.WriteString(w, h.errorBody())
        tw.timedOut = true
    }

中间件BaseContext回调将返回context.Background() 对“即发即弃”端点的请求。

我不喜欢的一件事是,这样做我会丢失任何编写的上下文键,因此这个新的中间件会有很强的使用限制。总的来说,我觉得这比它应该的要复杂。

我完全错过了一些明显的东西吗?欢迎任何关于 API 检测(也许我们的中间件反模式)/fire and forget 实现的反馈!

编辑:由于大多数评论是客户端不等待响应的请求具有未指定的行为,因此我检查了有关发生这种情况的典型客户端的更多信息。

从我们的日志来看,这发生在看似移动设备的用户代理上。我可以想象连接可能会更加不稳定,问题可能不会消失。因此,我不会断定我不应该找到解决方案,因为这目前正在创建误报警报。

4

0 回答 0