一. Web服务器
说起web服务器,相信大家都比较熟悉,比如Nginx、Apache、Tomcat等,通过代理或者反向代理方式为用户提供服务。如果使用这些组件,则需要部署Web服务器、项目代码等,而且相关配置等一堆,还是比较麻烦的,而且很多功能需要基于网关开发或者在项目代码中支持开发等。那么如果脱离这些Web服务器,我们是否可以实现一个Web服务器,完全自我可控?
二. Go实现Web服务
Golang本身提供了一个比较完善的Http服务的内置包,在业务开发中,只需要在此包基础上就可以实现一个功能丰富、强大的web服务器。
2.1 Golang标准库:net/http
net/http库实现了整套的http服务中的客户端、服务端接口,可以基于此轻松的发起HTTP请求或者对外提供HTTP服务。本期主要介绍基于此包实现对外提供HTTP服务。
server基本介绍
server服务的基本信息
type Server struct {
Addr string // 定义服务监听的地址端口,如果为空,则默认监听80端口
Handler Handler // 请求被处理的业务方,默认 http.DefaultServeMux
TLSConfig *tls.Config // 可选的TLS配置,对外提供https服务
ReadTimeout time.Duration // 读取客户端请求的超时时间,包含读取请求体
ReadHeaderTimeout time.Duration // 读取请求头的超时时间,如果为空,则使用 ReadTimeout, 如果两者都没有,则没有超时时间
WriteTimeout time.Duration // 服务响应的超时时间
IdleTimeout time.Duration // 长链接空闲的超时时间
MaxHeaderBytes int // 客户端请求头的最大大小,默认为1MB
ConnState func(net.Conn, ConnState) // 指定可选的回调方法,当客户端连接状态发生改变时
ErrorLog *log.Logger // 连接错误、handlers异常或者文件系统异常时使用,默认使用标准库的logger接口
onShutdown []func() // 服务停止时触发的方法调用
}
基于以上server结构,Golang标准库提供了如下几个服务接口
func (srv *Server) Close() error // 立即关闭所有的活跃监听以及所有的连接,包括新建的连接、活跃的或者空闲的连接
func (srv *Server) ListenAndServe() error // 启动服务监听tcp连接以及将请求转发到handler中
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error // 支持https服务
func (srv *Server) Shutdown(ctx context.Context) error // 实现优雅关闭连接
2.2 启动首个web服务示例
2.2.1 Web服务示例
最简单的Server:
package main
import (
"fmt"
"net/http"
)
func helloWorldHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello world\n")
}
func main() {
http.HandleFunc("/", helloWorldHandler)
srv := http.Server{
Addr: ":9090",
}
er := srv.ListenAndServe()
if er != nil {
panic("Start Server Failed With " + er.Error())
}
}
启动服务:
go run http_be.go
请求服务:
curl -i -X 'GET' '#39;
HTTP/1.1 200 OK
Date: Sun, 25 Jul 2021 05:43:52 GMT
Content-Length: 12
Content-Type: text/plain; charset=utf-8
Hello world
仅仅20行代码就实现了一个web服务器,那么这中间都发生了什么事情呢?
2.2.2 起源
世界万物的起源都来自于一点,如同单细胞生物进化到现如今五彩斑斓的世界。Web服务的起源也来自于一点,即
srv := http.Server{
Addr: ":9090",
在此处初始化server服务,除显示指定监听端口外,还有一个重要的默认handler参数,即 http.DefaultServeMux ,提供web服务的路由解析功能。即
在此处初始化server服务,除显示指定监听端口外,还有一个重要的默认handler参数,即 http.DefaultServeMux ,提供web服务的路由解析功能。即
http.HandleFunc("/", helloWorldHandler)
以上代码其实等同于如下:
hdler := http.DefaultServeMux
hdler.HandleFunc("/", helloWorldHandler)
srv := http.Server{
Addr: ":9090",
Handler: hdler,
}
http的mux维护了一个路由解析的map表,服务在启动时,所有的请求路由会被解析到这个map表中,其结构体为:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
其中muxEntry 中存储着路由的路径以及对应的处理方法handler。基于此Mux接口,还可以实现更加复杂的路由协议。
2.2.3 请求的处理
er := srv.ListenAndServe()
通过以上简单的一句代码,就实现了服务的监听以及服务,那么他是如何做到的呢?带着疑问,又一次进入如海般的代码中寻找代码。
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
func (srv *Server) Serve(l net.Listener) error {
if fn := testHookServerServe; fn != nil {
fn(srv, l) // call hook with unwrapped listener
}
origListener := l
l = &onceCloseListener{Listener: l}
defer l.Close()
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
if !srv.trackListener(&l, true) {
return ErrServerClosed
}
defer srv.trackListener(&l, false)
baseCtx := context.Background()
if srv.BaseContext != nil {
baseCtx = srv.BaseContext(origListener)
if baseCtx == nil {
panic("BaseContext returned a nil context")
}
}
var tempDelay time.Duration // how long to sleep on accept failure
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(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", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(connCtx)
}
}
可以看出,Golang接收web是基于TCP协议之上,然后就是调用Serve方法,处理连接请求,Serve方法会启动goroutine进行异步处理,这也是高并发的基本所在。
宏观层面查看大致流程如图:
serve方法中以无限循环方式(for)接收客户端请求并进行处理,主要逻辑如下:
if tlsConn, ok := c.rwc.(*tls.Conn); ok {
if d := c.server.ReadTimeout; d != 0 {
c.rwc.SetReadDeadline(time.Now().Add(d))
}
if d := c.server.WriteTimeout; d != 0 {
c.rwc.SetWriteDeadline(time.Now().Add(d))
}
if err := tlsConn.Handshake(); err != nil {
// If the handshake failed due to the client not speaking
// TLS, assume they're speaking plaintext HTTP and write a
// 400 response on the TLS conn's underlying net.Conn.
if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
re.Conn.Close()
return
}
c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
return
}
c.tlsState = new(tls.ConnectionState)
*c.tlsState = tlsConn.ConnectionState()
if proto := c.tlsState.NegotiatedProtocol; validNextProto(proto) {
if fn := c.server.TLSNextProto[proto]; fn != nil {
h := initALPNRequest{ctx, tlsConn, serverHandler{c.server}}
fn(c.server, tlsConn, h)
}
return
}
}
...
serverHandler{c.server}.ServeHTTP(w, w.req)
其中ServeHTTP的实现:
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)
}
在这里终于发现我们定义的路由处理的handler方法。
整体流程如图:
通过以上源码的观看,基本了解了golang处理一次web请求的大体过程。对此,你是否已经有所了解?
本文首次初步探索Golang的web服务的大体过程,提供一个功能强大的web服务这才是初步探索,还要有路由解析、中间件等更多的组件进行封装,敬请期待后续的探索研究。