深入理解Go语言中的Dispatcher

介绍

Go使用goroutines来处理connection的读写事件,不会阻塞:

c, err := srv.newConn(rw)
  if err != nil {
    continue
  }
  go c.serve()

c即为创建的connection,保存了该次请求的信息,然后再传递到对应的handler,handler就可以读取到请求的header信息,保证了请求之间独立。

Go中的ServeMux

上面代码中提到了c(这个c就是connection).serve()方法。其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。

默认路由器ServeMux,结构如下:

type ServeMux struct {
 mu sync.RWMutex  //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
 m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
 hosts bool // 是否在任意的规则中带有host信息
}

下面看一下muxEntry:

type muxEntry struct {
 explicit bool  // 是否精确匹配
 h    Handler // 这个路由表达式对应哪个handler
 pattern string //匹配字符串
}

接着看一下Handler的定义:

type Handler interface {
 ServeHTTP(ResponseWriter, *Request) // 路由实现器
}

Handler是一个接口,但是前一小节中的sayhelloName函数并没有实现ServeHTTP这个接口,仍然能添加到路由表中,原因就是http包里还有一个HandlerFunc,我们定义的函数sayhelloName就是这个HandlerFunc调用的结果,而这个类型默认实现了ServeHTTP这个接口,即我们调用了HandlerFunc(f) ,强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。

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

我们看一下HandlerFunc的官方注解:

HandlerFunc类型是一个适配器,允许使用普通的函数作为HTTP处理程序。如果f是具有适当签名的函数,HandlerFunc(f)是调用f的Handler。

适当的签名,由于作者水平也不深厚(毕竟我本命语言是java),猜一下指的应该是函数的参数以及返回值,也就是说:如果函数的参数是两个,分别是ResponseWriter和一个指向Request的指针,并且返回值为void类型的函数,可以强转为HandlerFunc,而最终调用的f中的Handler接口的方法也就是ServeHttp。

路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?请看下面的代码,默认的路由器实现了ServeHTTP:

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

如上所示路由器接收到请求之后,如果是*那么关闭链接,不然调用mux.Handler(r)返回对应设置路由的处理Handler,然后执行h.ServeHTTP(w, r) 。看一下ServeMUX.Handler(*request)的官方文档:

Handler返回用于给定请求的处理程序,请咨询r.Methodr.Hostr.URL.Path。它总是返回一个非nil处理程序。如果路径不是其规范形式,处理程序将是重定向到规范路径的内部生成的处理程序。

Handler还返回与请求匹配的注册模式,或者在内部生成的重定向的情况下,返回在跟随重定向之后匹配的模式。

如果没有适用于请求的注册处理程序,则Handler返回“未找到页面”处理程序和空模式。

说白了,根据request的method、host和请求的URL的路径返回一个处理程序,这个处理程序就是我们说过的Handler,再看看Handler接口的方法,我们就知道了,最终会跑到我们sayhelloName里面~。我们看看ServeMux.Handler(*request)的实现:

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
}

为了不让读者懵逼,我们还是看一下match方法,这是个私有方法,循环迭代了mux中的map:

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
 var n = 0
 for k, v := range mux.m {
 if !pathMatch(k, path) {
  continue
 }
 if h == nil || len(k) > n {
  n = len(k)
  h = v.h
  pattern = v.pattern
 }
 }
 return
}

匹配到之后返回存储的handler,调用这个handler的ServeHTTP接口就可以执行到相应的函数了。

Go其实支持外部实现的路由器 ListenAndServe的第二个参数就是用以配置外部路由器的,它是一个Handler接口,即外部路由器只要实现了Handler接口就可以,我们可以在自己实现的路由器的ServeHTTP里面实现自定义路由功能。

我们实现一个简易路由器:

package main
import (
 "fmt"
 "net/http"
)
type MyMux struct {}
func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 if r.URL.Path == "/" {
 sayhelloName(w, r)
 return
 }
 http.NotFound(w, r)
 return
}
func sayhelloName(w http.ResponseWriter, r *http.Request) {
 fmt.Fprintf(w, "Hello myroute!")
}
func main() {
 mux := &MyMux{}
 http.ListenAndServe(":9090", mux)
}

通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程:

