Golang HTTP 服务器

07-19 1093阅读

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 客户端

      步骤:

      1. 创建客户端
      2. 发起请求
      3. 处理服务器响应
      4. 关闭连接
      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"))
        })
        
        创建路由
        1. 首先进入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 接口的实现函数

        1. 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))
        }
        
        1. 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 的全部属性

        关系 :

        1. 一个 server 可以接受多个请求
        2. 每个客户端又可以和 server 建立多个连接,并且每个连接又可以发送很多次请求

        所以关系都是一对多的

        • Addr 服务监听的地址 “IP + Port” ,默认是 80

        • Handler 路由处理器,没有指定的话,默认走 http.DefaultServeMux

        • TLSconfig TLS的相关配置,如果要进行 https 的请求的时候使用

        • ReadTimeoout 读超时,客户端向服务端的管道,请求

        • WriteTimeout 写超时,服务端行客户单的管道,响应

          Golang HTTP 服务器

          监听端口
          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))
              }
              
VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]