net/http包的使用模式和源码解析

目录:

 
一、http包的3个关键类型:
Handler接口:所有请求的处理器、路由ServeMux都满足该接口;
1
2
3
type  Handler  interface  {
   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
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函数类型;

模式一:使用默认的路由来注册处理函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var  addr = flag.String( "addr" ":8080" "http server address" )
//1.不带参数处理函数
func  serveHome(w http.ResponseWriter, r *http.Request) {
    if  r.URL.Path !=  "/"  {
       http.NotFound(w, r)
        return
   }
   http.ServeFile(w, r,  "home.html" )
}
//2.带参数处理函数,闭包函数隐式转换为http.HandlerFunc函数类型
func  myHandler(s string) http.HandlerFunc {
     return  func (w http.ResponseWriter, r *http.Request) {
         if  r.URL.Path !=  "/"  {
            http.NotFound(w, r)
             return
        }
        http.ServeFile(w, r, s)  //使用参数s
    }
}
func  main() {
   flag.Parse()
     
    //向默认路由注册处理器函数
   http.HandleFunc( "/" , serveHome)  //或http.Handle("/", http.HandlerFunc(serveHome))
   http.Handle( "/file" ,myHandler( "somefile" ))
     
   err := http.ListenAndServe(*addr, nil)  //启动监听,第二个参数nil表示使用默认路由DefaultServeMux中注册的处理器
    if  err != nil {
      log.Fatalln( "ListenAndServe: " , err)
   }
}
 
模式二:使用自定义的路由来注册处理函数:
1
2
3
4
5
6
7
8
9
10
func  main() {
  mux := http.NewServeMux()  //新建一个自定义的路由
  mux.Handle( "/file" ,myHandler( "somefile" ))
  mux.HandleFunc( "/" , serveHome)
     
  err := http.ListenAndServe(*addr,mux)  //启动监听
    if  err != nil {
      log.Fatalln( "ListenAndServe: " , err)
   }
}
 
模式三:直接自定义一个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()方法:具体见代码中我加的注释说明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func  (srv *Server) Serve(l net.Listener) error {
     defer  l.Close()
     if  fn := testHookServerServe; fn != nil {
        fn(srv, l)
    }
     var  tempDelay time.Duration  //重试间隔
 
     if  err := srv.setupHTTP2_Serve(); err != nil {
         return  err
    }
 
    srv.trackListener(l, true)  //缓存该监听器
     defer  srv.trackListener(l, false)  //从缓存中删除当前监听器
 
    baseCtx := context.Background()
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)  //新建一个context用来管理每个连接conn的Go程
     for  {
        rw, e := l.Accept()  //调用tcpKeepAliveListener对象的 Accept() 方法
         if  e != nil {
             select  {
             case  <-srv.getDoneChan():
                 return  ErrServerClosed  //退出Serve方法,并执行延迟调用(从缓存中删除当前监听器)
             default :
            }
             //如果发生了net.Error错误,则隔一段时间就重试一次,间隔时间每次翻倍,最大为1秒
             if  ne, ok := e.(net.Error); ok && ne.Temporary() {
                 if  tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                }  else  {
                    tempDelay *= 2
                }
                 if  max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf( "http: Accept error: %v; retrying in %v" , e, tempDelay)
                time.Sleep(tempDelay)
                 continue
            }
             return  e
        }
        tempDelay = 0
        c := srv.newConn(rw)  //该方法根据net.Conn、srv构造了一个新的http.conn类型
        c.setState(c.rwc, StateNew)  //缓存该连接的状态,如果方法:Server.ConnState(net.Conn, ConnState)不为nil,就根据当前连接的状态执行它
         go  c.serve(ctx)
    }
}

  

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)  //调用自身
}
 
至此,整个调用过程讲解完毕,至于业务层的处理逻辑,则由各个处理函数实现

四、重定向:
http包自带了几个创建常用处理器的函数:FileServer,NotFoundHandler、RedirectHandler、StripPrefix、TimeoutHandler。
而RedirectHandler函数就是用来重定向的:它返回一个请求处理器,该处理器会对每个请求都使用状态码code重定向到网址url
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程同时使用。出于效率考虑,应该一次建立、尽量重用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值