英文:
Http Server Read-Write timeouts and Server Side Events
问题
我正在使用SSE编写一个测试应用程序,但我的问题是ReadTimeout和WriteTimeout每10秒关闭一次客户端连接,因此主页面会丢失数据。
我该如何处理这个问题,同时提供SSE和网页服务,而不会有goroutine泄漏的风险,并且SSE能正常工作?
服务器:
server := &http.Server{
Addr: addr,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
Handler: s.mainHandler(),
}
处理程序:
func sseHandler(w http.ResponseWriter, r *http.Request) {
f, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming not supported!", http.StatusInternalServerError)
log.Println("Streaming not supported")
return
}
messageChannel := make(chan string)
hub.addClient <- messageChannel
notify := w.(http.CloseNotifier).CloseNotify()
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
for i := 0; i < 1440; {
select {
case msg := <-messageChannel:
jsonData, _ := json.Marshal(msg)
str := string(jsonData)
fmt.Fprintf(w, "data: {\"str\": %s, \"time\": \"%v\"}\n\n", str, time.Now())
f.Flush()
case <-time.After(time.Second * 60):
fmt.Fprintf(w, "data: {\"str\": \"No Data\"}\n\n")
f.Flush()
i++
case <-notify:
f.Flush()
i = 1440
hub.removeClient <- messageChannel
}
}
}
以上是你提供的代码的翻译。
英文:
I'm writing a test app with SSE, but my problem is that
ReadTimeout and WriteTimeout are closing the clients connection every 10 Seconds and because of this the main page are losing data.
How can I manage this Issue, serving SSE and webpages together without goroutines leak risk and SSE working done?
Server:
server := &http.Server{Addr: addr,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
Handler: s.mainHandler(),
}
Handler:
func sseHandler(w http.ResponseWriter, r *http.Requests) {
f, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming not supported!", http.StatusInternalServerError)
log.Println("Streaming not supported")
return
}
messageChannel := make(chan string)
hub.addClient <- messageChannel
notify := w.(http.CloseNotifier).CloseNotify()
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
for i := 0; i < 1440; {
select {
case msg := <-messageChannel:
jsonData, _ := json.Marshal(msg)
str := string(jsonData)
fmt.Fprintf(w, "data: {\"str\": %s, \"time\": \"%v\"}\n\n", str, time.Now())
f.Flush()
case <-time.After(time.Second * 60):
fmt.Fprintf(w, "data: {\"str\": \"No Data\"}\n\n")
f.Flush()
i++
case <-notify:
f.Flush()
i = 1440
hub.removeClient <- messageChannel
}
}
}
答案1
得分: 5
ReadTimeout
和WriteTimeout
都定义了从客户端读取整个请求或将其写回客户端的持续时间。这些超时适用于底层连接(http://golang.org/src/pkg/net/http/server.go?s=15704:15902),在接收任何标头之前应用,因此您无法为单独的处理程序设置不同的限制-服务器中的所有连接将共享相同的超时限制。
说到这一点,如果您需要每个请求的自定义超时,您需要在处理程序中实现它们。在您的代码中,您已经在使用超时来处理作业,所以这只是在处理程序开始时创建一个time.After
,在处理程序本身中不断检查(甚至传递它)并在必要时停止请求。这实际上可以更精确地控制超时,因为WriteTimeout
只会在尝试写入响应时触发(例如,如果超时设置为10秒,并且在任何写入之前准备响应需要一分钟,那么在作业完成之前您不会收到任何错误,因此您将浪费50秒的资源)。从这个角度来看,我认为WriteTimeout
本身只有在响应非常快速准备好,但是当网络变得非常慢(或客户端故意停止接收数据)时,您想要断开连接时才有用。
库中有一个小助手函数http.TimeoutHandler
,其行为类似于WriteTimeout
,但如果响应时间超过预定义时间,则返回503错误。您可以使用它来为每个处理程序设置类似于WriteTimeout
的行为,例如:
package main
import (
"log"
"net/http"
"time"
)
type Handler struct {
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
time.Sleep(3*time.Second)
// This will return http.ErrHandlerTimeout
log.Println(w.Write([]byte("body")))
}
func main() {
h := &Handler{}
http.Handle("/t1", http.TimeoutHandler(h, 1*time.Second, ""))
http.Handle("/t2", http.TimeoutHandler(h, 2*time.Second, ""))
http.ListenAndServe(":8080", nil)
}
这看起来非常方便,但我发现一个会影响您的代码的缺点:从http.TimeoutHandler
传递的http.ResponseWriter
不实现http.CloseNotifier
。如果需要,您可以深入研究实现,看看他们是如何解决超时问题以提出类似但增强的解决方案的。
英文:
Both ReadTimeout
and WriteTimeout
define the duration within which the whole request must be read from or written back to the client. These timeouts are applied to the underlying connection (http://golang.org/src/pkg/net/http/server.go?s=15704:15902) and this is before any headers are received, so you cannot set different limits for separate handlers – all connections within the server will share the same timeout limits.
Saying this, if you need customised timeouts per request, you will need to implement them in your handler. In your code you're already using timeouts for your job, so this would be a matter of creating a time.After
at the beginning of the handler, keep checking (in the handler itself or even pass it around) and stop the request when necessary. This actually gives you more precise control over the timeout, as WriteTimeout
would only trigger when trying to write the response (e.g. if the timeout is set to 10 seconds and it takes a minute to prepare the response before any write, you won't get any error until the job is done so you'll waste resources for 50 seconds). From this perspective, I think WriteTimeout
itself is good only when your response is ready quite quickly, but you want to drop the connection when network become very slow (or the client deliberately stops receiving data).
There is a little helper function in the library, http.TimeoutHandler
, which behaves similarly to WriteTimeout
, but sends back 503 error if the response takes longer than predefined time. You could use it to set up behaviour similar to WriteTimeout
per handler, for example:
package main
import (
"log"
"net/http"
"time"
)
type Handler struct {
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
time.Sleep(3*time.Second)
// This will return http.ErrHandlerTimeout
log.Println(w.Write([]byte("body")))
}
func main() {
h := &Handler{}
http.Handle("/t1", http.TimeoutHandler(h, 1*time.Second, ""))
http.Handle("/t2", http.TimeoutHandler(h, 2*time.Second, ""))
http.ListenAndServe(":8080", nil)
}
This looks very handy, but I found one disadvantage that would affect your code: http.ResponseWriter
passed from http.TimeoutHandler
doesn't implement http.CloseNotifier
. If it's required, you could dive into the implementation and see how they solved the timeout problem in order to come up with a similar, but enhanced solution.
答案2
得分: 0
HTTP处理程序超时是在每个服务器上指定的:ReadTimeout
,WriteTimeout
。在Go 1.20
之后,HTTP ResponseController提供了对扩展的每个请求功能的访问,这些功能不由http.ResponseWriter
接口处理。
ResponseController类型提供了一种更清晰、更易于发现的方式来添加每个处理程序的控制。在Go 1.20中还添加了两个这样的控制:
SetReadDeadline
和SetWriteDeadline
,它们允许设置每个请求的读取和写入截止时间。
示例
func exampleHandler(w http.ResponseWriter, r *http.Request) {
rc := http.NewResponseController(w)
// 在5秒后设置写入截止时间。
err := rc.SetWriteDeadline(time.Now().Add(5 * time.Second))
if err != nil {
}
// 一些工作...
// 写入正常响应。
w.Write([]byte("something"))
}
英文:
HTTP handler timeouts are specified on a per-Server basis: ReadTimeout
, WriteTimeout
. After Go 1.20
, the HTTP ResponseController provides access to extended per-request functionality not handled by the http.ResponseWriter
interface.
> The ResponseController type provides a clearer, more discoverable way to add per-handler controls. Two such controls also added in Go 1.20 are SetReadDeadline
and SetWriteDeadline
, which allow setting per-request read and write deadlines.
Sample
func exampleHandler(w http.ResponseWriter, r *http.Request) {
rc := http.NewResponseController(w)
// Set a write deadline in 5 seconds time.
err := rc.SetWriteDeadline(time.Now().Add(5 * time.Second))
if err != nil {
}
// Some job ...
// Write the normal response.
w.Write([]byte("something"))
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论