1
头图

大家好,针对Go语言 net/http 标准库,将梳理的相关知识点分享给大家~~
围绕 net/http 标准库相关知识点还有许多章节,请大家多多关注。
文章中代码案例只有关键片段,完整代码请查看github仓库:https://github.com/hltfaith/go-example/tree/main/net-http
本章节案例,请大家以 go1.16+ 版本以上进行参考。

net/http标准库系列文章

本节内容

  • HandleFunc() 方法
  • ListenAndServe() 方法
  • ListenAndServeTLS() 方法
  • FileServer()方法
  • FileServerFS()方法

ListenAndServe()、HandleFunc()

ListenAndServer() 函数顾名思义是:监听 TCP 端口和服务。主要作用是使用处理程序调用服务来处理传入连接上的请求,接受的连接被配置为启用TCP保活。
ListenAndServer() 函数,会返回非 nil 的错误。
函数原型

func ListenAndServe(addr string, handler Handler) error

HandleFunc()函数,主要作用是在DefaultServeMux全局锁中注册指定的路由处理程序。
函数原型

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

http服务创建监听,代码片段。

这是一个非常简单代码片段,首先新增了路由,并对请求响应返回一段字符串,最后监听8080端口等待请求连接。

注:代码中 :8080 代表监听本地所有网卡,都可以访问到该服务。其次是默认将 ipv4ipv6 地址全部监听, 这种 http.ListenAndServe("0.0.0.0:8080", nil) 写法是和 http.ListenAndServe(":8080", nil) 一样的。

结合上面代码片段,我们来分析一下 /hello 路由服务是如何监听到本地的。

首先,先分析第一个问题: http.ListenAndServe()如何将url路由关联起来的,我们看到代码中ListenAndServe(":8080", nil) 第二个参数 nil 代表是0零值,并没有携带处理程序调用的服务,那它怎么与 http.HandleFunc() 关联起来的?
ListenAndServe() 函数源码中的解释是:如果 Handler 参数为nil 值时则使用 DefaultServeMux 全局锁。

下面我们再来看看到底在什么环节,判断 Handler 参数为nil 值时则使用 DefaultServeMux类型的。

调用过程:http.ListenAndServe() -> (srv *Server) ListenAndServe() -> (srv *Server) Serve(l net.Listener) -> go c.serve(connCtx)

最终 net/http 库会针对每个请求的客户端,通过 go c.serve(connCtx)协程针对每次http请求单独处理。

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
...
// HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another,
// so we might as well run the handler in this goroutine.
// [*] Not strictly true: HTTP pipelining. We could let them all process
// in parallel even if their responses need to be serialized.
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
serverHandler{c.server}.ServeHTTP(w, w.req)
  ...
}

下图代码中就进行判断 handler 是不是nil值,如果是则使用 DefaultServeMux 所维护的 Handler 路由服务进行处理。

接着,我们来分析第二个问题:http.ListenAndServe() 如何进行端口监听的呢?
http.ListenAndServe()先是通过 net.Listen("tcp", addr)收听本地网络地址上的广播, 然后拿到监听描述符,最后一直监听 Accept() 的请求连接。

ListenAndServeTLS()

ListenAndServeTLS() 函数是基于 ListenAndServe() 函数的基础上增加了安全证书,也是我们说的HTTPS
这里在简单说明下HTTPHTTPS的区别:
(1) HTTP是超文本传输协议,信息是明文传输,HTTPS则是具有安全性的SSL加密传输协议。
(2) HTTPHTTPS使用的是完全不同的连接方式,使用的端口也不一样,前者是80,后者是443。
(3) HTTP的连接很简单,是无状态的。
(4) HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比HTTP协议安全。
ListenAndServeTLS() 函数原型

func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error

参数一 addr :监听网卡的IP地址与端口,如果值是空字符默认则是443端口。
参数二 certFile:证书文件,可以通过 crypto/tls 库去生成。(或者通过OpenSSL签发证书)
参数三 keyFile:证书Key
参数四 handler:和ListenAndServe()函数中的作用一致,是处理http路由服务处理程序。

函数使用
listenandservetls.go

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
        io.WriteString(w, "Hello, TLS!\n")
    })
    log.Fatal(http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil))
}

需要说明下,这里的 cert.pemkey.pem 证书与key文件是通过 crypto/tls 库中的 generate_cert.go 提供的代码块生成。
生成cert证书用法

macbookpro:net mac$ go run /usr/local/go/src/crypto/tls/generate_cert.go -host 127.0.0.1
2024/05/12 17:31:15 wrote cert.pem
2024/05/12 17:31:15 wrote key.pem

我这里的GOROOT路径是 /usr/local/go,大家可以通过 go env | grep GOROOT 命令查看具体的路径。
-host 127.0.0.1 参数:代表证书仅在本地环境测试使用。

下面,我们运行程序后,在浏览器访问 https://127.0.0.1地址,发现浏览器警告这是一个不安全的连接,因为我们的证书没经过受信任机构处理。

下面将所生成的证书加入到系统受信任区域,我所使用的是mac系统,下面操作根据系统步骤不太一样,大家可以根据系统自行搜索下。
首先找到我们的证书路径

操作完上面的配置后,我们在浏览器访问 https://127.0.0.1地址。(务必要先重启浏览器)

浏览器告诉我们,这次连接访问是安全的。
在浏览器开发调试模式下,也可以看到证书是安全的。

FileServer()

FileServer() 函数创建一个静态文件服务。
函数使用
fileserver.go

func main() {
    log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("."))))
}

代码中把当前目录设置为根目录,本目录下的所有文件将展示在web页面中
在浏览器中访问 http://127.0.0.1:8080 地址,可以看到本地目录中的所有文件和目录都可以被访问到。

FileServerFs()

FileServerFS() 函数,使用文件系统的内容为HTTP请求提供服务的处理程序。

注:该函数是 go1.22.0 版本新增的特性。

函数使用
fileserverfs.go

func main() {
    filename := "index.html"
    contents := []byte("<h1>帽儿山的枪手</h1>")
    fsys := fstest.MapFS{
        filename: {Data: contents},
    }
    http.Handle("/", http.FileServerFS(fsys))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

代码块中封装了html格式的文件内容,然后通过 FileServerFS() 函数发布服务。

技术文章持续更新,请大家多多关注呀~~

搜索微信公众号,关注我【 帽儿山的枪手 】

参考材料


帽儿山的枪手
71 声望18 粉丝