Http服务器的读写超时和服务器端事件

huangapple go评论76阅读模式
英文:

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 := &amp;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, &quot;Streaming not supported!&quot;, http.StatusInternalServerError)
		log.Println(&quot;Streaming not supported&quot;)
		return
	}
	messageChannel := make(chan string)
	hub.addClient &lt;- messageChannel
	notify := w.(http.CloseNotifier).CloseNotify()

	w.Header().Set(&quot;Content-Type&quot;, &quot;text/event-stream&quot;)
	w.Header().Set(&quot;Cache-Control&quot;, &quot;no-cache&quot;)
	w.Header().Set(&quot;Connection&quot;, &quot;keep-alive&quot;)
	for i := 0; i &lt; 1440; {
		select {
		case msg := &lt;-messageChannel:
			jsonData, _ := json.Marshal(msg)
			str := string(jsonData)
			fmt.Fprintf(w, &quot;data: {\&quot;str\&quot;: %s, \&quot;time\&quot;: \&quot;%v\&quot;}\n\n&quot;, str, time.Now())
			f.Flush()

		case &lt;-time.After(time.Second * 60):
			fmt.Fprintf(w, &quot;data: {\&quot;str\&quot;: \&quot;No Data\&quot;}\n\n&quot;)

			f.Flush()
			i++

		case &lt;-notify:
			f.Flush()
			i = 1440
			hub.removeClient &lt;- messageChannel
		}
	}
}

答案1

得分: 5

ReadTimeoutWriteTimeout都定义了从客户端读取整个请求或将其写回客户端的持续时间。这些超时适用于底层连接(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 (
	&quot;log&quot;	
	&quot;net/http&quot;
	&quot;time&quot;
)

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(&quot;body&quot;)))
}

func main() {
	h := &amp;Handler{}
	http.Handle(&quot;/t1&quot;, http.TimeoutHandler(h, 1*time.Second, &quot;&quot;))
	http.Handle(&quot;/t2&quot;, http.TimeoutHandler(h, 2*time.Second, &quot;&quot;))	
	http.ListenAndServe(&quot;:8080&quot;, 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处理程序超时是在每个服务器上指定的:ReadTimeoutWriteTimeout。在Go 1.20之后,HTTP ResponseController提供了对扩展的每个请求功能的访问,这些功能不由http.ResponseWriter接口处理。

ResponseController类型提供了一种更清晰、更易于发现的方式来添加每个处理程序的控制。在Go 1.20中还添加了两个这样的控制:SetReadDeadlineSetWriteDeadline,它们允许设置每个请求的读取和写入截止时间。

示例

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(&quot;something&quot;))
}

huangapple
  • 本文由 发表于 2014年11月24日 10:37:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/27097084.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定