44

net/http - 学习

 4 years ago
source link: https://www.tuicool.com/articles/RVVVzqm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
  1. 什么是 函数登记中心
  2. 登记中心里的处理函数是什么
  3. 登记中心里的处理工具是什么
  4. 登记中心里的内部结构是什么样的
  5. 取出服务中心的处理工具→登记中心
  6. 登记中心找出匹配的处理函数,处理请求

go语言大部分时候,作为后端出现. 那么它的最基本行为就是关于"http请求"的发送与接收. 用久了难免会想知道这里面到底是怎么工作的, 怎么它就能接收请求了, 怎么就能发送请求了.

下面这个片段是一个简单的小服务器, 我们从下面这个简单的小函数开始, 描述一下在这其中里面到底都发生了什么.

func handleRequest(w http.ResponseWriter, r *http.Request) {
    // ..
}

func main() {
    http.HandleFunc("/", handleRequest)
    _ = http.ListenAndServe(":8099", nil)
}
复制代码

函数登记中心是什么 - ServerMux

  • Server → 服务器
  • Mux → multi-plexer,多路选择器

二者组合在一起,表示一个服务器,这个服务器具有多路选择的功能, 能根据不同的情况做出不同的处理 → http请求中链接的路径不同做出不同的反应.

我是这样理解的, 这个选择器是一个登记中心, 执行 http.HandleFunc("<path>", func(ResponseWriter, *Request)) 的时候其实就是往这个登记中心里注册一下这个函数, 稍后服务中心开始工作的时候会拿着登记册里的注册信息, 根据路径找到处理函数,来做出反应.

登记中心里的处理函数是什么 - HandlerFunc

type HandlerFunc func(ResponseWriter, *Request)
复制代码

我们都知道,通过调用第一行的函数,我们可以将一个函数注册进去, 我们要求这个函数可以接收请求并写一个返回值, 做一个能做响应的函数. 只有这样的函数才能往登记中心里注册

登记中心里的处理工具是什么 - Handler

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
func (mux *ServeMux) ServeHTTP(w,r) {}
func (f HandlerFunc) ServeHTTP(w,r) {
	f(w, r)
}
复制代码

处理工具的本质是一个用于处理HTTP请求的工具, 在后面, 我们会给出我们在何时调用处理工具来处理HTTP请求,我们对于这个工具唯一的期望, 就是希望这个"处理工具"能够实现ServeHTTP函数, 做到

r *Request
w Response

ok, 回到上面

  • ServerMux, 登记中心实现了这个函数, 所以登记中心是处理工具
  • HandlerFunc, 实现了这个函数, 所以HandlerFunc是处理工具
    • 并且, 处理函数实现它的方法是直接执行这个函数

事实上, 在服务中心接收到请求的时候, 我们直接找的"处理工具",而不是"登记中心", 只是恰巧, 在这个例子中. 我们用的是"登记中心"

登记中心里的内部结构

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry 
	hosts bool
}

type muxEntry struct {
	h       Handler
	pattern string
}
复制代码
  • muxEntry 登记册中的一个基本单元, h是处理函数, pattern是模式, 也就是对应路径 → 一个单元里记录下一条路径对应的处理函数
  • map[string]muxEntry 这个就是登记册, 其中的string就是路径, 我们会根据路径找出单元, 从而

向登记中心里注册函数过程

// Part-1
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

// Part-2
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	if handler == nil {
		panic("http: nil handler")
	}
	mux.Handle(pattern, HandlerFunc(handler))
}

// Part-3
func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()
	// 做一些验证工作
	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
}
复制代码
  • Part-1 : 注册过程本来应该是针对一个登记中心, 但是这里没有描述具体针对哪个登记中心,因此这时候我们是向默认登记中心: DefaultServeMux 登记, 在后面我们能看到, 在启动服务中心的时候我们会使用这个默认的登记中心
  • Part-2 : 登记中心ServerMux里这样要求, 任何试图成为muxEntry的函数, 必须得是"处理工具"类型, 我们的函数是"处理函数"类型, 也就是"处理工具"类型, 因此能成功生成muxEntry单元而注册
  • Part-3 : 这里我们的读写锁就发挥作用了, 为了防止并发写入而造成不一样的结果, 我们会加锁, 做一些验证工作后, 针对这个路径以及处理函数生成一个登记单元

服务中心开始工作

