Golang HTTP 服务器
Http 服务器
用 Go实现一个 http server 非常容易,Go 语言标准库 net/http 自带了一系列结构和方法来帮助开发者简化 HTTP 服务开发的相关流程。因此,我们不需要依赖任何第三方组件就能构建并启动一个高并发的 HTTP 服务器。
1. 简单的 HTTP 服务器
1.1 http 服务端
// http.ListenAndServe func ListenAndServe(addr string, handler Handler) error
用于启动HTTP服务器,监听addr,并使用handler来处理请求。返回启动错误。其中:
-
addr,TCP address,形式为 IP:port,IP省略表示监听全部网络接口
-
handler,经常的被设置为 nil,表示使用DefaultServeMux(默认服务复用器)来处理请求。
-
DefaultServe Mux要使用以下两个函数来添加请求处理器
-
func Handle(pattern string, handler Handler)
-
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
func Http() { // 一: 设置不同路由对应的不同处理器 // 第一种方式: HandleFunc http.HandleFunc("/ping", HandlerPing) // 第二种方式: Handle // 需要结构体,以及实现方法ServerHTTP Info := InfoHandle{Info: "Hello , Sakura"} http.Handle("/info", Info) // 二:启动监听 addr := ":8089" log.Println("HTTP 服务器正在监听: ", addr) if err := http.ListenAndServe(addr, nil); err != nil { log.Fatalln(err) } } func HandlerPing(resp http.ResponseWriter, req *http.Request) { resp.Write([]byte("Pong")) } // Handle第二个参数需要结构体,并且实现ServerHTTP type InfoHandle struct { Info string } func (info InfoHandle) ServeHTTP(resp http.ResponseWriter, req *http.Request) { resp.Write([]byte(info.Info)) }
其中:Handler 接口的定义为:
type Handler interface { ServeHTTP(ResponseWriter, *Request) } //通过创建结构体 InfoHandler 实现 Handler接口,可以作为 http.Handle()的第二个参数来使用
1.2 http 客户端
步骤:
- 创建客户端
- 发起请求
- 处理服务器响应
- 关闭连接
func Client() { // 1.创建客户端 client := &http.Client{} // 2.创建请求 req, err := http.NewRequest("GET", "http://localhost:8080/sakura", nil) if err != nil { panic(err) return } // 3.发送请求 resp, err := client.Do(req) if err != nil { panic(err) } defer resp.Body.Close() // 4.处理服务器响应resp all, err := io.ReadAll(resp.Body) fmt.Println(string(all)) }
因为 Get 和 Post 比较常用,所以 net/http 包里本身封装了 Get 和 Post 请求,不过其他的请求比如说 Delete,Put 就没有了
func main() { // 1.Get 请求 http.Get("http://localhost:8080/sakura") buffer := bytes.NewBuffer([]byte{}) // 2.Post 请求 resp, err := http.Post("http://localhost:8080/service","application/json",buffer) if err != nil { return } fmt.Println(resp) }
2. 复杂的 HTTP 服务器
定制性的 HTTP 服务器,通过 Server 类型进行设置。其定义如下:
// net/http type Server struct { // TCP Address Addr string Handler Handler // handler to invoke, http.DefaultServeMux if nil // LSConfig optionally provides a TLS configuration for use // by ServeTLS and ListenAndServeTLS TLSConfig *tls.Config // 配置 HTTPS 相关 , 证书... // 读请求超时时间 ReadTimeout time.Duration // 读请求头超时时间 ReadHeaderTimeout time.Duration // 写响应超时时间 WriteTimeout time.Duration // 空闲超时时间 IdleTimeout time.Duration // Header最大长度 MaxHeaderBytes int // 其他字段略 }
该类型的 func (srv *Server) ListenAndServe() error 函数用于监听和服务
func CustomerHTTPServer() { // 一: 定义Server类型数据,指定配置 addr := ":8089" Handle := CustomHandle{Info: "Custom Server "} server := http.Server{ Addr: addr, Handler: Handle, ReadTimeout: 3 * time.Second, ReadHeaderTimeout: 3 * time.Second, WriteTimeout: 3 * time.Second, MaxHeaderBytes: 1 log.Fatalln(err) } } type CustomHandle struct { Info string } func (info CustomHandle) ServeHTTP(writer http.ResponseWriter, request *http.Request) { fmt.Fprintf(writer, info.Info) } // 注册路由 // 1.pattern: 路由规则 // 2.handler: 处理器函数(回调函数) http.HandleFunc("/sakura", func(writer http.ResponseWriter, request *http.Request) { writer.Write([]byte("hello,sakura")) }) // 启动服务 // 1.addr: 监听地址 host:port // 2.handler: 处理HTTP请求的处理器,默认为http.DefaultServeMux=ServeMux err := http.ListenAndServe(":8080", nil) panic(err) }
路由:底层使用切片 []slice 存储,按照从长到短存储,
- 匹配优先全字匹配,eg,/sakura 不会匹配 /sakura/book
- 没有匹配到的路由使用根目录进行兼容,eg,/sakura/info 会匹配到 /
http.HandleFunc("/sakura", func(writer http.ResponseWriter, request *http.Request) { writer.Write([]byte("hello,sakura")) }) http.HandleFunc("/sakura/book", func(writer http.ResponseWriter, request *http.Request) { writer.Write([]byte("hello,sakura")) }) http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { writer.Write([]byte("hello,sakura")) })
创建路由
- 首先进入http.HandleFunc() ,一直追入源码可以发现,HandlerFunc 类型实现了 Handler 接口
// 这个类型实现了Handler接口 type HandlerFunc func(ResponseWriter, *Request) // ServeHTTP calls f(w, r). func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) { f(w, r) } // Handler 接口 type Handler interface { ServeHTTP(ResponseWriter, *Request) }
也就是说, http.HandleFunc() 中的函数最终被封装为了一个 Handler 接口的实现函数
http.HandleFunc("/sakura", func(writer http.ResponseWriter, request *http.Request) { writer.Write([]byte("hello,sakura")) })
而http.ListenAndServe(“:8080”, nil) 中的 Hander 类型,HandlerFunc 实现的接口类型
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() } // ↓ // Handler 接口 type Handler interface { ServeHTTP(ResponseWriter, *Request) }
总结:
ListenAndServe() 的第二个参数,可以自行定义类型实现接口,默认使用 http.DefaultServeMux
HandleFunc() 中的第二个参数,可以直接指定函数,然后会将该函数封装为一个 Handler 接口的实现函数
- HanleFunc() 的底层处理,需要自己实现的 Handler 和 直接传入函数封装为 HandleFunc 的 Handler
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) } // ↓ func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) } // ↓ // DefaultServeMux 在源码上面定义了,就是ServeMux var DefaultServeMux = &defaultServeMux var defaultServeMux ServeMux // 多路复用器 type ServeMux struct { // HTTP 请求多路复用器:路由器 mu sync.RWMutex // 读写锁:可共享读,不可共享写;写时不读,读时不写(排它锁) m map[string]muxEntry // key: pattern; value: {Handler, pattern} es []muxEntry // entries切片,按URL从长到短排序,方便匹配到最佳路由 hosts bool // pattern中是否有主机名 }
这个 handler 是以参数的形式传过去的
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, handler) }
而 DefaultServeMux.HandleFunc() 中的 handle,由于 HanlerFunc 是一个定义的函数类型
HanlerFunc(handler) : 相当于将 handler 封装为 HanlerFunc,使得自己传入的函数,也实现了 Handler 这个接口
// HandleFunc registers the handler function for the given pattern. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) { if handler == nil { panic("http: nil handler") } mux.Handle(pattern, HandlerFunc(handler)) }
- mux.Handle 路由绑定
func (mux *ServeMux) Handle(pattern string, handler Handler) { mux.mu.Lock() // 加锁 defer mux.mu.Unlock() // pattern 的路径 if pattern == "" { panic("http: invalid pattern") } // handler 为nil if handler == nil { panic("http: nil handler") } // 路由重复 if _, exist := mux.m[pattern]; exist { panic("http: multiple registrations for " + pattern) } if mux.m == nil { mux.m = make(map[string]muxEntry) // 初始化map } e := muxEntry{h: handler, pattern: pattern} // 完成路由与处理器的映射 mux.m[pattern] = e if pattern[len(pattern)-1] == '/' { // 最后一个字符如果是'/'的话, mux.es = appendSorted(mux.es, e) //将新路由放到正确的位置,从长到短 } if pattern[0] != '/' { //如果路由不是以/开头,例如127.0.0.1:8080/sakura mux.hosts = true //将 mux.hosts置为true } }
创建服务器
// ListenAndServe always returns a non-nil error. func ListenAndServe(addr string, handler Handler) error { // 在这里创建服务器 server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
通过源码可以看到 server 的全部属性
关系 :
- 一个 server 可以接受多个请求
- 每个客户端又可以和 server 建立多个连接,并且每个连接又可以发送很多次请求
所以关系都是一对多的
-
Addr 服务监听的地址 “IP + Port” ,默认是 80
-
Handler 路由处理器,没有指定的话,默认走 http.DefaultServeMux
-
TLSconfig TLS的相关配置,如果要进行 https 的请求的时候使用
-
ReadTimeoout 读超时,客户端向服务端的管道,请求
-
WriteTimeout 写超时,服务端行客户单的管道,响应
监听端口
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} // 服务器创建完成之后,启动监听 return server.ListenAndServe() } // 启动 TCP 监听 func (srv *Server) ListenAndServe() error { if srv.shuttingDown() { return ErrServerClosed } addr := srv.Addr if addr == "" { addr = ":http" } // 创建TCp监听器 ln, err := net.Listen("tcp", addr) if err != nil { return err } // 提供服务 return srv.Serve(ln) }
- 核心方法 Serve()
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() // 都是细节判断.... ctx := context.WithValue(baseCtx, ServerContextKey, srv) // 核心 // 一个监听可以创建多个连接 for { // 等待客户端建立连接,如果没有连接,会被阻塞 rw, err := l.Accept() // 监听的过程中出现错误之后,进行处理,尝试重新连接.. if err != nil { // .............. } connCtx := ctx if cc := srv.ConnContext; cc != nil { connCtx = cc(connCtx, rw) if connCtx == nil { panic("ConnContext returned nil") } } tempDelay = 0 // 如果有客户端发送了请求,将Accetp()中得到的连接进行一个封装 c := srv.newConn(rw) c.setState(c.rwc, StateNew, runHooks) // before Serve can return // 连接成功,新建协程提供服务 go c.serve(connCtx) } }
启动协程提供服务的时机是,TCP 连接之后,TLS 连接(握手)之前。在协程里面进行 TLS 握手
- 最核心方法 serve()
方法前半段: 处理关闭连接和 TLS 握手
// Serve a new connection. func (c *conn) serve(ctx context.Context) { if ra := c.rwc.RemoteAddr(); ra != nil { c.remoteAddr = ra.String() } ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr()) var inFlightResponse *response defer func() { // 关闭连接之后的首尾工作,异常处理,日志,连接状态,钩子函数 // ... }() if tlsConn, ok := c.rwc.(*tls.Conn); ok { // tls 握手(非常复杂) } //.. }
方法后半段:真正处理连接的请求
一个监听器可以创建多个连接,一个连接可以创建多个请求
// HTTP/1.x from here on. ctx, cancelCtx := context.WithCancel(ctx) c.cancelCtx = cancelCtx defer cancelCtx() c.r = &connReader{conn: c} c.bufr = newBufioReader(c.r) c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4 // 读取请求 w, err := c.readRequest(ctx) // 如果读取到了请求,设置状态,防止被关闭连接 if c.r.remain != c.server.initialReadLimitSize() { // If we read any bytes off the wire, we're active. c.setState(c.rwc, StateActive, runHooks) } // 请求错误处理 if err != nil { const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n" switch { // 431 客户端文件太大 case err == errTooLarge: const publicErr = "431 Request Header Fields Too Large" fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr) c.closeWriteAndWait() return // 无法识别客户端的编码 case isUnsupportedTEError(err): code := StatusNotImplemented // We purposefully aren't echoing back the transfer-encoding's value, // so as to mitigate the risk of cross side scripting by an attacker. fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders) return case isCommonNetReadError(err): return // don't reply // 默认错误,会根据状态码响应对应的错误信息 // 例如404 not found | 400 bad request default: if v, ok := err.(statusError); ok { //... } } // Expect 100 Continue support // ... c.curReq.Store(w) // 将请求与响应实例 w绑定,回写客户端 // .... inFlightResponse = w serverHandler{c.server}.ServeHTTP(w, w.req) // 调用handler处理请求 inFlightResponse = nil w.cancelCtx() if c.hijacked() { return } w.finishRequest() // 请求完成,刷新response缓存 c.rwc.SetWriteDeadline(time.Time{}) if !w.shouldReuseConnection() { //尝试复用TCP连接 if w.requestBodyLimitHit || w.closedRequestBodyEarly() { c.closeWriteAndWait() } return } c.setState(c.rwc, StateIdle, runHooks) // 将连接置为空闲状态 c.curReq.Store(nil) // 响应实例置为空,便于下一次返回响应 // http/1.1 持久连接,客户端可以继续发送下个报文 if !w.conn.server.doKeepAlives() { // We're in shutdown mode. We might've replied // to the user without "Connection: close" and // they might think they can send another // request, but such is life with HTTP/1.1. return } if d := c.server.idleTimeout(); d != 0 { c.rwc.SetReadDeadline(time.Now().Add(d)) } else { c.rwc.SetReadDeadline(time.Time{}) } // 读取请求缓存,看看那是否有数据 if _, err := c.bufr.Peek(4); err != nil { return } // 设置截止时间,不截止,进入下次循环 c.rwc.SetReadDeadline(time.Time{}) } } // 1.创建客户端 client := &http.Client{} // 2.创建请求 req, err := http.NewRequest("GET", "http://localhost:8080/sakura", nil) if err != nil { panic(err) return } // 3.发送请求 resp, err := client.Do(req) if err != nil { panic(err) } defer resp.Body.Close() // 4.处理服务器响应resp all, err := io.ReadAll(resp.Body) fmt.Println(string(all)) } // 客户端发送请求,服务端响应,称为一个往返 // 执行http请求的机制 // 请求到响应一整个流程的主函数 - RoundTrip // 如果为nil,则使用 DefaultTransport Transport RoundTripper // 检查是否需要重定向 CheckRedirect func(req *Request, via []*Request) error // Cookie包 Jar CookieJar // 超时时间 Timeout time.Duration } // 执行请求和响应的这个流程 RoundTrip(*Request) (*Response, error) } // 从上下文中找到代理环境 Proxy: ProxyFromEnvironment, // 拨号 DialContext: defaultTransportDialContext(&net.Dialer{ Timeout: 30 * time.Second, // 超时时间 KeepAlive: 30 * time.Second, // 保活时间 }), ForceAttemptHTTP2: true, // 尝试连接http2 MaxIdleConns: 100, // 最大空闲连接 IdleConnTimeout: 90 * time.Second, // 空闲超时时间 TLSHandshakeTimeout: 10 * time.Second, // TLS 超时时间 ExpectContinueTimeout: 1 * time.Second, // 100状态码超时时间 } // 真正的Transport type Transport struct { idleMu sync.Mutex closeIdle bool // 用户请求关闭所有的闲置连接 idleConn map[connectMethodKey][]*persistConn // 每个host对应的闲置连接列表 idleConnWait map[connectMethodKey]wantConnQueue // 每个host对应的等待闲置连接列表,在其它request将连接放回连接池前先看一下这个队列是否为空,不为空则直接将连接交由其中一个等待对象 idleLRU connLRU // 用来清理过期的连接 reqMu sync.Mutex reqCanceler map[*Request]func(error) connsPerHostMu sync.Mutex connsPerHost map[connectMethodKey]int // 每个host对应的等待连接个数 在当前主机/Client/Transport/连接池实体 中,等待获取连接的队列集合 map:[key:请求方法, value:使用此方法发送请求的等待队列] connsPerHostWait map[connectMethodKey]wantConnQueue // 每个host对应的等待连接列表 // 用于指定创建未加密的TCP连接的dial功能,如果该函数为空,则使用net包下的dial函数 DialContext func(ctx context.Context, network, addr string) (net.Conn, error) Dial func(network, addr string) (net.Conn, error) // 以下两个函数处理https的请求 DialTLSContext func(ctx context.Context, network, addr string) (net.Conn, error) DialTLS func(network, addr string) (net.Conn, error) DisableKeepAlives bool // 是否复用连接 DisableCompression bool // 是否压缩 MaxIdleConns int // 总的最大闲置连接的个数 MaxIdleConnsPerHost int // 每个host最大闲置连接的个数 MaxConnsPerHost int // 每个host的最大连接个数,如果已经达到该数字,dial连接会被block住 IdleConnTimeout time.Duration // 闲置连接的最大等待时间,一旦超过该时间,连接会被关闭 ResponseHeaderTimeout time.Duration // 读超时,从写完请求到接受到返回头的总时间 ExpectContinueTimeout time.Duration // Expect:100-continue两个请求间的超时时间 MaxResponseHeaderBytes int64 // 返回中header的限制 WriteBufferSize int // write buffer的使用量 ReadBufferSize int // read buffer的使用量 } // 创建连接池 transport := &http.Transport{ DialContext: (&net.Dialer{ Timeout: 30 * time.Second, //连接超时 KeepAlive: 30 * time.Second, //探活时间 }).DialContext, ForceAttemptHTTP2: true, MaxIdleConns: 100, //最大空闲连接 IdleConnTimeout: 90 * time.Second, //空闲超时时间 TLSHandshakeTimeout: 10 * time.Second, //tls握手超时时间 ExpectContinueTimeout: 1 * time.Second, //100-continue状态码超时时间 } // 创建客户端 client := &http.Client{ Timeout: time.Second * 30, Transport: transport, } // 请求数据 resp, err := client.Get("http://127.0.0.1:9527/hello") defer resp.Body.Close() if err != nil { panic(err) } // 读取内容 bds, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) } fmt.Println(string(bds)) }
- 最核心方法 serve()
- 核心方法 Serve()
-