目录

文章目录

  • 目录
  • HTTP 服务端
    • 实现原理
      • 注册处理程序和请求路由
      • 监听并处理请求
  • HTTP 服务器的可扩展性
    • 自定义 Handler,同时使用默认的 DefaultServeMux 和默认的 Server 的结构
    • 自定义 Handler 和 ServeMux,使用默认的 Server 的结构
    • 自定义 Handler、ServeMux 和 Server
    • 将函数作为处理器

HTTP 服务端

同样的,我们可以设想作为 HTTP 服务端处理一次请求应该具备哪些行为:

  1. 实现处理函数
  2. 预设 URL、Request Method、处理函数,三者之间的路由映射
  3. 监听请求
  4. 分发请求并完成处理

net/http 将上述行为整合为了两大步骤:

  1. 注册处理程序和请求路由。
  2. 监听并处理请求。

通过一个最简单的示例来感受:

package mainimport ("fmt""net/http"
)func indexHandler(w http.ResponseWriter, request *http.Request) {fmt.Fprintln(w, "Hello World.")
}func main() {http.HandleFunc("/", indexHandler)err := http.ListenAndServe("127.0.0.1:80", nil)if err != nil {fmt.Println("ListenAndServe: ", err)}
}
  • http.HandleFunc 函数:用于注册处理程序 indexHandler 和请求路由 ‘/’。
  • http.ListenAndServe 函数:用于监听 Socket(127.0.0.1:80)和处理外部请求。

实现原理

注册处理程序和请求路由

对于 net/http 而言,HTTP 服务器的本质就是一组实现了 http.Handler 接口的 Handlers(处理器集合),处理 HTTP 请求时,根据请求的 URL 进行路由选择到合适的 Handler 进行处理:

主要体现为两个关键的结构体 http.ServeMux 和 http.Handler:

  • http.Handler:处理 Request 并返回 Response。任何满足了 http.Handler 接口的对象都可作为一个 Handler。http.Handler 是 net/http 服务端具有可扩展性的核心体现。

  • http.ServrMux:本质上是一个 HTTP 请求路由器,或者叫多路复用器(Multiplexor)。它把收到的请求的 URL 与一组预先定义的 HASH 表进行匹配,HASH 的 keys 就是 URL paths + Request Methods 的组合、values 就是关联的 Handlers。

当我们调用 http.HandleFunc 注册一个 Handler(处理器)和请求路由时,默认使用的是 http.DefaultServeMux(路由器):


var defaultServeMux ServeMux
var DefaultServeMux = &defaultServeMuxfunc HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {DefaultServeMux.HandleFunc(pattern, handler)  //调用ServeMux的HandleFunc函数
}

上述可见,DefaultServeMux 是 ServeMux 类型的变量,所以实际上调用了 http.ServeMux.HandleFunc 方法:

type ServeMux struct {mu    sync.RWMutexm     map[string]muxEntry  // key 是 URLhosts bool
}type muxEntry struct {h       Handler   // 通过 HandleFunc 函数传入 Handlerpattern string    // URL
}func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {mux.Handle(pattern, HandlerFunc(handler))  //调用Handle
}func (mux *ServeMux) Handle(pattern string, handler Handler) {mux.mu.Lock()defer mux.mu.Unlock()//....mux.m[pattern] = muxEntry{h: handler, pattern: pattern}//....
}

可见,http.ServeMux.Handle 方法完成了 Handler 和请求路径的映射工作。http.ServeMux 的成员 http.muxEntry,存储了从 URL 到 Handler 的映射关系,HTTP 服务器在处理请求时就会使用该哈希查找到对应的 Handler。

与一个细节值得注意,HandleFunc 函数中会把 handler 变量强制类型转换为 HandlerFunc 类型:HandlerFunc(handler),HandleFunc 类型的定义如下:

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

可见,handler 变量和 HandleFunc 类型的函数签名是一致的:func(ResponseWriter, *Request),并且 HandleFunc 结构体具有一个 ServeHTTP 方法。同时 ServeHTTP 方法会调用传入的 handler,如:f(w, r)

综上,可以看出 HandlerFunc 只是对我们传入的 handler 变量进行了包装并提供了 ServeHTTP 方法来调用 handler 自身。

这里总结一下注册一个 Handler 的流程

  1. http.HandlerFunc 调用 ServeMux.HandleFunc 方法。
  2. ServeMux.HandleFunc 首先对我们传进来的 handler 变量强制转换为 HandlerFunc 类型(目的是提供 ServerHTTP 接口来执行传入的 Handler)。然后调用 ServeMux.Handle 方法。
  3. ServeMux.Handle 方法会把 URL 和 handler 存在 ServeMux 结构的 m 字段中。