生成服务中心对象

在已经有了登记中心以后,我们会说说服务中心是怎么开始工作的

_ = http.ListenAndServe(":9090", nil)
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}
复制代码

我们拿着主机+端口信息, Handler=nil 不指定处理工具的方式, 生成一个服务中心, 让这个服务中心开始工作

生成监听器

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(tcpKeepAliveListener{ln.(*net.TCPListener)})
}
复制代码

负责监听的是监听器'ln', 服务中心只有有了监听器才能收集到请求, 我们设置好监听地址,请求类型=tcp → 开始监听

循环收集请求

func (srv *Server) Serve(l net.Listener) error {
	if fn := testHookServerServe; fn != nil {
		fn(srv, l) // call hook with unwrapped listener
	}

	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)

	var tempDelay time.Duration     // how long to sleep on accept failure
	baseCtx := context.Background() // base is always background, per Issue 16220
	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
	for {
		rw, e := l.Accept()
		if e != nil {
			select {
			case <-srv.getDoneChan():
				return ErrServerClosed
			default:
			}
			if ne, ok := e.(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", e, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return e
		}
		tempDelay = 0
		c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(ctx)
	}
}
复制代码

收到新的请求时, 开启一个协程, 生成一个Context上下文用于存储数据, 这个协程负责去读取请求以及做出反馈, 这个函数可以理解为, 只负责接收请求, 每次接收到请求, 就负责找人(协程)处理, 而它自己则回归原位继续等下一个请求

读取请求内容,Server对象开始调用处理工具

func (c *conn) serve(ctx context.Context) {
	for {
		w, err := c.readRequest(ctx)
		serverHandler{c.server}.ServeHTTP(w, w.req)
		w.finishRequest()
	}
}
复制代码

经过简化以后, 这个函数只做三件事:

w, err := c.readRequest()
serverHandler{c.server}.ServeHTTP(w, w.req)
w.finishRequest()

下面我们就开始一层一层的将这个处理请求下发:

找到服务中心的处理工具,登记中心

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{}
    }
    
    // 这个就是服务中心的处理工具, 它要处理HTTP请求
    handler.ServeHTTP(rw, req)
}
复制代码

从这里开始, 我们已经有了请求里的正文, 我们接下来开始找Server对象里的处理工具, 用于处理请求.

在一开始, 在生成Server对象的时候, 我们只给了监听地址,但是把处理工具设置为nil,因此在下面的代码中, 我们要开始使用默认的登记中心, 作为我们的处理工具

拿到了处理工具, 我们开始对着服务中心的处理工具,处理请求. 我们调用ServeHTTP方法, 按照ServeHTTP方法的定义, 它必须能接收一个请求,并且能写一个反馈, 能做响应.

回到登记中心, 找到对应的处理函数

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	if r.RequestURI == "*" {
		if r.ProtoAtLeast(1, 1) {
			w.Header().Set("Connection", "close")
		}
		w.WriteHeader(StatusBadRequest)
		return
	}
	
	// 第一件事: 找到对应处理函数
	h, _ := mux.Handler(r)
	// 第二件事: 执行处理函数
	h.ServeHTTP(w, r)
}
复制代码

现在我们已经回到登记中心了, 我们要做的第一件事, 是根据请求内容, 找到对应的处理函数.

在上面我们说过, 登记中心的任何函数都必须实现ServeHTTP方法, 因此我们的第二件事就是执行函数ServeHTTP, 也就是执行这个执行函数本身.

登记中心: 解析请求寻找对应处理函数的过程

// 第一步, 解析请求内容
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
  ...
	host := stripHostPort(r.Host)
  ...
	return mux.handler(host, r.URL.Path)
}

// 第二步, 尝试找到匹配的函数
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
	mux.mu.RLock()
	defer mux.mu.RUnlock()

	// 开始匹配
	if mux.hosts {
		h, pattern = mux.match(host + path)
	}
	if h == nil {
		h, pattern = mux.match(path)
	}
	if h == nil {
		h, pattern = NotFoundHandler(), ""
	}
	return
}


func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	// 看看这个路径能不能直接匹配上
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}

	// 如果找不到直接匹配上, 找出最为接近的
	for _, e := range mux.es {
		if strings.HasPrefix(path, e.pattern) {
			return e.h, e.pattern
		}
	}
	return nil, ""
}

复制代码

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK