Go实现独立的Web服务器

一. 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 :9090/ 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 { musync.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 theyre speaking plaintext HTTP and write a // 400 response on the TLS conns 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服务这才是初步探索,还要有路由解析、中间件等更多的组件进行封装,敬请期待后续的探索研究。