在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 := patternif 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.RWMutexm     map[string]muxEntry //路由规则,一个string对应一个mux实例对象,map的key就是注册的路由表达式(string类型的)hosts bool // whether any patterns contain hostnames
}//muxEntry
type muxEntry struct {explicit boolh        Handler //这个路由表达式对应哪个handlerpattern  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.Addrif 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 failureif 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 16220ctx := 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 ErrServerCloseddefault:}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 = 0c := srv.newConn(rw)c.setState(c.rwc, StateNew) // before Serve can returngo 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.Handlerif 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.URLurl.Path = preturn 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 onesif 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 = 0for k, v := range mux.m {  //mux.m就是系统默认路由的mapif !pathMatch(k, path) {continue}if h == nil || len(k) > n {n = len(k)h = v.hpattern = 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()

Go的web工作原理相关推荐

  1. HTTP协议和web工作原理

    HTTP协议和web工作原理 http://blog.csdn.net/kjfcpua/archive/2009/12/04/4932597.aspx 本章学完之后能干什么? 要把 知识点学好,那就需 ...

  2. Linux运维系列总结-Linux系统启动过程、WEB工作原理、DHCP工作原理、DNS解析原理、NFS网络文件系统、FTP文件传输协议、PXE+KICKSTART自动安装系统

    Linux运维系列总结-Linux系统启动过程.WEB工作原理.DHCP工作原理.DNS解析原理.NFS网络文件系统.FTP文件传输协议.PXE+KICKSTART自动安装系统 1.Linux系统的启 ...

  3. linux服务器基础知识及工作原理汇总

    服务器基础 一.服务器的种类 1.按网络规模划分 按网络规模划分,服务器分为工作组级服务器.部门级服务器.企业级服务器 2.按架构划分 按照服务器的结构,可以分为CISC(复杂指令集)架构的服务器和R ...

  4. 一些零碎知识(域名、DNS、浏览器、动态静态页面、web应用系统工作原理)

    域名: http://localhost:8080/practice(胡写的,用于说明问题) http:表明当前请求是http协议,所有的Java Web应用程序都是基于HTTP协议,HTTP全称Hy ...

  5. struts2中struts.xml和web.xml文件解析及工作原理

    转自:https://www.cnblogs.com/printN/p/6434526.html web.xml <?xml version="1.0" encoding=& ...

  6. Web服务器的工作原理

    了解WEB服务器的工作原理和相关概念是后台开发人员的必修课,这篇文章来自 : Web服务器的工作原理 很多时候我们都想知道,web容器或web服务器(比如Tomcat或者jboss)是怎样工作的?它们 ...

  7. java session原理_java web开发—session的工作原理总结

    session的工作原理总结 一.什么是session session是一次浏览器和服务器交互的会话,在jsp中,作为一个内置对象存在.我的理解,就是当用户打开网页时,程序会在浏览器中开辟一段空间来存 ...

  8. 【技术干货】浏览器工作原理和常见WEB攻击 (下)

    本文作者:上海驻云开发总监 陈昂 上篇给大家带来的是关于浏览器基本工作原理的总结和介绍,这篇文章重点给大家说明有哪些常见WEB攻击. 常见WEB攻击 互联网是个面向全世界的开放平台,越是开放的东西漏洞 ...

  9. 【科普】Web(瓦片)地图的工作原理

    [译者按:在看MapBox Guides文档时,看到这篇 How do web maps work?,这篇文档通俗易懂地阐述了Web地图是如何工作的,其实更偏向讲瓦片地图的工作原理,鉴于之前很多人不了 ...

最新文章

  1. python预处理删除特殊字符_python - 如何删除包含特殊字符的字符串?_others_酷徒编程知识库...
  2. php使用webuploader表单上传文件覆盖文件key doesn't match with scope的问题和解决思路
  3. SURF算法与源码分析、下
  4. 【多线程】线程池拒绝策略详解与自定义拒绝策略
  5. 轻量级数据库Sqlite的使用
  6. 丢包和网络延迟对网络性能的影响
  7. 如何优雅地在公众号输入数学公式?推荐几款神器
  8. 【DP】小明游天界(zjoj 2149)
  9. [css] flex布局的缺点有哪些?(除兼容性外)
  10. 【POJ - 1724 】ROADS (带限制的最短路 或 dfs 或 A*算法,双权值)
  11. 前后两组结构相同的数据进行比较,找出新增的,需要删除的,原来存在的
  12. 51单片机学习板,超声波模块学习
  13. JS与C语言的数据类型转换
  14. Ubuntu 20.04 开启麦克风降噪功能
  15. 计算机系统基础期末复习--袁春风详细版
  16. 挽留梁孟松,中芯国际发大招了
  17. 这几种常见的保护电路,你都了解吗?
  18. MySQL——聚合函数和group by分组的使用
  19. CSS透明度[简述]
  20. 为什么我在繁忙的工作之余坚持写作?这几点是关键

热门文章

  1. Android无线测试之—UiAutomator UiObject API介绍二
  2. 块状元素的居中,首先设置宽度,再设 margin: 0 auto
  3. 微软企业库调用Oracle分页存储过程
  4. Oracle正则表达式的用法
  5. idea+springboot+mongodb的实战使用分享
  6. Linux的开源免费办公软件,开源免费Office办公套件(LibreOffice)
  7. Golang——Println与键盘录入
  8. c语言保存后怎么打开文件,保存打开文件之后,怎么也不能在显示函数中出来。。...
  9. 什么叫大地高_什么才叫睡得好?睡觉能满足这5个标准,说明睡眠质量高
  10. 德利捷读码器设置软件_S7-1500PLC+变频器+编码器组成位置控制系统