本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导!
转摘本文也请注明出处:Go语言备忘录(3):net/http包的使用模式和源码解析,多谢!
目录:
Handler接口:所有请求的处理器、路由ServeMux都满足该接口;
1
2
3
|
type
Handler
interface
{
ServeHTTP(ResponseWriter, *Request)
}
|
- http包有一个包级别变量DefaultServeMux,表示默认路由:var DefaultServeMux = NewServeMux(),使用包级别的http.Handle()、http.HandleFunc()方法注册处理器时都是注册到该路由中;
- ServeMux结构体有ServeHTTP()方法(满足Handler接口),主要用于间接调用它所保存的处理器的ServeHTTP()方法
1
2
3
4
5
|
type
HandlerFunc
func
(ResponseWriter, *Request)
//实现Handler接口的ServeHTTP方法
func
(f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
//调用自身
}
|
二、HTTP服务器的使用模式:
处理函数:只要函数的签名为 func(w http.ResponseWriter, r *http.Request) ,均可作为处理函数,即它可以被转换为http.HandlerFunc函数类型;
模式一:使用默认的路由来注册处理函数:
模式二:使用自定义的路由来注册处理函数:
模式三:直接自定义一个Server实例:该模式可以很方便的管理服务端的行为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
mux := http.NewServeMux()
mux.Handle(
"/file"
,myHandler(
"somefile"
))
mux.HandleFunc(
"/"
, serveHome)
s := &http.Server{
Addr:
":8080"
,
Handler: mux,
//指定路由或处理器,不指定时为nil,表示使用默认的路由DefaultServeMux
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 << 20,
ConnState:
//指定连接conn的状态改变时的处理函数
//....
}
log.Fatal(s.ListenAndServe())
|
接下来,我们就跟踪源码来仔细的分析下整个执行过程。
三、HTTP服务器的执行过程:
1.使用http.ListenAndServe()方法启动服务,它根据给定参数构造Server类型,然后调用server.ListenAndServe()
1
2
3
4
|
func
ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return
server.ListenAndServe()
}
|
2.而server.ListenAndServe()方法内部调用net.Listen("tcp", addr),该方法内部又调用net.ListenTCP()创建并返回一个监听器net.Listener,如下的ln;
1
2
3
4
5
6
7
8
9
10
11
|
func
(srv *Server) ListenAndServe() error {
addr := srv.Addr
if
addr ==
""
{
addr =
":http"
}
ln, err := net.Listen(
"tcp"
, addr)
if
err != nil {
return
err
}
return
srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
|
3.然后把监听器 ln 断言转换为 TCPListener 类型,并根据它构造一个 tcpKeepAliveListener 对象并传递给server.Serve()方法;
- 因为TCPListener实现了Listener接口,所以tcpKeepAliveListener也实现了Listener接口,并且它重写了Accept()方法,目的是为了调用SetKeepAlive(true),让操作系统为收到的每一个连接启动发送keepalive消息(心跳,为了保持连接不断开)。
1
2
3
4
5
6
7
8
9
10
11
12
|
type
tcpKeepAliveListener
struct
{
*net.TCPListener
}
func
(ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if
err != nil {
return
}
tc.SetKeepAlive(true)
//发送心跳
tc.SetKeepAlivePeriod(3 * time.Minute)
//发送周期
return
tc, nil
}
|
4.server.Serve()方法调用tcpKeepAliveListener 对象的 Accept() 方法返回一个连接conn(该连接启动了心跳),并为每一个conn创建一个新的go程执行conn.server()方法:具体见代码中我加的注释说明
5.而conn.server(
)方法会读取请求,然后根据conn内保存的server来构造一个serverHandler类型,并调用它的ServeHTTP()方法:serverHandler{c.server}.ServeHTTP(w, w.req),该方法的源码如下:
1
2
3
4
5
6
7
8
9
10
|
func
(sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if
handler == nil {
handler = DefaultServeMux
}
if
req.RequestURI ==
"*"
&& req.Method ==
"OPTIONS"
{
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
|
6.如上源码可以看到,当 handler == nil 时使用默认的DefaultServeMux路由,否则使用在第1步中为Serve指定了的Handler;然后调用该Handler的ServeHTTP方法(该Handler一般被设置为路由ServeMux类型);
7.而路由ServeMux的ServeHTTP方法则会根据当前请求提供的信息来查找最匹配的Handler(这里为):
1
2
3
4
5
6
7
8
9
10
11
|
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)
//规范化请求的路径格式,查找最匹配的Handler
h.ServeHTTP(w, r)
}
|
8.以上查找到的Handler接口值h就是我们事先注册到路由中与请求匹配的Handler;而h的动态类型是HandlerFunc类型(它也满足Handler接口);
所以,以上 h.ServeHTTP(w, r) 实际上调用的是接口值h中持有的动态值(也就是我们定义的处理函数)
1
2
3
4
5
|
type
HandlerFunc
func
(ResponseWriter, *Request)
//实现Handler接口的ServeHTTP方法
func
(f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
//调用自身
}
|
至此,整个调用过程讲解完毕,至于业务层的处理逻辑,则由各个处理函数实现
1
2
3
4
5
6
7
8
|
func
main() {
mux := http.NewServeMux()
mux.Handle(
"/to"
,http.RedirectHandler(
"http://example.org"
, 307))
err := http.ListenAndServe(*addr,mux)
//启动监听
if
err != nil {
log.Fatalln(
"ListenAndServe: "
, err)
}
}
|
好了,本文就暂时讲关于http包关于HTTP服务端方面的东西,至于客户端方面的就简单引用一下官方文档说明吧,毕竟客户端很少用Go实现。
五、客户端的实现:
Get、Head、Post和PostForm函数发出HTTP/ HTTPS请求。
1
2
3
4
5
6
|
resp, err := http.Get(
"http://example.com/"
)
...
resp, err := http.Post(
"http://example.com/upload"
,
"image/jpeg"
, &buf)
...
resp, err := http.PostForm(
"http://example.com/form"
,
url.Values{
"key"
: {
"Value"
},
"id"
: {
"123"
}})
|
程序在使用完回复后必须关闭回复的主体。
1
2
3
4
5
6
7
|
resp, err := http.Get(
"http://example.com/"
)
if
err != nil {
// handle error
}
defer
resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
// ...
|
要管理HTTP客户端的头域、重定向策略和其他设置,创建一个Client:
1
2
3
4
5
6
7
8
9
10
|
client := &http.Client{
CheckRedirect: redirectPolicyFunc,
}
resp, err := client.Get(
"http://example.com"
)
// ...
req, err := http.NewRequest(
"GET"
,
"http://example.com"
, nil)
// ...
req.Header.Add(
"If-None-Match"
, `W/
"wyzzy"
`)
resp, err := client.Do(req)
// ...
|
要管理代理、TLS配置、keep-alive、压缩和其他设置,创建一个Transport:
1
2
3
4
5
6
|
tr := &http.Transport{
TLSClientConfig: &tls.Config{RootCAs: pool},
DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get(
"https://example.com"
)
|
Client和Transport类型都可以安全的被多个go程同时使用。出于效率考虑,应该一次建立、尽量重用。
以上如有误导的地方,请前辈们务必指出!