net/http 自建了几个常用的 Handler:FileServer、RedirectHandler 和 NotFoundHandler,例子:

package mainimport ("log""net/http"
)func main() {mux := http.NewServeMux()rh := http.RedirectHandler("http://www.baidu.com", 307)mux.Handle("/foo", rh)log.Println("Listening...")http.ListenAndServe(":3000", mux)
}
  1. 调用了 http.NewServeMux 函数来创建一个空的 ServeMux。
  2. 调用了 http.RedirectHandler 函数创建了一个新的 Handler,这个 Handler 会对收到的所有请求都执行 307 重定向到指定的 URL,如 http://www.baidu.com。
  3. 接下来使用 ServeMux.Handle 函数将 Handler 注册到新建的 ServeMux,所以它在 URL path /foo 上收到所有的请求都交给这个 Handler。
  4. 最后创建了一个新的 ServeHTTP,并通过 http.ListenAndServe 函数监听所有进入的请求,通过传递刚才创建的 ServeMux 来为请求去匹配对应 Handler。

监听并处理请求

http.ListenAndServe 函数用于监听 TCP 连接并处理请求,该函数会使用传入的监听地址和 Handler 初始化一个 HTTP 服务器 http.Server,然后调用该服务器的 http.Server.ListenAndServe 方法:


func ListenAndServe(addr string, handler Handler) error {server := &Server{Addr: addr, Handler: handler}return server.ListenAndServe()
}func (srv *Server) ListenAndServe() error {addr := srv.Addrif addr == "" {addr = ":http"}ln, err := net.Listen("tcp", addr)  // 监听端口if err != nil {return err}return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

http.Server.ListenAndServe 方法使用了 net 库提供的 net.Listen 函数监听对应地址上的 TCP 连接并通过 http.Server.Serve 处理客户端的请求。http.Server.Serve 会在循环中监听外部的 TCP 连接并为每个连接调用 http.Server.newConn 创建新的结构体 http.conn,它是 HTTP 连接的服务端表示:

func (srv *Server) Serve(l net.Listener) error {// ...for {rw, e := l.Accept() // 等待请求//...c := srv.newConn(rw)  // 这里创建了一个 conn 结构体,它代表一个连接c.setState(c.rwc, StateNew) // before Serve can returngo c.serve(ctx)}
}

创建了服务端的连接之后,net/http 中的实现会为每个 HTTP 请求创建单独的 Goroutine 并在其中调用 http.Conn.serve 方法,如果当前 HTTP 服务接收到了海量的请求,会在内部创建大量的 Goroutine,这可能会使整个服务质量明显降低无法处理请求。

func (c *conn) serve(ctx context.Context) {c.remoteAddr = c.rwc.RemoteAddr().String()ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())ctx, cancelCtx := context.WithCancel(ctx)c.cancelCtx = cancelCtxdefer cancelCtx()c.r = &connReader{conn: c}c.bufr = newBufioReader(c.r)c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)for {w, _ := c.readRequest(ctx)serverHandler{c.server}.ServeHTTP(w, w.req)w.finishRequest()...}
}

上述代码片段是简化后的连接处理过程,其中包含读取 HTTP 请求、调用 Handler 处理 HTTP 请求以及调用完成该请求。读取 HTTP 请求会调用 http.Conn.readRequest 方法,该方法会从连接中获取 HTTP 请求并构建一个实现了 http.ResponseWriter 接口的变量 http.response,向该结构体写入的数据都会被转发到它持有的缓冲区中:

func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) {...w.written += int64(lenData)if w.contentLength != -1 && w.written > w.contentLength {return 0, ErrContentLength}if dataB != nil {return w.w.Write(dataB)} else {return w.w.WriteString(dataS)}
}

解析了 HTTP 请求并初始化 http.ResponseWriter 之后,我们就可以调用 http.serverHandler.ServeHTTP 方法查找处理器来处理 HTTP 请求了:

type serverHandler struct {srv *Server
}func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {handler := sh.srv.Handlerif handler == nil {handler = DefaultServeMux}if req.RequestURI == "*" && req.Method == "OPTIONS" {handler = globalOptionsHandler{}}handler.ServeHTTP(rw, req)
}

conn.serve 调用了 serverHandler 的 ServeHTTP 方法,如果我们没有手动设置新的 ServerMux 的话,那么这个方法会调用DefaultServeMux 的 ServeHTTP 方法处理外部的 HTTP 请求。

