Go的web工作原理

在Go中使用及其简单的代码即可开启一个web服务。如下:

//开启web服务
func test(){
http.HandleFunc("/", sayHello)
err := http.ListenAndServe(":9090",nil)
if err!=nil {
log.Fatal("ListenAndServer:",err)
}
} func sayHello(w http.ResponseWriter, r *http.Request){
r.ParseForm()
fmt.Println("path",r.URL.Path)
fmt.Println("scheme",r.URL.Scheme) fmt.Fprintf(w, "Hello Guest!")
}

在使用ListenAndServe这个方法时,系统就会给我们指派一个路由器,DefaultServeMux是系统默认使用的路由器,如果ListenAndServe这个方法的第2个参数传入nil,系统就会默认使用DefaultServeMux。当然,这里也可以传入自定义的路由器。

先来看http.HandleFunc("/", sayHello),从HandleFunc方法点进去,如下:

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

在这里调用了DefaultServeMuxHandleFunc方法,这个方法有两个参数,pattern是匹配的路由规则,handler表示这个路由规则对应的处理方法,并且这个处理方法有两个参数。

在我们书写的代码示例中,pattern对应/handler对应sayHello,当我们在浏览器中输入http://localhost:9090时,就会触发sayHello方法。

我们再顺着DefaultServeMuxHandleFunc方法继续点下去,如下:

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
mux.Handle(pattern, HandlerFunc(handler))
}

在这个方法中,路由器又调用了Handle方法,注意这个Handle方法的第2个参数,将之前传入的handler这个响应方法强制转换成了HandlerFunc类型。

这个HandlerFunc类型到底是个什么呢?如下:

type HandlerFunc func(ResponseWriter, *Request)

看来和我们定义的SayHello方法的类型都差不多。但是!!!

这个HandlerFunc默认实现了ServeHTTP接口!这样HandlerFunc对象就有了ServeHTTP方法!如下:

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

这个细节是十分重要的,因为这一步关乎到当路由规则匹配时,相应的响应方法是否会被调用的问题!这个方法的调用时机会在下一小节中讲到。

接下来,我们返回去继续看muxHandle方法,也就是这段代码mux.Handle(pattern, HandlerFunc(handler))。这段代码做了哪些事呢?源码如下:

func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock() if pattern == "" {
panic("http: invalid pattern " + pattern)
}
if handler == nil {
panic("http: nil handler")
}
if mux.m[pattern].explicit {
panic("http: multiple registrations for " + pattern)
} if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern} if pattern[0] != '/' {
mux.hosts = true
} // Helpful behavior:
// If pattern is /tree/, insert an implicit permanent redirect for /tree.
// It can be overridden by an explicit registration.
n := len(pattern)
if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
// If pattern contains a host name, strip it and use remaining
// path for redirect.
path := pattern
if pattern[0] != '/' {
// In pattern, at least the last character is a '/', so
// strings.Index can't be -1.
path = pattern[strings.Index(pattern, "/"):]
}
url := &url.URL{Path: path}
mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
}
}

代码挺多,其实主要就做了一件事,向DefaultServeMuxmap[string]muxEntry中增加对应的路由规则和handler

map[string]muxEntry是个什么鬼?

  • map是一个字典对象,它保存的是key-value
  • [string]表示这个字典的keystring类型的,这个key值会保存我们的路由规则。
  • muxEntry是一个实例对象,这个对象内保存了路由规则对应的处理方法。

找到相应代码,如下:

//路由器
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry //路由规则,一个string对应一个mux实例对象,map的key就是注册的路由表达式(string类型的)
hosts bool // whether any patterns contain hostnames
} //muxEntry
type muxEntry struct {
explicit bool
h Handler //这个路由表达式对应哪个handler
pattern string
} //路由响应方法
type Handler interface {
ServeHTTP(ResponseWriter, *Request) //handler的路由实现器
}

ServeMux就是这个系统默认的路由器。

最后,总结一下这个部分:

  1. 调用http.HandleFunc("/", sayHello)
  2. 调用DefaultServeMuxHandleFunc(),把我们定义的sayHello()包装成HandlerFunc类型
  3. 继续调用DefaultServeMuxHandle(),向DefaultServeMuxmap[string]muxEntry中增加路由规则和对应的handler

OK,这部分代码做的事就这么多,第一部分结束。

