go-zero
go-zero copied to clipboard
Failed on streaming response when timeout enabled.
timeouthandler开启时会导致streaming-response失败
1、无法再写入新的header头,handler函数中无法通过w.Header()获取到中间件设置的响应头
解决方法
将rest/handler/timeouthandler.go 71行代码 h: make(http.Header),
改为 h: w.Header(),
2、无法流式写入响应,需要修改Write相关function
无法流式写入响应,需要修改Write相关function 将rest/handler/timeouthandler.go 方法
func (tw *timeoutWriter) Flush() {
if flusher, ok := tw.w.(http.Flusher); ok {
flusher.Flush()
}
}
修改为
func (tw *timeoutWriter) Flush() {
if flusher, ok := tw.w.(http.Flusher); ok {
tw.w.Write(tw.wbuf.Bytes())
flusher.Flush()
}
}
The code is looking like below, it should meet your requirements. Please check.
// Flush implements the Flusher interface.
func (tw *timeoutWriter) Flush() {
flusher, ok := tw.w.(http.Flusher)
if !ok {
return
}
header := tw.w.Header()
for k, v := range tw.h {
header[k] = v
}
tw.w.Write(tw.wbuf.Bytes())
tw.wbuf.Reset()
flusher.Flush()
}
https://github.com/zeromicro/go-zero/blob/a2e703c53eaaccc82673a8a8e499fae5a3749843/rest/handler/timeouthandler.go#L134
The code is looking like below, it should meet your requirements. Please check.
// Flush implements the Flusher interface. func (tw *timeoutWriter) Flush() { flusher, ok := tw.w.(http.Flusher) if !ok { return } header := tw.w.Header() for k, v := range tw.h { header[k] = v } tw.w.Write(tw.wbuf.Bytes()) tw.wbuf.Reset() flusher.Flush() }https://github.com/zeromicro/go-zero/blob/a2e703c53eaaccc82673a8a8e499fae5a3749843/rest/handler/timeouthandler.go#L134
This actually doesn't solve my needs. I can tell you more about the problems I found, because
func (h *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Header.Get(headerUpgrade) == valueWebsocket {
h.handler.ServeHTTP(w, r)
return
}
ctx, cancelCtx := context.WithTimeout(r.Context(), h.dt)
The timeout context is set in h.dt after a specific time interval, the connection must be disconnected. Even if the server's streaming response is not over, it will be disconnected.
In order to solve this problem, I changed the timeout context to a resettable timer, which will trigger the reset action when the Flush method is called.
Changing timeouthandler.go alone is not enough, because this is set in the engine.go part, this is the source code
func (ng *engine) withTimeout() internal.StartOption {
return func(svr *http.Server) {
timeout := ng.timeout
if timeout > 0 {
// factor 0.8, to avoid clients send longer content-length than the actual content,
// without this timeout setting, the server will time out and respond 503 Service Unavailable,
// which triggers the circuit breaker.
svr.ReadTimeout = 4 * timeout / 5
// factor 1.1, to avoid servers don't have enough time to write responses.
// setting the factor less than 1.0 may lead clients not receiving the responses.
svr.WriteTimeout = 11 * timeout / 10
}
}
}
The write timeout is also set here, which will have a higher priority than the timeouthandler. My personal suggestion is that the engine does not enable the write timeout configuration when the timeouthandler middleware is enabled. All the pr codes I submitted have been verified the pr i submitted
This issue is stale because it has been open for 30 days with no activity.
This issue was closed because it has been inactive for 14 days since being marked as stale.