2014-11-24 121 views
1

我正在用SSE編寫測試應用程序,但我的問題是 ReadTimeout和WriteTimeout正在每隔10秒關閉客戶端連接,因此主頁面正在丟失數據。Http服務器讀寫超時和服務器端事件

我該如何管理這個問題,一起服務SSE和網頁,而沒有goroutines泄漏風險和SSE工作?

服務器:

server := &http.Server{Addr: addr, 
    ReadTimeout: 10 * time.Second, 
    WriteTimeout: 10 * time.Second, 
    Handler:  s.mainHandler(), 
} 

處理程序:

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 
     } 
    } 
} 
+3

爲什麼不增加/禁用超時?你需要每個處理程序單獨的超時? – tomasz 2014-11-24 14:07:46

+0

@tomasz可能很棒有一個不同的超時處理程序,我不知道是否有可能,也許有類似的情況下,我的一些最佳做法,我真的不知道它呢... – Mmeyer 2014-11-24 15:04:03

回答

2

兩個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.ResponseWriterhttp.TimeoutHandler傳遞沒有實現http.CloseNotifier。如果需要,您可以深入探索implementation,看看他們如何解決超時問題,以便提出類似的增強型解決方案。