Go语言备忘录,3:net/http包的使用模式和源码解析

本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导!

转摘本文也请注明出处:Go语言备忘录(3):net/http包的使用模式和源码解析,多谢!

目录:

一、http包的3个关键类型:

Handler接口:所有请求的处理器、路由ServeMux都满足该接口;

1

2

3

typeHandlerinterface{

ServeHTTP(ResponseWriter, *Request)

}

ServeMux结构体:HTTP请求的多路转接器(路由),它负责将每一个接收到的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。它内部用一个map来保存所有处理器Handler
  • http包有一个包级别变量DefaultServeMux,表示默认路由:var DefaultServeMux = NewServeMux(),使用包级别的http.Handle()、http.HandleFunc()方法注册处理器时都是注册到该路由中;
  • ServeMux结构体有ServeHTTP()方法(满足Handler接口),主要用于间接调用它所保存的处理器的ServeHTTP()方法
http.HandlerFunc函数类型:它满足Handler接口

1

2

3

4

5

typeHandlerFuncfunc(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函数类型;

模式一:使用默认的路由来注册处理函数:

+ View Code

模式二:使用自定义的路由来注册处理函数:

+ View Code

模式三:直接自定义一个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

funcListenAndServe(addr string, handler Handler) error {

server := &Server{Addr: addr, Handler: handler}

returnserver.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

ifaddr ==""{

addr =":http"

}

ln, err := net.Listen("tcp", addr)

iferr != nil {

returnerr

}

returnsrv.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

typetcpKeepAliveListenerstruct{

*net.TCPListener

}

func(ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {

tc, err := ln.AcceptTCP()

iferr != nil {

return

}

tc.SetKeepAlive(true)//发送心跳

tc.SetKeepAlivePeriod(3 * time.Minute)//发送周期

returntc, nil

}

4.server.Serve()方法调用tcpKeepAliveListener 对象的 Accept() 方法返回一个连接conn(该连接启动了心跳),并为每一个conn创建一个新的go程执行conn.server()方法:具体见代码中我加的注释说明

+ View Code

  

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

ifhandler == nil {

handler = DefaultServeMux

}

ifreq.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) {

ifr.RequestURI =="*"{

ifr.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

typeHandlerFuncfunc(ResponseWriter, *Request)

//实现Handler接口的ServeHTTP方法

func(f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {

f(w, r)//调用自身

}

至此,整个调用过程讲解完毕,至于业务层的处理逻辑,则由各个处理函数实现

四、重定向:

http包自带了几个创建常用处理器的函数:FileServer,NotFoundHandler、RedirectHandler、StripPrefix、TimeoutHandler。

而RedirectHandler函数就是用来重定向的:它返回一个请求处理器,该处理器会对每个请求都使用状态码code重定向到网址url

1

2

3

4

5

6

7

8

funcmain() {

mux := http.NewServeMux()

mux.Handle("/to",http.RedirectHandler("http://example.org", 307))

err := http.ListenAndServe(*addr,mux)//启动监听

iferr != 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/")

iferr != nil {

// handle error

}

deferresp.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程同时使用。出于效率考虑,应该一次建立、尽量重用。

以上如有误导的地方,请前辈们务必指出!

读完觉得学到点什么,就( 顶一个!)

本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导!

转摘本文也请注明出处:Go语言备忘录(3):net/http包的使用模式和源码解析,多谢!

目录:

一、http包的3个关键类型:

Handler接口:所有请求的处理器、路由ServeMux都满足该接口;

1

2

3

typeHandlerinterface{

ServeHTTP(ResponseWriter, *Request)

}

ServeMux结构体:HTTP请求的多路转接器(路由),它负责将每一个接收到的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。它内部用一个map来保存所有处理器Handler
  • http包有一个包级别变量DefaultServeMux,表示默认路由:var DefaultServeMux = NewServeMux(),使用包级别的http.Handle()、http.HandleFunc()方法注册处理器时都是注册到该路由中;
  • ServeMux结构体有ServeHTTP()方法(满足Handler接口),主要用于间接调用它所保存的处理器的ServeHTTP()方法
http.HandlerFunc函数类型:它满足Handler接口

1

2

3

4

5

typeHandlerFuncfunc(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函数类型;

模式一:使用默认的路由来注册处理函数:

+ View Code

模式二:使用自定义的路由来注册处理函数:

+ View Code

模式三:直接自定义一个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

funcListenAndServe(addr string, handler Handler) error {

server := &Server{Addr: addr, Handler: handler}

returnserver.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

ifaddr ==""{

addr =":http"

}

ln, err := net.Listen("tcp", addr)

iferr != nil {

returnerr

}

returnsrv.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

typetcpKeepAliveListenerstruct{

*net.TCPListener

}

func(ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {

tc, err := ln.AcceptTCP()

iferr != nil {

return

}

tc.SetKeepAlive(true)//发送心跳

tc.SetKeepAlivePeriod(3 * time.Minute)//发送周期

returntc, nil

}

4.server.Serve()方法调用tcpKeepAliveListener 对象的 Accept() 方法返回一个连接conn(该连接启动了心跳),并为每一个conn创建一个新的go程执行conn.server()方法:具体见代码中我加的注释说明

+ View Code

  

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

ifhandler == nil {

handler = DefaultServeMux

}

ifreq.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) {

ifr.RequestURI =="*"{

ifr.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

typeHandlerFuncfunc(ResponseWriter, *Request)

//实现Handler接口的ServeHTTP方法

func(f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {

f(w, r)//调用自身

}

至此,整个调用过程讲解完毕,至于业务层的处理逻辑,则由各个处理函数实现

四、重定向:

http包自带了几个创建常用处理器的函数:FileServer,NotFoundHandler、RedirectHandler、StripPrefix、TimeoutHandler。

而RedirectHandler函数就是用来重定向的:它返回一个请求处理器,该处理器会对每个请求都使用状态码code重定向到网址url

1

2

3

4

5

6

7

8

funcmain() {

mux := http.NewServeMux()

mux.Handle("/to",http.RedirectHandler("http://example.org", 307))

err := http.ListenAndServe(*addr,mux)//启动监听

iferr != 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/")

iferr != nil {

// handle error

}

deferresp.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程同时使用。出于效率考虑,应该一次建立、尽量重用。

以上如有误导的地方,请前辈们务必指出!