golang提供的网络编程库对于开发者来说十分易用,使用golang能轻松写出可靠、高效的web应用。并且能够让使用者清晰地理解web应用的构成及基本原理。
什么是web应用
单从狭义的角度来看,web应用应该包含以下三大最基本的功能:
- 能够接收从客户端发来的HTTP请求报文。
- 能够对HTTP请求报文进行处理。
- 能够将结果通过HTTP响应报文发送回客户端。
对于这三大基本功能,一个完整的web应用应包含:多路复用器(multiplexer)与控制器(handler)。
多路复用器
当请求到来时,多路复用器会根据路径将请求送到事先绑定好的处理器,交由处理器来处理HTTP请求报文。
1 | mux := http.NewServeMux() //构造多路复用器mux |
让我们来看下多路复用器的类型ServeMux
。
1 | type ServeMux struct { |
可以看到,ServeMux
结构体里包含了三部分内容,一把读写锁mu
、一个map类型m
以及一个布尔值hosts
。
map里存储的就是路径与处理器的映射。由于map不是线程安全的,所以需要一把读写锁来对map进行保护。hosts我们先不讨论。
多路复用器ServeMux类型实现了ServeHTTP方法,因此多路复用器其本质上也是一个处理器。
1 | func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) { |
为什么要将多路复用器与处理器分开呢?我想是因为一下两点原因:
- 职责划分不通:多路复用器是一个总索引,主要的职责是将路径与处理器绑定起来。而处理器主要是针对不通的业务进行处理。
- 代码清晰:将不通的处理器单独写,并由多路复用器汇总,这样代码逻辑清晰,便于维护。
除了net/http
包中的ServeMux
。还有很多各有特色的多路复用器,最常见的有mux
和HttpRouter
。
处理器
一个类型实现了ServeHTTP(w ResponseWriter, r *Request)
函数就能成为一个简单的处理器。其中Request
是客户端发来的请求,ResponseWriter
则是给客户端的回复。
此外,处理器可以通过串联的方式来进行非侵入的扩展。
1 | type content string |
代码中logUtil
就是一个用来给处理器进行扩展的函数。logUtil
函数传入一个http.Handler
,返回一个http.Handler
。因此,可以将此类的函数进行串联,为处理器提供功能丰富的扩展。
监听
可以通过http.Server
结构体类型来设置web应用的一些参数,并使用ListenAndServe()
函数来启动web应用。
1 | server := http.Server{ |
当然,我们仅需要关注我们感兴趣的那些参数,不必对每个参数进行设置,绝大多数参数都具有默认值。例如在http.Server
结构体类型中如果不指定处理器的话,默认会使用http.DefaultServeMux
来作为处理器。
同样的,我们可以为http.Server
设置一个处理器来替代默认的处理器:
1 | //无论遇到什么请求都只会返回hello |
在调用ListenAndServe()
之后web应用便启动起来了,它的本质其实就是一个死循环,在不断获取监听到的请求并交由处理器处理。
在ListenAndServe()
中调用了Server
类型的Serve(l net.Listener)
函数。web应用的死循环就是写在Serve
函数中的。
在Serve
函数中使用无条件的for循环获取监听的连接,将连接以goroutine的方式交给conn
类型的serve(ctx context.Context)
函数处理。最后通过在serve
函数中调用Handler接口的ServeHTTP
函数来处理具体的业务逻辑。