Golang HTTP 服务器

avatar
作者
猴君
阅读量:1

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 << 10, // 2 *10 	}  	// 二:启动网络监听 	fmt.Println("启动自定义HTTP服务器,监听端口为: ", addr) 	err := server.ListenAndServe() 	if err != nil { 		log.Fatalln(err) 	} }  type CustomHandle struct { 	Info string }  func (info CustomHandle) ServeHTTP(writer http.ResponseWriter, request *http.Request) { 	fmt.Fprintf(writer, info.Info) } 

3. 分析源码

3.1 服务端源码

func Server() { 	// 注册路由 	// 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 写超时,服务端行客户单的管道,响应
    在这里插入图片描述

监听端口
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<<10)  	// 一个连接对应多个请求,所以这里使用for 	for { 		// 读取请求 		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{}) 	} } 

3.2 客户端源码

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)) } 
  • 首先查看 client 结构体
type Client struct { 	// 客户端发送请求,服务端响应,称为一个往返 	// 执行http请求的机制 	// 请求到响应一整个流程的主函数 -> RoundTrip 	// 如果为nil,则使用 DefaultTransport 	Transport RoundTripper 	// 检查是否需要重定向 	CheckRedirect func(req *Request, via []*Request) error 	// Cookie包 	Jar CookieJar 	// 超时时间 	Timeout time.Duration } 
type RoundTripper interface { 	// 执行请求和响应的这个流程 	RoundTrip(*Request) (*Response, error) } 

Transport

  • DefaluTransport

client 相当于浏览器, Transport 就相当于连接池

// 默认Transport var DefaultTransport RoundTripper = &Transport{ 	// 从上下文中找到代理环境 	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 中的字段,并且完成 Client 的初始化
func main() { 	// 创建连接池 	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)) } 

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!