第二部分主要就是研究这句代码err := http.ListenAndServe(":9090",nil),也就是ListenAndServe这个方法。从这个方法点进去,如下:

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

在这个方法中,初始化了一个server对象,然后调用这个server对象的ListenAndServe方法,在这个方法中,如下:

func (srv *Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

在这个方法中,调用了net.Listen("tcp", addr),也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。

代码的最后,调用了srvServe方法,如下:

func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
if fn := testHookServerServe; fn != nil {
fn(srv, l)
}
var tempDelay time.Duration // how long to sleep on accept failure if err := srv.setupHTTP2_Serve(); err != nil {
return err
} srv.trackListener(l, true)
defer srv.trackListener(l, false) baseCtx := context.Background() // base is always background, per Issue 16220
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
for {
rw, e := l.Accept()
if e != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}

最后3段代码比较重要,也是Go语言支持高并发的体现,如下:

c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)

上面那一大坨代码,总体意思是进入方法后,首先开了一个for循环,在for循环内时刻Accept请求,请求来了之后,会为每个请求创建一个Conn,然后单独开启一个goroutine,把这个请求的数据当做参数扔给这个Conn去服务:go c.serve()。用户的每一次请求都是在一个新的goroutine去服务,每个请求间相互不影响。

connserve方法中,有一句代码很重要,如下:

serverHandler{c.server}.ServeHTTP(w, w.req)

表示serverHandler也实现了ServeHTTP接口,ServeHTTP方法实现如下:

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

在这里如果handler为空(这个handler就可以理解为是我们自定义的路由器),就会使用系统默认的DefaultServeMux,代码的最后调用了DefaultServeMuxServeHTTP()

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是Handler接口对象
h.ServeHTTP(w, r) //调用Handler接口对象的ServeHTTP方法实际上就调用了我们定义的sayHello方法
}

路由器接收到请求之后,如果是*那么关闭链接,如果不是*就调用mux.Handler(r)返回该路由对应的处理Handler,然后执行该handlerServeHTTP方法,也就是这句代码h.ServeHTTP(w, r)mux.Handler(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)
url := *r.URL
url.Path = p
return RedirectHandler(url.String(), 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
} func (mux *ServeMux) match(path string) (h Handler, pattern string) {
var n = 0
for k, v := range mux.m { //mux.m就是系统默认路由的map
if !pathMatch(k, path) {
continue
}
if h == nil || len(k) > n {
n = len(k)
h = v.h
pattern = v.pattern
}
}
return
}

它会根据用户请求的URL到路由器里面存储的map中匹配,匹配成功就会返回存储的handler,调用这个handlerServeHTTP()就可以执行到相应的处理方法了,这个处理方法实际上就是我们刚开始定义的sayHello(),只不过这个sayHello()HandlerFunc又包了一层,因为HandlerFunc实现了ServeHTTP接口,所以在调用HandlerFunc对象的ServeHTTP()时,实际上在ServeHTTP ()的内部调用了我们的sayHello()

总结一下:

  1. 调用http.ListenAndServe(":9090",nil)
  2. 实例化server
  3. 调用serverListenAndServe()
  4. 调用serverServe方法,开启for循环,在循环中Accept请求
  5. 对每一个请求实例化一个Conn,并且开启一个goroutine为这个请求进行服务go c.serve()
  6. 读取每个请求的内容c.readRequest()
  7. 调用serverHandlerServeHTTP(),如果handler为空,就把handler设置为系统默认的路由器DefaultServeMux
  8. 调用handlerServeHTTP() =>实际上是调用了DefaultServeMuxServeHTTP()
  9. ServeHTTP()中会调用路由对应处理handler
  10. 在路由对应处理handler中会执行sayHello()

有一个需要注意的点:

DefaultServeMux和路由对应的处理方法handler都实现了ServeHTTP接口,他们俩都有ServeHTTP方法,但是方法要达到的目的不同,在DefaultServeMuxServeHttp()里会执行路由对应的处理handlerServeHttp()

文章转自

https://juejin.cn/post/6844903550993039374

golang web源码解析的更多相关文章

  1. 04、NetCore2.0下Web应用之Startup源码解析

    04.NetCore2.0Web应用之Startup源码解析   通过分析Asp.Net Core 2.0的Startup部分源码,来理解插件框架的运行机制,以及掌握Startup注册的最优姿势. - ...

  2. 【源码解析】凭什么?spring boot 一个 jar 就能开发 web 项目

    问题 为什么开发web项目,spring-boot-starter-web 一个jar就搞定了?这个jar做了什么? 通过 spring-boot 工程可以看到所有开箱即用的的引导模块 spring- ...

  3. 深入学习 esp8266 wifimanager源码解析(打造专属自己的web配网)

    QQ技术互动交流群:ESP8266&32 物联网开发 群号622368884,不喜勿喷 单片机菜鸟博哥CSDN 1.前言 废话少说,本篇博文的目的就是深入学习 WifiManager 这个gi ...

  4. Spring框架之spring-web web源码完全解析

    Spring框架之spring-web web源码完全解析 spring-web是Spring webMVC的基础,由http.remoting.web三部分组成,核心为web模块.http模块封装了 ...

  5. springboot源码解析-管中窥豹系列之web服务器(七)

    一.前言 Springboot源码解析是一件大工程,逐行逐句的去研究代码,会很枯燥,也不容易坚持下去. 我们不追求大而全,而是试着每次去研究一个小知识点,最终聚沙成塔,这就是我们的springboot ...

  6. kubernetes源码解析---- apiserver路由构建解析(1)

    kubernetes源码解析---- apiserver路由构建解析(1) apiserver作为k8s集群的唯一入口,内部主要实现了两个功能,一个是请求的路由和处理,简单说就是监听一个端口,把接收到 ...

  7. Gin框架源码解析

    Gin框架源码解析 Gin框架是golang的一个常用的web框架,最近一个项目中需要使用到它,所以对这个框架进行了学习.gin包非常短小精悍,不过主要包含的路由,中间件,日志都有了.我们可以追着代码 ...

  8. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

  9. ExcelReport第二篇:ExcelReport源码解析

    导航 目   录:基于NPOI的报表引擎——ExcelReport 上一篇:使用ExcelReport导出Excel 下一篇:扩展元素格式化器 概述 针对上一篇随笔收到的反馈,在展开对ExcelRep ...

随机推荐

  1. Python实用案例,Python脚本,Python实现文件自动归类

    前言: 今天我们就利用Python脚本实现文件自动归类吧.直接开整~ 预备知识 这个脚本实现比较简单,我把涉及的知识点列了出来. 1.相对路径.绝对路径,绝对路径就是最完整的路径. 'D:/code/ ...

  2. noip模拟22[d·e·f]

    noip模拟22 solutions 哈哈哈,这次暴力打满直接190,其实不到哈哈哈,187.. 这次的题暴力极其好打,但是正解确实不简单... 打了好久才改完这个题,改完的时候爽暴了 这些一个字母的 ...

  3. Session与Cookie的原理以及使用小案例>从零开始学JAVA系列

    目录 Session与Cookie的原理以及使用小案例 Cookie和Session所解决的问题 Session与Cookie的原理 Cookie的原理 Cookie的失效时机 小提示 Session ...

  4. Android无障碍宝典-talkback

    http://geek.csdn.net/news/detail/93269 http://geek.csdn.net/news/detail/135867

  5. OpenFaaS实战之八:自制模板(maven+jdk8)

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  6. TypeScript学习笔记(四)装饰器

    目录 一.装饰器的作用 二.类装饰器 1. 普通装饰器 为类扩展属性和方法 使用装饰器修改属性和重写方法 2. 装饰器工厂 三.属性装饰器 四.方法装饰器 使用方法装饰器对方法进行扩展 五.方法参数装 ...

  7. AcWing 第11场周赛题解

    计算abc 首先 \(0<=a<=b<=c\) 会随机给出 \(a+b,a+c,b+c,a+b+c\)的值 因为\(a,b,c\)都为正整数,所以\(a+b+c\)一定为最大值 然后 ...

  8. Alibaba-技术专区-Dubbo3总体技术体系介绍及技术指南(序章)

    Dubbo的背景介绍 Apache Dubbo 是一款微服务开发框架(是一款高性能.轻量级的开源 Java 服务框架),它提供了 RPC通信 与 微服务治理 两大关键能力.这意味着,使用 Dubbo ...

  9. Semi-automation Script Based on Sleep

    The following script auto login to server 49, send 2 commands and exit from the server. Create a aut ...

  10. Python语言系列-02-基础数据类型

    格式化输出 #!/usr/bin/env python3 # author:Alnk(李成果) # 百分号% 格式化输出 name = input('姓名:') age = input('年龄:') ...