深入了解net/http

http包提供了HTTP客户端和服务端的实现。

最简单Web服务

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("hello")
	})
	log.Fatal(http.ListenAndServe("localhost:8000", nil))
}

我们先分析下面这个函数

  • func ListenAndServe(addr string, handler Handler) error
    • addr 参数很明显就是监听的host和端口
    • handler 参数是一个满足Handler 接口的对象

我们首先来了解下Handler类型 其定义如下

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

也就是说凡是实现了 ServeHTTP(ResponseWriter, *Request)方法的类型都是满足Handler接口的,所以我们可以用下面的方式来实现一个web服务

(上面的说法并不严谨,我会在最后讲述使用http.HandlerFunc()进行类型转换来实现满足Handler接口的方法)

import (
	"fmt"
	"log"
	"net/http"
)

type myHandler struct {
}

func (h myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	switch r.URL.Path {
	case "/":
		fmt.Println("hello")
	default:
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprintf(w, "not found: %s\n", r.URL)
	}
}
func main() {
	handler := myHandler{}
	log.Fatal(http.ListenAndServe("localhost:8000", handler))
}

可能有人注意到了我们在最开始写的,最简单的web服务中的http.ListenAndServe("localhost:8000", nil)我们向第二个参数传递的是nil,并不满足http.Handler接口,这个问题我们留到下面去解答

http.ServeMux

ServeMux类型是HTTP请求的多路转接器。它会将每一个接收的请求的URL与一个注册模式的列表进行匹配,并调用和URL最匹配的模式的处理器。

上面的例子中myHandler.ServeHTTP()实际上既实现了路由分配又实现了逻辑处理。在大多数情况下,我们更希望不同的路由交由不同的方法来处理,所以http包中引入了ServeMux类型来帮助我们实现路由分配

用法
type myHandler struct {
}

func (h myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "hello world")
}
func main() {
	handler := myHandler{}
	mux := http.NewServeMux()
	mux.Handle("/", handler)
	log.Fatal(http.ListenAndServe("localhost:8000", mux))
}
ServeMux的结构
type ServeMux struct {
    mu sync.RWMutex   //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
    m  map[string]muxEntry  // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
    hosts bool // 是否在任意的规则中带有host信息
}
type muxEntry struct {
    explicit bool   // 是否精确匹配
    h        Handler // 这个路由表达式对应哪个handler
    pattern  string  //匹配字符串
}

可以注意到muxEntry中的h的类型为实现Handler接口结构的对象

路由解析实现思路

通过ServeMux可以看到实际的路由规则记录在ServeMux.m 中,这个属性是一个map[string]muxEntry 类型,string记录都是路由,muxEntry里包含实际的handler

通过http.ListenAndServe("localhost:8000", mux)很容易想到ServeMux实现了也实现了Handler接口(拥有ServerHTTP()方法)

所以实现路由解析的原理为

  • ListenAndServe() 调用ServeMux中的ServerHTTP()
  • ServeMux中的ServerHTTP()方法会根据路由去map[string]muxEntry中找到对应的muxEntry,然后调用muxEntry中的 h.ServerHTTP()

源码入下

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        w.Header().Set("Connection", "close")
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    if r.Method != "CONNECT" {
        if p := cleanPath(r.URL.Path); p != r.URL.Path {
            _, pattern = mux.handler(r.Host, p)
            return RedirectHandler(p, StatusMovedPermanently), pattern
        }
    }    
    return mux.handler(r.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
}
添加handler

通过上面路由解析的原理可以了解:据用户请求的URL和路由器里面存储的map去匹配,当匹配到之后返回存储的handler,调用这个handler的ServeHTTP接口就可以执行到相应的函数了。

而将URL和handler 添加到map中则需要使用func (mux *ServeMux) Handle(pattern string, handler Handler)

看上面用法也可以很容易理解 mux.Handle("/", handler) 函数将URL:"/" 添加到了map的键,将对应的handler 添加到了muxEntry结构中

http.HandlerFunc()

还记得上面的说法:凡是实现了ServeHTTP(ResponseWriter, *Request) 方法的类型都是满足Handler接口的。但实际开发中我们会发现大部分handler 并没有实现ServeHTTP() 的函数,而是如下的一种方式

type myHandler struct {
}

func (h myHandler) index(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "hello world")
}
func (h myHandler) News(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "news one")
}
func main() {
	h := myHandler{}
	mux := http.NewServeMux()
	mux.Handle("/", http.HandlerFunc(h.index))
	mux.Handle("/news", http.HandlerFunc(h.News))
	log.Fatal(http.ListenAndServe("localhost:8000", mux))
}

http.HandlerFunc(h.index)在这里并不是函数调用,而是强制的类型转换

type HandlerFunc func(ResponseWriter, *Request)

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

即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。

mux.HandleFunc()

因为像上面http.HandlerFunc()方式是非常普遍的,所以下面两个是等价的

mux.HandleFunc("/", h.index)
mux.Handle("/news", http.HandlerFunc(h.News))
http.HandleFunc()

注意和上面的http.HandlerFunc()进行区分,前者是实现了Handler接口的类型,可使用强制类型转换http.HandlerFunc(f) 让f拥有ServeHTTP方法。

后者是为默认的ServeMux:DefaultServeMux注册路由和handler

http.ListenAndServe("localhost:8000", nil)中第二参数为nil时,http.Serve()便使用DefaultServeMux作为处理的handler

Go代码的执行流程

通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。 首先调用Http.HandleFunc 按顺序做了几件事:

  1. 调用了DefaultServeMux的HandleFunc
  2. 调用了DefaultServeMux的Handle
  3. 往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则

其次调用http.ListenAndServe(":9090", nil) 按顺序做了几件事情:

  1. 实例化Server

  2. 调用Server的ListenAndServe()

  3. 调用net.Listen("tcp", addr)监听端口

  4. 启动一个for循环,在循环体中Accept请求

  5. 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()

  6. 读取每个请求的内容w, err := c.readRequest()

  7. 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux

  8. 调用handler的ServeHttp

  9. 在这个例子中,下面就进入到DefaultServeMux.ServeHttp

  10. 根据request选择handler,并且进入到这个handler的ServeHTTP

    mux.handler(r).ServeHTTP(w, r)

  11. 选择handler:

  • 判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry)
  • 如果有路由满足,调用这个路由handler的ServeHttp
  • 如果没有路由满足,调用NotFoundHandler的ServeHttp

参考资料

转载于:https://my.oschina.net/liangwt/blog/1618513

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值