欢迎访问www.allbetgaming.com!

首页科技正文

平顶山旅游景点大全:Golang Web入门(1):自顶向下明白Http服务器

admin2020-04-157

摘要

由于Golang优异的并发处置,许多公司使用Golang编写微服务。对于Golang来说,只需要短短几行代码就可以实现一个简朴的Http服务器。加上Golang的协程,这个服务器可以拥有极高的性能。然而,正是由于代码过于简朴,我们才应该去研究他的底层实现,做到会用,也知道为什么这么用。

在本文中,会以自顶向下的方式,从若何使用,到若何实现,一点点的剖析Golang中net/http这个包中关于Http服务器的实现方式。内容可能会越来越难明了,作者会只管把这些源码讲的更清晰一些,希望对列位有所辅助。

1 确立

首先,我们以怎么用为起点。

究竟,知道了怎么用,才气一步一步的深入挖掘为什么这么用。

先来看第一种最简朴的确立方式(省略了导包):

func helloWorldHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello World !")
}

func main() {
	http.HandleFunc("/", helloWorldHandler)
	http.ListenAndServe(":8000", nil)
}

实在在这一部门中,代码应该很容易明了。就是先做一个映射,把需要接见的地址,和接见后执行的函数,写在一起。然后再加上监听的端口,就可以了。

若是你是一个Java程序员,你应该能觉察这个和Java中的Servlet很相似。也是确立一个个的Servlet,然后注册。

再来看看第二种确立方式,也一样省略了导包:

type helloWorldHandler struct {
	content string
}

func (handler *helloWorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, handler.content)
}

func main() {
	http.Handle("/", &helloWorldHandler{content: "Hello World!"})
	http.ListenAndServe(":8000", nil)
}

在这里,我们能发现相较于第一种方式,有些许的改动。

我们界说了一个结构体,然后又给这个结构体编写了一个方式。凭据我们之前对于接口的观点:要实现一个接口必须要实现这个接口的所有方式

那么我们是不是可以推测:存在这么一个接口A,内里有一个名为ServeHTTP的方式,而我们所编写的这个结构体,他已经实现了这个接口A了,他现在是属于这个A类型的一个结构体了。

type A interface{
    ServeHTTP()
}

而且,在main函数中关于映射URI和方式的参数部门,需要挪用实现了这个接口A的一个工具。

带着这个问题,我们可以继续往下。

2 注册

在第一部门,我们提到了两种注册方式,一种是传入一个函数,一种是传入一个结构体指针。

http.HandleFunc("/", helloWorldHandler)

http.Handle("/", &helloWorldHandler{content: "Hello World!"})

我们来看看http包内的源码:

package http

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	    DefaultServeMux.HandleFunc(pattern, handler)
}

func Handle(pattern string, handler Handler) {
        DefaultServeMux.Handle(pattern, handler) 
}

先看一下这里的代码,他们被称为注册函数

首先研究一下HandleFunc这个函数。在main函数中,挪用了这个具有func(pattern string, handler func(ResponseWriter, *Request))署名的函数,这里的patternstring类型的,指的是匹配的URI,这个很容易明了。第二个参数是一个具有func(ResponseWriter, *Request)署名的函数。

然后我们继续看,在这个函数中,挪用了这个方式:

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

我们可以看到,最终是挪用了DefaultServeMux工具的Handle方式

好,先到这里,我们再看一看刚刚提到的署名为func (pattern string, handler Handler)另外一个函数。在这个函数内里,同样是挪用了DefaultServeMux工具的Handle方式

也就是说,无论我们使用哪种注册函数,最终挪用的都是这个函数:

func (mux *ServeMux) Handle(pattern string, handler Handler)

这里涉及到了两种工具,第一是ServeMux工具,第二是Handler工具。

ServeMux工具我们一会再聊,先聊聊Handler工具。

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

在Golang中,Handler是一种接口类型,只要实现了ServeHTTP这个方式,谁人就可以称这个结构体是Handler类型的。

注重到,在前面有一行代码是这样的:

mux.Handle(pattern, HandlerFunc(handler))

有人可能会想,HandlerFunc func(ResponseWriter, *Request)这个函数,是输入一个函数,返回一个Handler类型的工具,实在这是纰谬的。我们来看看这个函数的源码:

type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

我们可以发现,这个函数,他是一个结构体类型,而且这个结构体也是实现了ServeHTTP方式的,也就是说,这个结构体也是一个Handler类型。以是,这个方式实在并不是输入一组参数,返回一个Handler类型,而是他自己就是一个Handler类型,可以直接挪用ServeHTTP方式。

这里对照绕,然则信赖当你明了了之后,会感受妙啊

说完了Handler,我们再来聊聊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
}

我们先关注一下这个结构内里的m字段。这个字段是一个map类型,key是URI,value是muxEntry类型。而这个muxEntry类型,内里包含了一个HandlerURI。也就是说,通过这个m字段,我们可以用URI找到对应的Handler工具。

继续说回上面提到的func (mux *ServeMux) Handle(pattern string, handler Handler)方式。我们已经知道了挪用这个方式的工具是ServeMux,也知道了这个方式的参数中的Handler是什么,下面让我们来看看这个方式的详细实现:

func (mux *ServeMux) Handle(pattern string, handler Handler) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	if pattern == "" {
		panic("http: invalid pattern")
	}
	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)
	}
	e := muxEntry{h: handler, pattern: pattern}
	mux.m[pattern] = e
	if pattern[len(pattern)-1] == '/' {
		mux.es = appendSorted(mux.es, e)
	}

	if pattern[0] != '/' {
		mux.hosts = true
	}
}

