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

简介:

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

转摘本文也请注明出处:Go语言备忘录(3):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函数类型;

模式一:使用默认的路由来注册处理函数:
 
模式二:使用自定义的路由来注册处理函数:
 
模式三:直接自定义一个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)  //调用自身
}
 
至此,整个调用过程讲解完毕,至于业务层的处理逻辑,则由各个处理函数实现

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

 
以上如有误导的地方,请前辈们务必指出!
相关文章
|
1月前
|
Cloud Native 安全 Java
Go语言深度解析:从入门到精通的完整指南
🌟蒋星熠Jaxonic,Go语言探索者。深耕云计算、微服务与并发编程,以代码为笔,在二进制星河中书写极客诗篇。分享Go核心原理、性能优化与实战架构,助力开发者掌握云原生时代利器。#Go语言 #并发编程 #性能优化
335 43
Go语言深度解析:从入门到精通的完整指南
|
3月前
|
数据采集 数据挖掘 测试技术
Go与Python爬虫实战对比:从开发效率到性能瓶颈的深度解析
本文对比了Python与Go在爬虫开发中的特点。Python凭借Scrapy等框架在开发效率和易用性上占优,适合快速开发与中小型项目;而Go凭借高并发和高性能优势,适用于大规模、长期运行的爬虫服务。文章通过代码示例和性能测试,分析了两者在并发能力、错误处理、部署维护等方面的差异,并探讨了未来融合发展的趋势。
284 0
|
2月前
|
Cloud Native 安全 Java
Go语言深度解析:从入门到精通的完整指南
🌟 蒋星熠Jaxonic,执着的星际旅人,用Go语言编写代码诗篇。🚀 Go语言以简洁、高效、并发为核心,助力云计算与微服务革新。📚 本文详解Go语法、并发模型、性能优化与实战案例,助你掌握现代编程精髓。🌌 从goroutine到channel,从内存优化到高并发架构,全面解析Go的强大力量。🔧 实战构建高性能Web服务,展现Go在云原生时代的无限可能。✨ 附技术对比、最佳实践与生态全景,带你踏上Go语言的星辰征途。#Go语言 #并发编程 #云原生 #性能优化
|
7月前
|
算法 Go 索引
【LeetCode 热题100】45:跳跃游戏 II(详细解析)(Go语言版)
本文详细解析了力扣第45题“跳跃游戏II”的三种解法:贪心算法、动态规划和反向贪心。贪心算法通过选择每一步能跳到的最远位置,实现O(n)时间复杂度与O(1)空间复杂度,是面试首选;动态规划以自底向上的方式构建状态转移方程,适合初学者理解但效率较低;反向贪心从终点逆向寻找最优跳点,逻辑清晰但性能欠佳。文章对比了各方法的优劣,并提供了Go语言代码实现,助你掌握最小跳跃次数问题的核心技巧。
298 15
|
7月前
|
机器学习/深度学习 存储 算法
【LeetCode 热题100】347:前 K 个高频元素(详细解析)(Go语言版)
这篇文章详细解析了力扣热题 347——前 K 个高频元素的三种解法:哈希表+小顶堆、哈希表+快速排序和哈希表+桶排序。每种方法都附有清晰的思路讲解和 Go 语言代码实现。小顶堆方法时间复杂度为 O(n log k),适合处理大规模数据;快速排序方法时间复杂度为 O(n log n),适用于数据量较小的场景;桶排序方法在特定条件下能达到线性时间复杂度 O(n)。文章通过对比分析,帮助读者根据实际需求选择最优解法,并提供了完整的代码示例,是一篇非常实用的算法学习资料。
446 90
|
3月前
|
缓存 监控 安全
告别缓存击穿!Go 语言中的防并发神器:singleflight 包深度解析
在高并发场景中,多个请求同时访问同一资源易导致缓存击穿、数据库压力过大。Go 语言提供的 `singleflight` 包可将相同 key 的请求合并,仅执行一次实际操作,其余请求共享结果,有效降低系统负载。本文详解其原理、实现及典型应用场景,并附示例代码,助你掌握高并发优化技巧。
280 0
|
3月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
5月前
|
存储 设计模式 安全
Go 语言单例模式全解析:从青铜到王者段位的实现方案
单例模式确保一个类只有一个实例,并提供全局访问点,适用于日志、配置管理、数据库连接池等场景。在 Go 中,常用实现方式包括懒汉模式、饿汉模式、双重检查锁定,最佳实践是使用 `sync.Once`,它并发安全、简洁高效。本文详解各种实现方式的优缺点,并提供代码示例与最佳应用建议。
158 5
|
6月前
|
存储 算法 Go
【LeetCode 热题100】17:电话号码的字母组合(详细解析)(Go语言版)
LeetCode 17题解题思路采用回溯算法,通过递归构建所有可能的组合。关键点包括:每位数字对应多个字母,依次尝试;递归构建下一个字符;递归出口为组合长度等于输入数字长度。Go语言实现中,使用map存储数字到字母的映射,通过回溯函数递归生成组合。时间复杂度为O(3^n * 4^m),空间复杂度为O(n)。类似题目包括括号生成、组合、全排列等。掌握回溯法的核心思想,能够解决多种排列组合问题。
187 11
|
6月前
|
Go
【LeetCode 热题100】155:最小栈(详细解析)(Go语言版)
本文详细解析了力扣热题155:最小栈的解题思路与实现方法。题目要求设计一个支持 push、核心思路是使用辅助栈法,通过两个栈(主栈和辅助栈)来维护当前栈中的最小值。具体操作包括:push 时同步更新辅助栈,pop 时检查是否需要弹出辅助栈的栈顶,getMin 时直接返回辅助栈的栈顶。文章还提供了 Go 语言的实现代码,并对复杂度进行了分析。此外,还介绍了单栈 + 差值记录法的进阶思路,并总结了常见易错点,如 pop 操作时忘记同步弹出辅助栈等。
199 6

热门文章

最新文章

推荐镜像

更多
  • DNS