七叶笔记 » golang编程 » Go实现独立的Web服务器

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' '#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服务这才是初步探索,还要有路由解析、中间件等更多的组件进行封装,敬请期待后续的探索研究。

相关文章