到目前为止,我们已经知道实现 ServeHTTP 的类型有两个:ServerMux 和 HandlerFunc。

  • HandlerFunc 结构体:实现了 Handler 接口,开发者编写的 Handler 都会被转换为 HandlerFunc 类型,它的 ServeHTTP 方法会执行我们注册的 Handler,即:“处理器”。
  • ServeMux 结构体:实现了 Handler 接口,它的 m 字段用于存储我们注册的 Handler,它的 ServeHTTP 方法提供了路由功能,即: “多路复用器”。
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {h, _ := mux.Handler(r)  // 获取对应的 Handlerh.ServeHTTP(w, r)       // 执行 Handler
}

经过一系列的函数调用,上述过程最终会调用 HTTP 服务器的 http.ServerMux.match 方法,该方法会遍历前面注册过的路由表并根据特定规则进行匹配:

func (mux *ServeMux) match(path string) (h Handler, pattern string) {v, ok := mux.m[path]if ok {return v.h, v.pattern}for _, e := range mux.es {if strings.HasPrefix(path, e.pattern) {return e.h, e.pattern}}return nil, ""
}

如果请求的路径和路由中的表项匹配成功,就会调用表项中对应的 Handler,Handler 中包含的业务逻辑会通过 http.ResponseWriter 构建 HTTP 请求对应的响应并通过 TCP 连接发送回客户端。

HTTP 服务器的可扩展性

net/http 服务器的可扩展性体现在 3 个方面。

自定义 Handler,同时使用默认的 DefaultServeMux 和默认的 Server 的结构

package mainimport ("io""log""net/http"
)func main() {// 设置路由规则http.HandleFunc("/", Tmp)// 使用默认的 DefaultServeMuxerr := http.ListenAndServe(":8080", nil)if err != nil {log.Fatal(err)}
}func Tmp(w http.ResponseWriter, r *http.Request) {io.WriteString(w, "version 1")
}

自定义 Handler 和 ServeMux,使用默认的 Server 的结构

package mainimport ("io""log""net/http"
)type myHandler struct{}func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {io.WriteString(w, "URL:"+r.URL.String())
}func Tmp(w http.ResponseWriter, r *http.Request) {io.WriteString(w, "version 2")
}func main(){mux := http.NewServeMux()mux.Handle("/", &myHandler{})  mux.HandleFunc("/tmp", Tmp)err = http.ListenAndServe(":8080", mux)if err != nil {log.Fatal(err)}
}

自定义 Handler、ServeMux 和 Server

package mainimport ("io""log""net/http""time"
)var mux map[string]func(http.ResponseWriter, *http.Request) func main(){server := http.Server{Addr: ":8080",Handler: &myHandler{},ReadTimeout: 5*time.Second,}mux = make(map[string]func(http.ResponseWriter, *http.Request))mux["/tmp"] = Tmperr := server.ListenAndServe()if err != nil {log.Fatal(err)}
}type myHandler struct{}func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){// 实现路由的转发if h, ok := mux[r.URL.String()];ok{// 用这个 Handler 来实现路由转发,相应的路由调用相应 funch(w, r)return}io.WriteString(w, "URL:"+r.URL.String())
}func Tmp(w http.ResponseWriter, r *http.Request) {io.WriteString(w, "version 3")
}

将函数作为处理器

对于简单的情况(比如上面的例子),定义个新的有 ServerHTTP 方法的自定义类型有些累赘。

实际上,任何具有 func(http.ResponseWriter, *http.Request) 签名的函数都能转化为一个 HandlerFunc 类型。这很有用,因为 HandlerFunc 对象内置了 ServeHTTP 方法,后者可以聪明又方便的调用我们最初提供的函数内容。

package mainimport ("log""net/http""time"
)func timeHandler(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(time.RFC1123)w.Write([]byte("The time is: " + tm))
}func main() {mux := http.NewServeMux()// Convert the timeHandler function to a HandlerFunc typeth := http.HandlerFunc(timeHandler)// And add it to the ServeMuxmux.Handle("/time", th)log.Println("Listening...")http.ListenAndServe(":3000", mux)
}

实际上,将一个函数转换成 HandlerFunc 后注册到 ServeMux 是很普遍的用法,所以 Golang 为此提供了个便捷的 ServerMux.HandlerFunc 方法:

package mainimport ("log""net/http""time"
)func timeHandler(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(time.RFC1123)w.Write([]byte("The time is: " + tm))
}func main() {mux := http.NewServeMux()mux.HandleFunc("/time", timeHandler)log.Println("Listening...")http.ListenAndServe(":3000", mux)
}

再进一步的优化,如果我们想从 main() 函数中传递一些信息或者变量给 Hander 函数,一个优雅的方式是将我们 Hander 放到一个闭包中,将我们要使用的变量带进去:

