• http服务源码分析


    多读go的源码,可以加深对go语言的理解和认知,今天分享一下http相关的源码部分
    在不使用第三方库的情况下,我们可以很容易的的用go实现一个http服务,

    package main
    
    import (
    	"fmt"
    	"net/http"
    )
    
    func IndexHandler(w http.ResponseWriter, r *http.Request) {
    	fmt.Fprintln(w, "hello world ! ")
    }
    
    func main() {
    	http.HandleFunc("/", IndexHandler)
    	if err := http.ListenAndServe(":9100", nil); err != nil {
    		panic(err)
    	}
    }
    

    直接在浏览器里访问9100端口就可以返回 hello world !
    go已经把所有的细节封装好了,我们只需要自己去写Handler实现就够了。源码简单来说做了以下几件事:

    • 把我们自定义的Handler方法添加到默认路由DefaultServeMux的Map里比如:http.HandleFunc("/", IndexHandler) (btw: go语言的map是非线程安全的,可以在http源码里看到官方的处理方式);
    • 启动一个tcp服务监听9100端口,等待http调用;
    • 当监听到有http调用时,启动一个协程来处理这个请求,这个是go的http服务快的一个重要原因,把请求内容转换成http.Request, 把当前连接封装http.RespnseWriter;
    • 默认路由DefaultServeMux根据request的path找到相应的Handler,把 request和 responseWriter传给Handler 进行业务逻辑处理,response响应信息write给客户端;

    ServeMux & Handler

    http 包的默认路由 DefaultServeMuxServeMux 结构休的实例
    http.HandleFunc("/", IndexHandler) 的调用,会把path信息和自定义的方法信息保存到 DefaultServeMuxm map[string]muxEntry变量里
    我们看一下ServeMux 的定义:

    type ServeMux struct {
    	mu    sync.RWMutex
    	m     map[string]muxEntry
    	es    []muxEntry // slice of entries sorted from longest to shortest.
    	hosts bool       // whether any patterns contain hostnames
    }
    
    type muxEntry struct {
    	h       Handler
    	pattern string
    }
    

    ServeMux 中保存了pathHandler 的对应关系,也是路由关系。

    Handler

    muxEntry 中的 h Handler 对就的就是我们自定义的Handler方法比如,我们自己例子中的方法 func IndexHandler(w http.ResponseWriter, r *http.Request) 细心的同学可能会问 Handler是一个接口,但是我们只是定义了一个方法,这是怎么转换的呢?
    接口Halder设置了签名规则,也就是我们自定义的处理方法

    type Handler interface {
    	ServeHTTP(ResponseWriter, *Request)
    }
    

    go语言中所有的自定义类型都可以实现自己的方法,http包是用一个自定义的func来去实现了Handler接口,

    type HandlerFunc func(ResponseWriter, *Request)
    
    // ServeHTTP calls f(w, r).
    func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    	f(w, r)
    }
    

    然后在ServerMux的方法HandleFunc处理的时候会把 handler func(ResponseWriter, *Request) 转换成 HandlerFunc, 如下所示:

    // 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))
    }
    

    ServerMux 结构中还有一个读写锁 mu sync.RWMutex mu就是用来处理多线程下map的安全访问的。

    查找&调用 Handler

    得到自定义的handler方法,就是去map中根据path匹配得到Handler

    func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    	mux.mu.RLock()
    	defer mux.mu.RUnlock()
    
    	// Host-specific pattern takes precedence over generic ones
    	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) {
    	// Check for exact match first.
    	v, ok := mux.m[path]
    	if ok {
    		return v.h, v.pattern
    	}
    
    	// Check for longest valid match.  mux.es contains all patterns
    	// that end in / sorted from longest to shortest.
    	for _, e := range mux.es {
    		if strings.HasPrefix(path, e.pattern) {
    			return e.h, e.pattern
    		}
    	}
    	return nil, ""
    }
    

    ServeMux 实现了 Handler 接口,也是默认的路由调用的具体规则实现的地方,他的 ServeHTTP 方法处理方式就是得到自定义的handler方法,并调用我们自定义的方法:

    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)
    }
    

    接口Halder设置了签名规则,也就是我们自定义的处理方法
    比如下面的代码,函数IndexHandler就是我们自定义的方法,返回给客户端请求一个 hello world ! 字符串。中间请求是如何调用到我们自定义的方法的具体逻辑都是http包提供的,但是一点也不神秘,

    http.HandleFunc("/", IndexHandler)
    
    // IndexHandler 我们自己定义的Handler方法
    func IndexHandler(w http.ResponseWriter, r *http.Request) {
    	fmt.Fprintln(w, "hello world ! ")
    }
    
    type Handler interface {
    	ServeHTTP(ResponseWriter, *Request)
    }
    // 
    type HandlerFunc func(ResponseWriter, *Request)
    // ServeHTTP calls f(w, r).
    func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    	f(w, r)
    }
    

    http ListenAndServe

    说完 ServeMux 是如何结合 Handler 接口,来实现路由和调用后,就要说一下,http服务是如何得到客户端传入的信息,封装requet和rresponse的。
    在启动程序的时候http.ListenAndServe, 有两个参数,第一个是指写端口号,第二个是处理逻辑,如果我们没有给定处理逻辑,会使用默认的处理DefaultServeMux

    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{}
    	}
    	handler.ServeHTTP(rw, req)
    }
    

    ListenAndServe 方法打开tcp端口进行监听,然后把Listener 传给srv.Serve方法

    func (srv *Server) ListenAndServe() error {
    	// 省略部分代码 ...
    	ln, err := net.Listen("tcp", addr)
    	if err != nil {
    		return err
    	}
    	return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
    }
    

    具体要说一下 Service 方法,这个方法中,对监听tcp请求,然后把请求的客户端连接进行封装,

    func (srv *Server) Serve(l net.Listener) error {
    	// 省略部分代码 ...
    	ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    	for {
    		rw, e := l.Accept()
    		// 省略部分代码 ...
    		tempDelay = 0
    		c := srv.newConn(rw)
    		c.setState(c.rwc, StateNew) // before Serve can return
    		go c.serve(ctx)
    	}
    }
    

    把客户端的请求封装成一个Conn,然后启动一个协程go c.serve(ctx)来处理这个连接请求,这就是http包快的一个重要原因,每一个连接就是一个协程。客户端可以先和服务器进行连接,然后利用这个conn来多次发送http请求,这样,就可以减少每次的进行连接而提高一些速度。像一些rpc里就是利用这点去实现的双向的stream流,比如我之前的帖子go微服务框架go-micro深度学习(五) stream 调用过程详解,他就是建立一个tcp连接,然后基于这个conn,发送多个request,返回多次respose数据。

    // Serve a new connection.
    func (c *conn) serve(ctx context.Context) {
    	c.remoteAddr = c.rwc.RemoteAddr().String()
    	ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
    	// 省略部分代码 ...
    	// 循环读取请求 ...
    	for {
    		// 读取请求数据,封装response
    		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)
    		}
    		// 省略部分代码 ...
    		// 路由调用自定义的方法,把封装好的responseWrite和 request传到方法内
    		serverHandler{c.server}.ServeHTTP(w, w.req)
    		w.cancelCtx()
    		if c.hijacked() {
    			return
    		}
    		w.finishRequest()
    		// 省略部分代码 ...
    	}
    }
    
  • 相关阅读:
    Java的内存区域划分
    Java中的浮点型进行四则运算精度丢失的问题
    单例模式的几种写法
    如何掌握一门工具及对工具的认识
    记一个命运多舛的项目总结
    几个超级好用但很少有人知道的 webstorm技巧
    如何自定义中间件,实现业务代码无侵入监控及拦截
    如何减少和处理死锁
    快照读与加锁读
    谈谈Java常用类库中的设计模式
  • 原文地址:https://www.cnblogs.com/li-peng/p/10855859.html
Copyright © 2020-2023  润新知