1、首先调用Http.HandleFunc,按顺序做了几件事:

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

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

  • 实例化Server
  • 调用Server的ListenAndServe()
  • 调用net.Listen(“tcp”, addr)监听端口
  • 启动一个for循环,在循环体中Accept请求
  • 对每个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()
  • 读取每个请求的内容w, err := c.readRequest()
  • 判断handler是否为空,如果没有设置handler(这个例子就没有设置handler),handler就设置为DefaultServeMux
  • 调用handler的ServeHttp
  • 在这个例子中,下面就进入到DefaultServeMux.ServeHttp
  • 根据request选择handler,并且进入到这个handler的ServeHTTP,mux.handler(r).ServeHTTP(w, r)
  • 选择handler:
  • 判断是否有路由能满足这个request(循环遍历ServerMux的muxEntry)
  • 如果有路由满足,调用这个路由handler的ServeHttp
  • 如果没有路由满足,调用NotFoundHandler的ServeHttp

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • 深入理解Go语言中的Dispatcher

    介绍 Go使用goroutines来处理connection的读写事件,不会阻塞: c, err := srv.newConn(rw) if err != nil { continue } go c.serve() c即为创建的connection,保存了该次请求的信息,然后再传递到对应的handler,handler就可以读取到请求的header信息,保证了请求之间独立. Go中的ServeMux 上面代码中提到了c(这个c就是connection).serve()方法.其实内部是调用了htt

  • 一文带你深入理解Go语言中的sync.Cond

    目录 sync.Cond 是什么 适用场景 sync.Cond 的基本用法 NewCond 创建实例 Wait 等待条件满足 Signal 通知一个等待的 goroutine Broadcast 通知所有等待的 goroutine sync.Cond 使用实例 为什么要用 sync.Cond close channel 广播实例 sync.Cond 基本原理 sync.Cond 的设计与实现 sync.Cond 模型 notifyList 结构体 sync.Cond 的方法 Wait 方法 Si

  • Ruby语言中的String深入理解

    Ruby语言中的String是mutable的,不像java.C#中的String是immutable的.比如 复制代码 代码如下: str1="abc" str2="abc" 在java中,对于字面量的字符串,jvm内部维持一张表,因此如果在java中,str1和str2是同一个String对象.而在Ruby中, str1和str2是完全不同的对象.同样,在java中对于String对象的操作都将产生一个新的对象,而Ruby则是操纵同一个对象,比如: 复制代码 代

  • C语言中bool变量的深入理解

    目录 前言 bool类型变量的大小 bool 值与0比较 c语言中bool如何输出 总结 前言 在一些高级语言当中,为了能够完成更好的逻辑判断,因此就有了bool类型,bool类型的变量值只有true和false两种. 而在C语言中,一般认为0为假,非0为真. 这是因为c99之前,c90是没有bool类型的的.但是c99引入了_Bool类型(_Bool就是一个类型,不过在新增头文件stdbool.h中,被重新用宏写成了 bool,为了保证C/C++兼容性). 目前为止大部分C语言书籍采用的标准还

  • 详解 Go 语言中 Map 类型和 Slice 类型的传递

    Map 类型 先看例子 m1: func main() { m := make(map[int]int) mdMap(m) fmt.Println(m) } func mdMap(m map[int]int) { m[1] = 100 m[2] = 200 } 结果是 map[2:200 1:100] 我们再修改如下 m2: func main() { var m map[int]int mdMap(m) fmt.Println(m) } func mdMap(m map[int]int) {

  • C语言中的BYTE和char深入解析

    例如,在下面的源程序中""""之内的"你"."好".","."C"."!"."\n"就属于程序要处理的字符. 复制代码 代码如下: #include <stdio.h>int main(void){      printf("你好,C!\n");         return 0;} 该源程序中的其他字符则属于书写源

  • C语言中static的作用及C语言中使用静态函数有何好处

    想了解Java中static关键字的作用和用法详细介绍,请点击此处了解详情. 在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条,分别是: 一是隐藏功能,对于static修饰的函数和全局变量而言 二是保持持久性功能,对于static修饰的局部变量而言. 三是因为存放在静态区,全局和局部的static修饰的变量,都默认初始化为0 下面我逐一给大家介绍: (1)先来介绍它的第一条也是最重要的一条:隐藏. 当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有

  • Go语言中slice作为参数传递时遇到的一些“坑”

    前言 相信看到这个题目,可能大家都觉得是一个老生常谈的月经topic了.一直以来其实把握一个"值传递"基本上就能理解各种情况了,不过最近遇到了更深一点的"小坑",与大家分享一下. 首先还是从最简单的说起,看下面代码: func main() { a := []int{7,8,9} fmt.Printf("len: %d cap:%d data:%+v\n", len(a), cap(a), a) ap(a) fmt.Printf("le

  • C语言中sizeof函数的基本使用总结

    前言 C语言中的sizeof是一个很有意思的关键字,经常有人用不对,搞不清不是什么.我以前也有用错的时候,现在写一写,也算是提醒一下自己吧. sizeof是什么 sizeof是C语言的一种单目操作符,如C语言的其他操作符++.--等,sizeof操作符以字节形式给出了其操作数的存储大小.操作数可以是一个表达式或括在括号内的类型名.这个操作数不好理解对吧?后面慢慢看就明白了.sizeof的返回值是size_t,在64位机器下,被定义为long unsigned int. sizeof函数的结果:

  • 汇编语言中mov和lea指令的区别详解

    指令(instruction)是一种语句,它在程序汇编编译时变得可执行.汇编器将指令翻译为机器语言字节,并且在运行时由 CPU 加载和执行. 一条指令有四个组成部分: 标号(可选) 指令助记符(必需) 操作数(通常是必需的) 注释(可选) 最近在学习汇编语言,过程中遇到很多问题,对此在以后的随笔会逐渐更新,这次谈谈mov,lea指令的区别   一,关于有没有加上[]的问题 1,对于mov指令来说: 有没有[]对于变量是无所谓的,其结果都是取值 如: num dw 2 mov bx,num mov

随机推荐