package mainimport ("log""net/http""time"
)func timeHandler(format string) http.Handler {fn := func(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(format)w.Write([]byte("The time is: " + tm))}return http.HandlerFunc(fn)
}func main() {mux := http.NewServeMux()th := timeHandler(time.RFC1123)mux.Handle("/time", th)log.Println("Listening...")http.ListenAndServe(":3000", mux)
}

Go 语言编程 — net/http — HTTP 服务端相关推荐

  1. Netty的Socket编程详解-搭建服务端与客户端并进行数据传输

    场景 Netty在IDEA中搭建HelloWorld服务端并对Netty执行流程与重要组件进行介绍: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article ...

  2. Socket网络编程(2)--服务端实现

    中秋了,首先祝大家中秋快乐,闲着无事在家整一个socket的聊天程序,有点仿QQ界面,就是瞎折腾,不知道最后是不是能将所有功能实现. 如果你对socket不了解,请看这篇文章:http://www.c ...

  3. c# WINFORM SOCKET编程-简单聊天程序(服务端)

    初学C#的SOCKET编程,照着网上的代码写下来,程序总是有问题,经过自己长时间的调试,完成程序,以下是原码,有需要可以参考一下,还不完善,欢迎大家批评指正.(这里的代码没更新,附件重新上传更新,在另 ...

  4. C语言实现UDP网络通信(附服务端和客服端完整源码)

    C语言实现UDP网络通信 服务端源码 客户端源码 服务端源码 #ifdef _WIN32 #define _WINSOCK_DEPRECATED_NO_WARNINGS #define close c ...

  5. C语言实现TCP网络通信(附服务端和客服端完整源码)

    C语言实现TCP网络通信 服务端源码 客户端源码 服务端源码 #include <stdio.h> #include <stdlib.h> #include <strin ...

  6. 前端3DOM编程3——Ajax和服务端通信

    1.通信基础    2.HTTP协议    3.Ajax    4.cookie 六.Ajax和服务端通信 1.通信基础 (1)Web服务器搭建 服务器是一台24h不断电.不关机,提供某种服务(文件. ...

  7. python实现socket编程,客户端和服务端之间互相对话(二)

    首先运行服务端,处于监听状态: 最后运行客户端,就可以实现服务端和客户端之间互相发送消息. 客户端: import os import cv2 import socketremote_IP='127. ...

  8. [C语言]一个很实用的服务端和客户端进行UDP通信的实例

    前段时间发了个TCP通信的例子,现在再来一个UDP通信的例子.这些可以作为样本程序,用到开发中."裸写"socket老是记不住步骤,经常被鄙视-- 下面的例子很简单,写一个UDP的 ...

  9. java基础—网络编程——TCP客户端与服务端交互

    import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; import ja ...

最新文章

  1. 计算机应届生必读的 AI 入门经典书单
  2. mfc窗口右下角如何显示一个三角形图案_大型建筑,如何做到室内外设计元素统一?...
  3. 微服务架构下分布式事务解决方案——阿里GTS
  4. 《Java和Android开发实战详解》——2.2节构建Java应用程序
  5. electronjs设置宽度_Js操作DOM元素及获取浏览器高宽的简单方法
  6. vs2010 打开项目卡死问题解决办法
  7. 完美且精准的 IE10- 版本检测。
  8. 超级搜索术4-学业有成/职场晋升
  9. Python办公自动化——发票开具明细汇总
  10. python方法怎么调用_python函数怎么调用自身?
  11. [日常] Go语言圣经前言
  12. Wps格式怎么转换成word,只要三分钟轻松搞定
  13. 找最大ASCII字符
  14. yolov5的anchor详解
  15. cncrypt安卓版_CnCrypt Protect
  16. 闪存,ROM,Nor Flash,NAND Flash
  17. Nokia NBU备份文件查看工具(包含联系人和短信)
  18. dede织梦如何去除网站底部的版权信息
  19. 【TCP 协议2】确认应答、超时重传机制
  20. python遗传算法排课表_遗传算法与Python图解

热门文章

  1. Xamarin.Forms中为WebView指定数据来源Source
  2. Wireshark网络分析实例集锦(大学霸内部资料)
  3. __weak与__block的区别
  4. java三大集合_java中三大集合框架
  5. 不同分类算法的优缺点是什么?(值得推荐)
  6. 计算机房电磁辐射防护,计算机房电磁屏蔽
  7. 这个赛车AI不再只图一时爽,学会了考虑长远策略
  8. 前NASA工程师让钢琴开口说英文,还能自弹世界上最难曲目,快到冒烟
  9. 真的有能开光追的手游了!自带实机演示的那种,OPPO这次玩“大”了
  10. 这年头,机器翻译都会通过文字脑补画面了 | NAACL 2021