在这个方式中,我们可以看到,Handle方式会先判断传入的URIhandler是否正当,然后判断这个URI对应的处置器是否已经注册,然后将这个URIhandler对应的map写入ServeMux工具中。

注重,这里另有一个步骤。若是这个URI是以/末端的,将会被送入es数组中,按长度排序。至于为什么会这么做,我们在后面的内容将会提到。

说完了这些,我们应该可以猜到这个ServeMux工具的作用了。他可以存储我们注册的URIHandler,以实现当有请求进来的时刻,可以委派给相对应的Handler的功效。

考虑到这个功效,那么我们也可以推断出,这个ServeMux也是一个Handler,只不过他和其他的Handler差别。其他的Handler处置的是详细的请求,而这个ServeMux处置的是请求的分配。

以是,ServeMux也实现了ServeHTTP方式,他也是一个Handler。而对于他是怎么实现ServeHTTP方式的,我们也在后面的内容提到。

3 监听

现在,让我们来聊聊main函数中的第二行:

http.ListenAndServe(":8000", nil)

根据老例,我们来看一看这个方式的实现:

func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

这里的Server,是一个庞大的结构体,内里包含了设置服务器的许多参数,然则这里我们只聊AddrHandler这两个属性。

Addr很容易明了,就是这个服务器所监听的地址。

Handler是处置器,卖力把请求分配给各个对应的handler。在这里留空,则使用Golang默认的处置器,也就是上文中我们提到的实现了ServeHTTP方式的ServeMux

知道了这些,我们继续往下看server.ListenAndServe()的实现:

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

这里对照主要的有两行,第一是ln, err := net.Listen("tcp", addr),也就是说,最先监听addr这个地址的tcp毗邻

然后,挪用srv.Serve(ln),我们来看看代码(省略部门,只保留与本文有关的逻辑):

func (srv *Server) Serve(l net.Listener) error {
    ...
    for{
        ...
        c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) // before Serve can return
		go c.serve(connCtx)
    }
}

简朴来讲,在这个方式中,有一个死循环,他不停吸收新的毗邻,然后启动一个协程,处置这个毗邻。我们来看看c.serve(connCtx)的详细实现:

func (c *conn) serve(ctx context.Context) {
    ...
    serverHandler{c.server}.ServeHTTP(w, w.req)
    ...
}

省略其他所有的细节,最要害的就是这一行代码了,然后我们再看看这个ServeHTTP方式。注重,这里的c.server,照样指的是最最先的谁人Server结构体。坚持一下下,马上就到最要害的地方啦:

type serverHandler struct {
	srv *Server
}

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

这里的ServeHTTP方式逻辑很容易看出,若是最最先没有界说一个全局处置的Handler,则会使用Golang的默认handlerDefaultServeMux

假设,我们这里使用的是DefaultServeMux,执行ServeHTTP方式。说到这里你是否有印象,我们在上一个章节里提到的:

以是,ServeMux也实现了ServeHTTP方式,他也是一个Handler。而对于他是怎么实现ServeHTTP方式的,我们也在后面的内容提到。

就是这里,对于ServeMux来说,他就是一个处置请求分发的Handler

若是你学过Java,我跟你说他和ServletDispatcher很相似,你应该能明了吧。

4 处置

到了这里,就是最后一步了,我们来看看这里处置请求分发的ServeHTTP方式详细实现:

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	...
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}

在省去其他细节之后我们应该可以推断,这个mux.Handler(r)方式返回的h,应该是所请求的URI所对应的Handler。然后,执行这个Handler所对应的ServeHTTP方式。我们来看看mux.Handler(r)这个方式:

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    ...
    host := stripHostPort(r.Host)
	path := cleanPath(r.URL.Path)
	...
	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()

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

到了这里,代码就变得简洁明了了。重点就是这个mux.match方式,会凭据地址,来返回对应的Handler。我们来看看这个方式:

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中存储了key为这个URI的路由规则的映射,则直接返回这个URI对应的Handler

否则,就去匹配es数组。还记得吗,这个数组是之前注册路由的时刻提到的,若是URI是以/末端的,就会把这个路由映射添加到es数组中,并由长到短举行排序。

这样的作用是,可以优先匹配到最长的URI,以到达近似匹配的时刻能够匹配到最合适的路由的目的。

至此,返回对应的Handler,然后执行,就乐成的实现了处置相对应的请求了。

写在最后

首先,谢谢你能看到这里!

不知道你有没有明了我所说的内容,希望这篇文章可以给你一些辅助。

实在写这篇文章的目的是这样的,学完了Golang的基础之后作者准备最先研究Golang Web。然则查找种种资料后发现,并没有找到一条很合适的学习门路。然后原本作者计划去直接研究一个框架,如MeeGo,Gin等。然则又考虑到,框架只是用来解决问题的,学会了框架却不知道基础内容,有种知其然不知其以是然的感受。

以是,作者计划从Golang的net/http包的源码最先,逐步去领会怎么用原生的Go语言去确立一个HTTP服务器,然后去领会一下怎么举行缓存,做持久化等,这也是作者思索之后决议的一条学习门路。当能够把这些内容都研究明了之后,再去研究框架,去看这些框架是怎么解决问题的,可能才是对照合适的。

固然了,作者也是刚入门。以是,可能会有许多的疏漏。若是在阅读的过程中,有哪些注释不到位,或者明了泛起了误差,也请你留言指正。

再次谢谢~

,

Sunbet

Sunbet www.169894.com Sunbet是进入Sunbet的主力站点。Sunbet开放Sunbet会员开户网址(www.sunbet.us)、Sunbet代理开户(www.sunbet.us)、Sunbet手机版下载(www.sunbet.us)、Sunbet电脑客户端下载(www.sunbet.us)等业务。

网友评论