Go WEB入门

Wesley13 等级 221 0 0
标签: handlerstringuri

摘要 由于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))签名的函数,这里的pattern是string类型的,指的是匹配的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) }

我们可以发现,这个函数,他是属于一种叫HandlerFunc类型的函数。

在Golang中,这是一种很特别的特性。我们可以将函数设置为一种类型,或者你可以理解成变量,你可以用一个变量名去表示这个函数,可以把这个函数赋值给某一个变量。

fn := func(){ fmt.Println("x is",x) } fn() 所以,在这里这个函数类型也是实现了ServeHTTP方法的,也就是说,这个名为HandlerFunc的函数类型,也是属于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类型,里面包含了一个Handler和URI。也就是说,通过这个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方法会先判断传入的URI和handler是否合法,然后判断这个URI对应的处理器是否已经注册,然后将这个URI和handler对应的map写入ServeMux对象中。

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

说完了这些,我们应该可以猜到这个ServeMux对象的作用了。他可以存储我们注册的URI和Handler,以实现当有请求进来的时候,可以委派给相对应的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,是一个复杂的结构体,里面包含了设置服务器的很多参数,但是这里我们只聊Addr和Handler这两个属性。

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的默认handler:DefaultServeMux。

假设,我们这里使用的是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,然后执行,就成功的实现了处理相对应的请求了。

收藏
评论区

相关推荐

Android Handler消息机制源码解析
好记性不如烂笔头,今天来分析一下Handler的源码实现 Handler机制是Android系统的基础,是多线程之间切换的基础。下面我们分析一下Handler的源码实现。 Handler消息机制有4个类合作完成,分别是Handler,MessageQueue,Looper,Message Handler : 获取消息,发送消息,以及处理消息的类 Mes
【Golang】GoWeb框架之Gin-简明教程
Gin 简介 Gin is a HTTP web framework written in Go (Golang). It features a
Go WEB入门
摘要 由于Golang优秀的并发处理,很多公司使用Golang编写微服务。对于Golang来说,只需要短短几行代码就可以实现一个简单的Http服务器。加上Golang的协程,这个服务器可以拥有极高的性能。然而,正是因为代码过于简单,我们才应该去研究他的底层实现,做到会用,也知道为什么这么用。 在本文中,会以自顶向下的方式,从如何使用,到如何实现,一点点的分
Go 语言简介(下)— 特性
#### goroutine GoRoutine主要是使用go关键字来调用函数,你还可以使用匿名函数,如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 packagemain import"fmt" func f(msg string) {     fmt.Println(msg) } f
Go语言_通神路之灵胎篇(6)
### 1、映射 ####     1.1 概念 go的映射在java中被叫做集合 type Vertex struct { Lat, Long float64 } var m map\[string\]Vertex 上面是go的映射m,如果用java代替的话,Vertex就是实体类,这样用java8可以写成 Map m = new Ha
go 动态数组 二维动态数组
go使用动态数组还有点麻烦,比python麻烦一点,需要先定义。 动态数组申明 ====== var dynaArr []string 动态数组添加成员 ======== dynaArr = append(dynaArr, "one") ```go # 结构体数组
HTTP协议对URI长度,POST数据长度及COOKIE长度限制说明
### 1\. URL长度限制 在Http1.1协议中并没有提出针对URL的长度进行限制,RFC协议里面是这样描述的,HTTP协议并不对URI的长度做任何的限制,服务器端必须能够处理任何它们所提供服务多能接受的URI,并且能够处理无限长度的URI,如果服务器不能处理过长的URI,那么应该返回414状态码。 虽然Http协议规定了,但是**
java.IO.File类详解
JDK1.0就存在的File类:用于对文件进行一般性操作。由于NIO中有对File对象进行改善,现在对其进行一般性说明。 **File类有四个构造函数:(都必须传入路径地址String或者Path)**   File(String pathname)   File(File parent, String child)   File(String pa
URI URL java 输出
URI uri =new URI("http://myname@www.baidu.com:8888/path1/path1?query1=value1#fffffff"); System.out.println("URI.getAuthority() = "+uri.getAuthority()); System.ou
URI、URL、URN 的联系和区别
原文同步至 [http://www.waylau.com/difference-of-uri-url-urn/](https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fwww.waylau.com%2Fdifference-of-uri-url-urn%2F) 在 Web 应用中,URI、URL
MySQl的一些基本知识
安装mysql服务: ---------- * 命令行 * 软件 * 程序存入数据 * mysql.server 存入数据 * mysql.client 命令行操作数据库 * 在sql里面#和--空格都代表注释。 * sql注入:“ or 1=1; -- *
VideoView支持播放https的url视频
重写setVideoUrI方法 @Override public void setVideoURI(Uri uri) { super.setVideoURI(uri); try { HttpsURLConnection.setDefaultSSLSocketFactory(S
Go 实现字符串相似度计算函数 Levenshtein 和 SimilarText
【转】[http://www.syyong.com/Go/Go-implements-the-string-similarity-calculation-function-Levenshtein-and-SimilarText.html](https://www.oschina.net/action/GoToLink?url=http%3A%2F%2Fwww
Golang代码实现HTTPs(HTTPS证书生成和部署)
在win7下试试: 1.**实现一个最简单的HTTPS Web Server** // gohttps/2-https/server.go package main import ( "fmt" "net/http" ) func handler(w http.R
Jetty的HttpClient设置代理
void customizeHttpClient(HttpClient httpClient) throws URISyntaxException { URI proxyURI = new URI("http://proxyhost:proxyport"); AuthenticationStore auth = htt