zoukankan      html  css  js  c++  java
  • Go语言备忘录(3):net/http包的使用模式和源码解析

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

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

    目录:

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

    模式一:使用默认的路由来注册处理函数:
    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)
       }
    }
     
    模式二:使用自定义的路由来注册处理函数:
    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实例:该模式可以很方便的管理服务端的行为
     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()
    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;

    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消息(心跳,为了保持连接不断开)。
    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()方法:具体见代码中我加的注释说明
    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),该方法的源码如下:
     
    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(这里为):
    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中持有的动态值(也就是我们定义的处理函数)
    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
    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请求

    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"}})
    

      

    程序在使用完回复后必须关闭回复的主体

    resp, err := http.Get("http://example.com/")
    if err != nil {
    	// handle error
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    // ...
    

      

    管理HTTP客户端的头域、重定向策略和其他设置,创建一个Client

    
    
    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

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

     
    以上如有误导的地方,请前辈们务必指出!
     
     
  • 相关阅读:
    关于Loki中promtail组件收集日志的几点思考
    安装 loki 轻量级日志监控系统
    官方文档采用Docker方式安装
    使用 Loki 搭建个人日志平台
    Elasticsearch:Node 介绍
    解决centos系统突然间网络不通的问题:Global IPv6 forwarding is disabled in configuration, but not currently disabled in kernel
    postgresql 角色权限
    Pycharm好用的插件
    the trustAnchors parameter must be non-empty(转)
    Django:json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
  • 原文地址:https://www.cnblogs.com/susufufu/p/7698900.html
Copyright © 2011-2022 走看看