http.StripPrefix
http.StripPrefix
?是 Go 語言?net/http
?包中的一個函數,它的主要作用是創建一個新的 HTTP 處理程序。這個新處理程序會在處理請求之前,從請求的 URL 路徑中移除指定的前綴,然后將處理工作委托給另一個提供的處理程序。
使用示例
package mainimport ("fmt""net/http"
)// 定義一個簡單的處理函數
func simpleHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "Path after stripping prefix: %s", r.URL.Path)
}func main() {// 創建一個處理程序,移除 "/static/" 前綴后將請求交給 simpleHandler 處理http.Handle("/static/", http.StripPrefix("/static/", http.HandlerFunc(simpleHandler)))// 啟動 HTTP 服務器,監聽 8080 端口fmt.Println("Starting server on :8080")http.ListenAndServe(":8080", nil)
}
在這個例子中,如果客戶端請求?/static/css/style.css
,http.StripPrefix
?會移除?/static/
?前綴,將?/css/style.css
?作為路徑傳遞給?simpleHandler
?處理。
源碼分析
http.StripPrefix
?函數的定義如下:
// StripPrefix returns a handler that serves HTTP requests
// by removing the given prefix from the request URL's Path
// and invoking the handler h. StripPrefix handles a
// request for a path that doesn't begin with prefix by
// replying with an HTTP 404 not found error.
func StripPrefix(prefix string, h Handler) Handler {if prefix == "" {return h}return HandlerFunc(func(w ResponseWriter, r *Request) {if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {r2 := new(Request)*r2 = *rr2.URL = new(url.URL)*r2.URL = *r.URLr2.URL.Path = ph.ServeHTTP(w, r2)} else {http.NotFound(w, r)}})
}
源碼詳細解釋
-
參數檢查:
?if prefix == "" {return h }
如果傳入的前綴?
prefix
?為空字符串,那么直接返回原處理程序?h
,因為不需要移除任何前綴。 -
創建新的處理程序:
?return HandlerFunc(func(w ResponseWriter, r *Request) {// ... })
使用?
HandlerFunc
?類型創建一個新的處理程序。HandlerFunc
?是一個函數類型,它實現了?http.Handler
?接口的?ServeHTTP
?方法。 -
移除前綴并處理請求:
?if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {r2 := new(Request)*r2 = *rr2.URL = new(url.URL)*r2.URL = *r.URLr2.URL.Path = ph.ServeHTTP(w, r2) }
- 使用?
strings.TrimPrefix
?函數嘗試從請求的 URL 路徑中移除指定的前綴。 - 如果移除成功(即移除后的路徑長度小于原路徑長度),則創建一個新的?
http.Request
?對象?r2
,并復制原請求的所有信息。 - 修改新請求對象?
r2
?的 URL 路徑為移除前綴后的路徑。 - 調用原處理程序?
h
?的?ServeHTTP
?方法,將新請求對象?r2
?傳遞給它進行處理。
- 使用?
-
處理未匹配的請求:
?} else {http.NotFound(w, r) }
如果請求的 URL 路徑不包含指定的前綴,那么調用?
http.NotFound
?函數返回一個 404 錯誤響應。
http.StripPrefix
?是一個非常實用的工具,它允許你在處理請求之前對 URL 路徑進行預處理,移除不必要的前綴。這在處理靜態文件服務、API 路由等場景中非常有用。通過分析源碼,我們可以看到它是如何創建新的請求對象、修改路徑并將處理工作委托給原處理程序的,同時也處理了未匹配前綴的情況。
http.TimeoutHandler?
http.TimeoutHandler
?是 Go 語言?net/http
?包中的一個函數,它用于為 HTTP 請求處理設置超時時間。當一個請求的處理時間超過預設的超時時間時,會返回一個超時響應給客戶端,避免客戶端長時間等待無響應的請求。
使用示例
package mainimport ("fmt""net/http""time"
)// 模擬一個耗時的處理函數
func slowHandler(w http.ResponseWriter, r *http.Request) {time.Sleep(5 * time.Second)fmt.Fprint(w, "Slow handler completed")
}func main() {// 設置超時時間為 2 秒timeoutHandler := http.TimeoutHandler(http.HandlerFunc(slowHandler), 2*time.Second, "Request timed out")// 注冊處理程序http.Handle("/slow", timeoutHandler)// 啟動 HTTP 服務器fmt.Println("Starting server on :8080")if err := http.ListenAndServe(":8080", nil); err != nil {fmt.Println("Error starting server:", err)}
}
在這個示例中,slowHandler
?函數模擬了一個耗時 5 秒的處理過程,而?http.TimeoutHandler
?設置的超時時間為 2 秒。當客戶端請求?/slow
?路徑時,如果處理時間超過 2 秒,客戶端將收到 "Request timed out" 的響應。
源碼分析
// TimeoutHandler returns a Handler that runs h with the given time limit.
//
// The new Handler calls h.ServeHTTP to handle each request, but if a
// call runs for longer than its time limit, the handler responds with
// a 503 Service Unavailable error and the given message in its body.
// (If msg is empty, a suitable default message will be sent.)
// After such a timeout, writes by h to its ResponseWriter will return
// ErrHandlerTimeout.
//
// TimeoutHandler buffers all Handler writes to memory and does not
// support the Hijacker or Flusher interfaces.
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler {return &timeoutHandler{handler: h,timeout: dt,msg: msg,}
}
TimeoutHandler
?函數接收三個參數:
h
:一個?http.Handler
?類型的處理程序,代表實際要執行的請求處理邏輯。dt
:一個?time.Duration
?類型的超時時間,指定了處理請求的最大允許時間。msg
:一個字符串類型的超時消息,當請求處理超時時,會將該消息作為響應體返回給客戶端。
timeoutHandler
?結構體
type timeoutHandler struct {handler Handlertimeout time.Durationmsg string
}
timeoutHandler
?結構體包含三個字段,分別存儲傳入的處理程序、超時時間和超時消息。
ServeHTTP
?方法實現
func (th *timeoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {done := make(chan struct{})tw := &timeoutWriter{w: w,h: make(http.Header),msg: th.msg,}go func() {th.handler.ServeHTTP(tw, r)close(done)}()select {case <-done:tw.mu.Lock()defer tw.mu.Unlock()copyHeader(w.Header(), tw.h)w.WriteHeader(tw.code)w.Write(tw.wbuf.Bytes())case <-time.After(th.timeout):tw.mu.Lock()defer tw.mu.Unlock()if !tw.wroteHeader {http.Error(w, tw.errorMessage(), http.StatusServiceUnavailable)tw.timedOut = true}}
}
ServeHTTP
?方法是?timeoutHandler
?結構體實現?http.Handler
?接口的方法,其執行流程如下:
-
創建通道和包裝響應寫入器:
- 創建一個?
done
?通道,用于通知請求處理是否完成。 - 創建一個?
timeoutWriter
?結構體實例?tw
,用于包裝原始的?http.ResponseWriter
,并記錄響應信息。
- 創建一個?
-
啟動 goroutine 處理請求:
- 啟動一個新的 goroutine 來執行實際的請求處理邏輯?
th.handler.ServeHTTP(tw, r)
。 - 當處理完成后,關閉?
done
?通道。
- 啟動一個新的 goroutine 來執行實際的請求處理邏輯?
-
使用?
select
?語句等待結果:- 如果?
done
?通道接收到信號,說明請求處理在超時時間內完成。此時,將?tw
?中記錄的響應頭、狀態碼和響應體復制到原始的?http.ResponseWriter
?中并發送給客戶端。 - 如果?
time.After(th.timeout)
?通道接收到信號,說明請求處理超時。此時,檢查是否已經寫入響應頭,如果沒有,則使用?http.Error
?函數返回一個 503 狀態碼和超時消息給客戶端,并標記?tw.timedOut
?為?true
。
- 如果?
timeoutWriter
?結構體
type timeoutWriter struct {w http.ResponseWriterh http.Headerwbuf bytes.Buffercode intwroteHeader booltimedOut boolmu sync.Mutexmsg string
}
timeoutWriter
?結構體用于包裝原始的?http.ResponseWriter
,并記錄響應頭、狀態碼、響應體等信息。同時,它使用互斥鎖?mu
?來保證并發安全。
http.TimeoutHandler
?是一個非常實用的工具,它可以幫助我們避免長時間無響應的請求阻塞服務器資源。通過使用 goroutine 和通道,結合?select
?語句進行超時控制,實現了對請求處理時間的有效管理。需要注意的是,TimeoutHandler
?會將處理程序的所有寫入操作緩沖到內存中,并且不支持?Hijacker
?和?Flusher
?接口。