net/http 使用源码分析
在 Go 语言的 HTTP 服务器里,HTTP handler 是实现了http.Handler
接口的对象。该接口定义如下:
go
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
ServeHTTP
方法接收两个参数:
http.ResponseWriter
:用于向客户端发送 HTTP 响应。*http.Request
:代表客户端的 HTTP 请求。
示例代码
go
package main
import (
"fmt"
"net/http"
)
// 自定义的HTTP处理程序
type HelloHandler struct{}
// 实现http.Handler接口的ServeHTTP方法
func (h HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
// 创建自定义handler的实例
hello := HelloHandler{}
// 注册handler到默认的ServeMux
http.Handle("/", hello)
// 启动HTTP服务器,监听8080端口
fmt.Println("Starting server on :8080")
http.ListenAndServe(":8080", nil)
}
在上述代码中,HelloHandler
结构体实现了http.Handler
接口,其ServeHTTP
方法会向客户端发送 "Hello, World!" 消息。
源码分析
http.Handler
接口定义在net/http/server.go
文件中:
vbnet
// Handler is an interface implemented by an HTTP handler.
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
该接口仅有一个方法ServeHTTP
,任何实现了这个方法的类型都可作为 HTTP handler 使用。
http.ServeMux
http.ServeMux
是一个 HTTP 请求多路复用器,它会将不同的请求路径映射到对应的 handler。其定义如下:
go
// ServeMux is an HTTP request multiplexer.
// It matches the URL of each incoming request against a list of registered
// patterns and calls the handler for the pattern that most closely matches the
// URL.
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
ServeMux
结构体包含一个映射m
,用于存储路径模式和对应的 handler。http.Handle
函数会向ServeMux
注册 handler:
go
// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
ServeMux
的ServeHTTP
方法会根据请求的路径查找对应的 handler 并调用其ServeHTTP
方法:
scss
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
http.ListenAndServe
http.ListenAndServe
函数用于启动一个 HTTP 服务器,它会监听指定的地址和端口,并处理 HTTP 请求:
go
// ListenAndServe listens on the TCP network address addr and then calls
// Serve with handler to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
//
// The handler is typically nil, in which case the DefaultServeMux is used.
//
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
http.ListenAndServe
函数创建了一个http.Server
实例,并调用其ListenAndServe
方法启动服务器。
内置的 HTTP 处理程序
http.NotFoundHandler
功能概述
http.NotFoundHandler
是 net/http
包提供的一个内置处理程序,当客户端请求的资源不存在时,使用这个处理程序可以返回一个标准的 404 响应。它通常用于处理那些没有匹配到任何路由规则的请求。
使用示例
go
package main
import (
"net/http"
)
func main() {
// 注册一个空的路由,这里没有具体的处理逻辑
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 处理其他特定逻辑
})
// 对于所有未匹配的路由,使用 NotFoundHandler 返回 404 响应
http.HandleFunc("/notfound", http.NotFoundHandler().ServeHTTP)
// 启动服务器,监听 8080 端口
http.ListenAndServe(":8080", nil)
}
在这个示例中,当客户端请求 /notfound
路径时,会触发 http.NotFoundHandler
,返回 404 状态码和相应的提示信息。
源码分析
http.NotFoundHandler
的定义和实现位于 net/http/server.go
文件中,下面是其源码:
go
// NotFoundHandler returns a simple request handler
// that replies to each request with a ``404 page not found'' reply.
func NotFoundHandler() Handler { return NotFoundHandlerFunc }
// NotFoundHandlerFunc is the default handler for http.NotFoundHandler.
func NotFoundHandlerFunc(w ResponseWriter, r *Request) {
http.Error(w, "404 page not found", http.StatusNotFound)
}
NotFoundHandler
是一个函数,它返回一个实现了http.Handler
接口的处理程序。在这个例子中,返回的是NotFoundHandlerFunc
。NotFoundHandlerFunc
是一个具体的函数,它实现了http.Handler
接口的ServeHTTP
方法所需的签名。当调用这个函数时,它会使用http.Error
函数向客户端发送一个 404 状态码和相应的错误信息。
http.Error
函数
http.Error
函数用于向客户端发送一个带有指定错误信息和状态码的响应,其源码如下:
go
// Error replies to the request with the specified error message and HTTP code.
// It does not otherwise end the request; the caller should ensure no further
// writes are done to w.
// The error message should be plain text.
func Error(w ResponseWriter, error string, code int) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
io.WriteString(w, error)
}
http.Error
函数首先设置响应头,指定内容类型为纯文本(text/plain
)并设置字符集为utf-8
,同时设置X-Content-Type-Options
为nosniff
以防止浏览器进行内容类型嗅探。- 然后,使用
w.WriteHeader(code)
设置 HTTP 状态码。 - 最后,使用
io.WriteString
函数将错误信息写入响应体。
RedirectHandler
http.RedirectHandler
的定义和实现位于net/http/server.go
文件中,下面是其源码:
go
// RedirectHandler returns a request handler that redirects
// each request it receives to the given url using the given
// status code.
//
// The provided code should be in the 3xx range and is usually
// StatusMovedPermanently, StatusFound or StatusSeeOther.
func RedirectHandler(url string, code int) Handler {
return redirectHandler{url, code}
}
type redirectHandler struct {
url string
code int
}
func (rh redirectHandler) ServeHTTP(w ResponseWriter, r *Request) {
Redirect(w, r, rh.url, rh.code)
}
go
// Redirect replies to the request with a redirect to url,
// which may be a path relative to the request path.
//
// The provided code should be in the 3xx range and is usually
// StatusMovedPermanently, StatusFound or StatusSeeOther.
func Redirect(w ResponseWriter, r *Request, url string, code int) {
if u, err := urlparse.Parse(url); err == nil {
// If url was relative, make absolute by
// combining with request path.
// The browser would probably do this for us,
// but doing it ourselves is more reliable.
//
// RFC 2616 says that the Location header must be an absolute URI,
// but we obey the spirit of the RFC and allow relative URIs so
// long as they can be made absolute relative to the request URI.
if u.Scheme == "" && u.Host == "" {
oldpath := r.URL.Path
if oldpath == "" { // should not happen, but avoid a crash if it does
oldpath = "/"
}
// no leading http://server
if url == "" || url[0] != '/' {
// make relative path absolute
last := len(oldpath) - 1
if last >= 0 && oldpath[last] != '/' {
oldpath = oldpath + "/"
}
url = oldpath + url
}
var query string
if i := strings.Index(url, "?"); i != -1 {
url, query = url[:i], url[i:]
}
// clean up but preserve trailing slash
trailing := strings.HasSuffix(url, "/")
url = path.Clean(url)
if trailing && !strings.HasSuffix(url, "/") {
url = url + "/"
}
url = url + query
}
}
w.Header().Set("Location", hexEscapeNonASCII(url))
w.WriteHeader(code)
// RFC 2616 recommends that a short note "SHOULD" be included in the
// response because older user agents may not understand 301/307.
// Shouldn't send the response for POST or HEAD; that leaves GET.
if r.Method == "GET" {
body := "<a href="" + html.EscapeString(url) + "">" + http.StatusText(code) + "</a>.\n"
io.WriteString(w, body)
}
}
http.Redirect
函数首先会处理相对 URL,将其转换为绝对 URL。- 然后,设置响应头的
Location
字段为目标 URL,并使用w.WriteHeader(code)
设置重定向状态码。 - 最后,如果请求方法是
GET
,会在响应体中返回一个简单的 HTML 链接,提示用户重定向信息
http.StripPrefix
http.StripPrefix
是 Go 语言 net/http
包中的一个函数,它的主要作用是创建一个新的 HTTP 处理程序。这个新处理程序会在处理请求之前,从请求的 URL 路径中移除指定的前缀,然后将处理工作委托给另一个提供的处理程序。
使用示例
go
package main
import (
"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
函数的定义如下:
go
// 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 = *r
r2.URL = new(url.URL)
*r2.URL = *r.URL
r2.URL.Path = p
h.ServeHTTP(w, r2)
} else {
http.NotFound(w, r)
}
})
}
源码详细解释
-
参数检查:
iniif prefix == "" { return h }
如果传入的前缀
prefix
为空字符串,那么直接返回原处理程序h
,因为不需要移除任何前缀。 -
创建新的处理程序:
goreturn HandlerFunc(func(w ResponseWriter, r *Request) { // ... })
使用
HandlerFunc
类型创建一个新的处理程序。HandlerFunc
是一个函数类型,它实现了http.Handler
接口的ServeHTTP
方法。 -
移除前缀并处理请求:
iniif p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) { r2 := new(Request) *r2 = *r r2.URL = new(url.URL) *r2.URL = *r.URL r2.URL.Path = p h.ServeHTTP(w, r2) }
- 使用
strings.TrimPrefix
函数尝试从请求的 URL 路径中移除指定的前缀。 - 如果移除成功(即移除后的路径长度小于原路径长度),则创建一个新的
http.Request
对象r2
,并复制原请求的所有信息。 - 修改新请求对象
r2
的 URL 路径为移除前缀后的路径。 - 调用原处理程序
h
的ServeHTTP
方法,将新请求对象r2
传递给它进行处理。
- 使用
-
处理未匹配的请求:
arduino} else { http.NotFound(w, r) }
如果请求的 URL 路径不包含指定的前缀,那么调用
http.NotFound
函数返回一个 404 错误响应。
http.StripPrefix
是一个非常实用的工具,它允许你在处理请求之前对 URL 路径进行预处理,移除不必要的前缀。这在处理静态文件服务、API 路由等场景中非常有用。通过分析源码,我们可以看到它是如何创建新的请求对象、修改路径并将处理工作委托给原处理程序的,同时也处理了未匹配前缀的情况。
http.TimeoutHandler
http.TimeoutHandler
是 Go 语言 net/http
包中的一个函数,它用于为 HTTP 请求处理设置超时时间。当一个请求的处理时间超过预设的超时时间时,会返回一个超时响应给客户端,避免客户端长时间等待无响应的请求。
使用示例
go
package main
import (
"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" 的响应。
源码分析
go
// 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
结构体
go
type timeoutHandler struct {
handler Handler
timeout time.Duration
msg string
}
timeoutHandler
结构体包含三个字段,分别存储传入的处理程序、超时时间和超时消息。
ServeHTTP
方法实现
scss
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
结构体
go
type timeoutWriter struct {
w http.ResponseWriter
h http.Header
wbuf bytes.Buffer
code int
wroteHeader bool
timedOut bool
mu sync.Mutex
msg string
}
timeoutWriter
结构体用于包装原始的 http.ResponseWriter
,并记录响应头、状态码、响应体等信息。同时,它使用互斥锁 mu
来保证并发安全。
http.TimeoutHandler
是一个非常实用的工具,它可以帮助我们避免长时间无响应的请求阻塞服务器资源。通过使用 goroutine 和通道,结合 select
语句进行超时控制,实现了对请求处理时间的有效管理。需要注意的是,TimeoutHandler
会将处理程序的所有写入操作缓冲到内存中,并且不支持 Hijacker
和 Flusher
接口。