目 录CONTENT

文章目录

Go网络编程-HTTP程序设计

Sakura
2023-09-07 / 0 评论 / 0 点赞 / 34 阅读 / 16688 字 / 正在检测是否收录...

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(默认服务复用器)来处理请求。

  • DefaultServeMux要使用以下两个函数来添加请求处理器

    • func Handle(pattern string, handler Handler)

    • func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

func Http() {
	// 一: 设置不同路由对应的不同处理器
	// 第一种方式: HandleFunc
	http.HandleFunc("/ping", HandlerPing)

	// 第二种方式: InfoHandleFunc
	// 需要结构体,以及实现方法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(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprintf(writer, "pong")
}

// Handle第二个参数需要结构体,并且实现ServerHTTP
type InfoHandle struct {
	Info string
}

func (info InfoHandle) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	fmt.Fprintf(writer, 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))
}

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 连接(握手)之前

  • 最核心方法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 客户端源码

Transport

Request 和 Response 结构体

获取连接getconn

处理响应roundTrip

0

评论区