Go Web 编程基础及原理


Web 服务器



Web 服务器的工作过程


一个 Web 服务器也被称为 HTTP 服务器,它通过 HTTP 协议与客户端进行通信。客户端与服务器之间的通信是非持久连接的,当服务器发送了应答后就与客户端断开连接,等待下一次请求。

  • 浏览器本身是一个客户端,根据用户输入的 URL ,浏览器首先会请求 DNS 服务器,通过 DNS 获取相应域名对应的 IP 。

  • 通过 IP 地址找到 IP 对应的服务器后,建立 TCP 连接。

  • 浏览器发送完 HTTP Request(请求)包,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务,返回 HTTP Response(响应)包。

  • 客户端收到来自服务器的响应后开始渲染该 Response 包里的主体(body),客户端收到全部的内容后,断开与该服务器之间的TCP连接。


访问一个Web站点的过程

  • 客户端通过 TCP/IP 协议建立到服务器的 TCP 连接。

  • 客户端向服务器发送 HTTP 协议请求包,请求服务器里的资源文档。

  • 服务器向客户机发送 HTTP 协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端。

  • 客户端与服务器断开,由客户端解释 HTML 文档,在客户端屏幕上渲染图形结果。


URI , URL 和 DNS 解析


URI

URI (Uniform Resource Identifier,统一资源标志符),用来标识 Web 上每一种可用资源,例如: HTML 文档、图像、视频片段、程序等都由一个 URI 进行标识。

URI 通常由资源的命名机制,标准存放资源的主机名,资源自身的名称组成。

URL

URL(Uniform Resource Locator,统一资源定位符)用于描述一个网络上的资源,基本格式如下:

scheme://host[:port#]/path/.../[?query-string][#anchor]

scheme        // 指定低层使用的协议(例如:http, https, ftp)

host          // HTTP 服务器的 IP 地址或者域名

port          // HTTP 服务器的默认端口是 80(可以省略),如果使用了别的端口,必须指明,例如 http://cqupt.edu.cn:8080/
    
path          // 访问资源的路径
 
query-string  // 发送给 http 服务器的数据
 
anchor        // 锚

在 Go 语言中,URI 结构体的定义如下:

type URI struct {
		Scheme 		string     		// 方案
		opaque 		string     		// 编码后的不透明数据
		User 		*Userinfo   	// 基本验证方式中 username 和 password 信息 
		Host 		string       	// 主机字段请求头
		Path 		string       	// 路径
		RawPath 	string
		OmitHost    bool 
		ForceQuery 	bool 
		RawQuery 	string   		// 查询字段
		Fragment 	string   		// 分片字段 
		RawFragment string
}

DNS 解析

DNS(Domain Name System)是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,用于 TCP/IP 网络,将主机名或域名转换为实际 IP 地址。


DNS工作原理

DNS 解析的过程如下:

  • 在浏览器中输入 www.qq.com 域名,操作系统会先检查自己本地的 hosts 文件是否有这个网址映射关系,如果有,就先调用这个 IP 地址映射,完成域名解析。

  • 如果 hosts 里没有这个域名的映射,则查找本地 DNS 解析器缓存,是否有这个网址映射关系,如果有,直接返回,完成域名解析。

  • 如果 hosts 与本地 DNS 解析器缓存都没有相应的网址映射关系,首先会找 TCP/IP 参数中设置的首选 DNS 服务器(本地 DNS 服务器),当此服务器收到查询时,如果要查询的域名包含在本地配置区域资源中,则返回解析结果给客户机,完成域名解析,此解析具有权威性。

  • 如果要查询的域名,不由本地 DNS 服务器区域解析,但该服务器已缓存了此网址映射关系,则调用这个 IP 地址映射,完成域名解析,此解析不具有权威性。

  • 如果本地 DNS 服务器本地区域文件与缓存解析都失效,则根据本地 DNS 服务器的设置(是否设置转发器)进行查询,如果未用转发模式,本地 DNS 就把请求发至 “根 DNS 服务器”,“根 DNS 服务器”收到请求后会判断这个域名(.com)由谁来授权管理,并会返回一个负责该顶级域名服务器的一个 IP 。本地 DNS 服务器收到 IP 信息后,将会联系负责 .com 域的这台服务器。这台负责 .com 域的服务器收到请求后,如果自己无法解析,它就会找一个管理 .com 域的下一级 DNS 服务器地址(qq.com)给本地 DNS 服务器。当本地 DNS 服务器收到这个地址后,就会找 qq.com 域服务器,重复上面的动作,进行查询,直至找到 www.qq.com 主机。

  • 如果用的是转发模式,此 DNS 服务器就会把请求转发至上一级 DNS 服务器,由上一级服务器进行解析,上一级服务器如果不能解析,找根 DNS 或把转请求转至上上级,以此循环。不管是本地 DNS 服务器用是否转发,还是根提示,最后都是把结果返回给本地 DNS 服务器,由此 DNS 服务器再返回给客户机。

DNS 解析的整个流程

通过上面的步骤,最后获取的是 IP 地址,即浏览器最后发起请求的时候是基于 IP 来和服务器做信息交互的。


HTTP 协议


HTTP 协议的基本概念

超文本传输协议(HTTP)是分布式、协作的、超媒体信息系统的应用层协议。HTTP 是一种让 Web 服务器与浏览器(客户端)通过 Internet 发送与接收数据的协议,它建立在 TCP 协议 之上,一般采用 TCP 的 80 端口,是一个无状态的请求/响应协议。

在 HTTP 协议中,客户端总是通过建立一个连接与发送一个 HTTP 请求来发起一个事务。服务器不能主动去与客户端联系,也不能给客户端发出一个回调连接,客户端与服务器端都可以提前中断一个连接。

HTTP 请求

客户端发送到服务器端的请求信息由请求行(Request Line),请求(Request Header),请求体(Request Body)组成。headerbody 之间有个空行。

  • 请求行

请求行由 请求方法URIHTTP 协议 / 协议版本 组成。

请求方法方法描述
GET请求页面,并返回页面内容
HEAD类似于GET请求,只不过返回的响应中没有具体的内容,用于获取报头
POST大多用于提交表单或上传文件,数据包含在请求体中
PUT从客户端向服务器传送的数据取代指定文档中的内容
DELETE请求服务器删除指定的资源
OPTIONS允许客户端查看服务器的性能
CONNECT把服务器当作跳板,让服务器代替客户端访问其他网页
TRACE回显服务器收到的请求,主要用于测试或诊断

一个 URL 地址用于描述一个网络上的资源,而 HTTP 中的 GET、POST、PUT、DELETE 方法对应着对这个资源的查、改、增、删 4 个操作。


GET 方法和 POST 方法的区别:

  • GET 一般用于获取/查询资源信息,而 POST 一般用于更新资源信息。GET请求消息体为空,POST请求带有消息体。

  • GET 提交的数据会放在 URL 之后,以 ? 分割 URL 和传输数据,参数之间以 & 相连,如EditPosts.aspx?name=test1&id=123456 。POST 方法是把提交的数据放在 HTTP 包的 Body 中。

  • GET 提交的数据大小有限制(因为浏览器对 URL 的长度有限制),而 POST 方法提交的数据没有限制。

  • GET 方式提交数据,会带来安全问题,比如一个登录页面,通过 GET 方式提交数据时,用户名和密码将出现在 URL 上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码。


  • 请求头

请求头包含服务器要使用的附加信息(Cookie 、 Referer 、 User-Agent 等)。

请求头示 例说明
AcceptAccept: text/plain,text/html指定客户端能够接收的内容类型
Accept-CharsetAccept-Charset: iso-8859-5字符编码集
Accept-EncodingAccept-Encoding: compress,gzip指定浏览器可以支持的 Web 服务器返回内容压缩编码类型
Accept-LanguageAccept-Language:en,zh浏览器可接受的语言
Accept-RangesAccept-Ranges: bytes可以请求网页实体的子范围字段
AuthorizationAuthorization: BasicdbxhZGRpbjpvcGVuIHNIc2Ftyd=HTTP 授权的授权证书
Cache-ControlCache-Control:no-cache指定请求和响应遵循的缓存机制
ConnectionConnection: close表示是否需要持久连接。(HTTP1.1默认进行持久连接)
CookieCookie: $Version=1; Skin=new;请求域名下的所有 cookie 值
Content-LengthContent-Length:348请求的内容长度
  • 请求体

请求体是指在 HTTP 请求中传输数据的实体,常用于 POST 、 PUT 等请求中。

HTTP 响应

HTTP 响应由服务器端返回给客户端,分为响应状态码(Response Status Code),响应头(Response Header)和响应体(Response Body)。

  • 状态码

状态码用来告诉 HTTP 客户端,HTTP 服务器是否产生了预期的 Response 。 HTTP/1.1 协议中定义了 5 类状态码,状态码由三位数字组成,第一个数字定义了响应的类别。

常见状态码类型:

状态码说明含义
1XX提示信息表示请求已被成功接收,继续处理
2XX成功表示请求已被成功接收,处理,接受
3XX重定向要完成请求必须进行更进一步的处理
4XX客户端错误请求有语法错误或请求无法实现
5XX服务器端错误服务器未能实现合法的请求
  • 响应头

响应头包含服务器对请求的应答信息,如 Content-Type 、 Server 、 Set-Cookie 等。

响应头说 明
Allow服务器支持哪些请求方法(如 GET、POST 等)
Content-Encoding文档的编码(Encode)方法。只有在解码之后才可以得到用 Content-Type 头指定的内容类型。利用 gzip 压缩文档能够显著地减少 HTML 文档的下载时间
Content-Length表示内容长度。只有当浏览器使用持久 HTTP 连接时才需要这个数据
Content-Type表示后面的文档属于什么 MIME 类型
Date当前的 GMT 时间
Expires应该在什么时候认为文档已经过期,从而不再缓存它
Last-Modified文档的最后改动时间。可以通过 If-Modified-Since 请求头提供一个日期,该请求将被视为一个有条件的 GET 请求。只有改动时间迟于指定时间的文档才会返回,否则返回一个 304(Not Modified)状态。
Last-Modified也可用setDateHeader()方法来设置
Location表示客户端应该当到哪里去提取文档,通常不是直接设置的
Refresh表示浏览器应该在多少时间之后刷新文档,以秒计
Server服务器的名字
Set-Cookie设置和页面关联的 Cookie
WWW-Authenticate客户应该在 Authorization 头中提供的授权信息。在包含 401(Unauthorized)状态行的应答中这个信息是必需的
  • 响应体

响应体是 HTTP 请求返回的内容,响应的正文数据都在响应体中。

Go 语言中的 HTTP

Go 语言中请求头和响应头使用 Header 类型表示,Header 类型是一个映射(map)类型,表示 HTTP 请求头中的多个键值对,其定义如下:

type Header map[string] []string

通过请求对象的 Header 属性可以访问到请求头信息。Header 属性是映射结构,提供了 Get() 方法以获取 key 对应的第一个值。Get() 方法的定义如下:

func (h Header) Get(key string)

Header 结构体的其他常用方法的定义如下:

func (h Header)Add(key, value string) 	// 添加头信息

func (h Header) Del(key string) 		// 删除头信息

func (h Header) Get(key string) string 	// 获取头信息

func (h Header)Set(key, value string) 	// 设置头信息

func(h Header) Write(w io.Writer) error	// 使用线模式 (in wire format) 写头信息

func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error

key 是 Header 字段名,value 是 Header 字段的值,同个字段多个值放在 string 的 slice 中。Write() 方法是将 Header 写进 Writer 中,比如从网络连接中发送出去,WriteSubSet() 方法和 Write() 方法类似,但可以指定 exclude[headerkey]==true 排除不写的字段。

请求体和响应体都由 Request 结构中的 Body 字段表示,Body 字段是一个 io.ReadCloser 接口,ReadCloser 接口的定义如下:

type ReadCloser interface {
		Reader
		Closer
}

Body 字段是 Reader 接口和 Closer 接口的结合,Reader 接口的定义如下:

type Reader interface {
		Read(p []byte) (n int, err error)
}

通过 Reader 接口可以看到,Read() 方法实现了 ReadCloser 接口,可以通过 Body.Read() 方法来读取请求体信息。

ResponseWriter 接口用于发送响应数据、响应 Header ,ResponseWriter 接口包含 WriteHeader()Header()Write() 三个方法。

type ResponseWriter interface {
    	Header() Header
    	Write([]byte) (int, error)
    	WriteHeader(statusCode int)
}
  • WriteHeader() 方法

WrteHeader() 方法支持传入一个整型数据来表示响应状态码,如果不调用该方法,则默认响应状态码是 200WriteHeader() 方法的主要作用是在 API 接口中返回错误码。

例如,可以自定义一个处理器 noAuth() 方法,并通过 w.WriteHeader() 方法返回一个 401 未认证状态码。

package main

import (
 		"fmt"
    	"net/http"
)

func noAuth(w http.ResponseWriter, r *http.Request) {
   		w.WriteHeader(401)      // 默认 200
   		fmt.Fprintln(w, "未授权,认证后才能访问该接口!")
}

func main() {
  		http.HandleFunc("/noAuth", noAuth)
    	err := http.ListenAndServe(":8080", nil)
    	if err != nil {
     			fmt.Println(err)
    	}
}

运行程序访问指定网址后得到如下结果:

运行访问结果图

(注意:在运行时,w 代表的是对应的 response 对象实例,而不是接口)。

  • Header() 方法

Header() 方法用于设置响应头,可以通过 Header().Set() 方法设置响应头,w.Header() 方法返回的是 Header 响应头对象,它和请求头共用一个结构体,因此在请求头中支持的方法这里都支持,比如可以通过 w.Header().Add() 方法新增响应头。

例如如果要设置一个 301 重定向响应,则只需要通过 w.WriteHeader() 方法将响应状态码设置为 301 ,再通过 w.Header().Set() 方法将 “Location” 设置为一个可访问域名即可。

package main

import (
 		"fmt"
    	"net/http"
)

func Redirect(w http.ResponseWriter, r *http.Request)  {
		// 设置一个 301 重定向,重定向无需响应体
    	w.Header().Set("Location", "https://www.baidu.com")
    	// WriteHeader()调用后,无法设置响应头
    	w.WriteHeader(301)      
}

func main() {
    	http.HandleFunc("/redirect", Redirect)
    	err := http.ListenAndServe(":8080", nil)
    	if err != nil {
     			fmt.Println(err)
    	}
}

运行程序后,打开浏览器访问网址 http://127.0.0.1:8080/redirect 就会重定向在指定的网址 https://www.baidu.com

提示:w.Header.Set() 方法应在 w.WriteHeader() 方法之前被调用,因为一旦调用了 w.WriteHeader() 方法就不能对响应头进行设置了。

  • Wite() 方法

Write() 方法用于将数据写入 HTTP 响应体中,响应头中 Content-Type 根据会传入数据自行判断。 Write() 方法可以返回字符数据,也可以返回 HTML 文档和 JSON 等常见的文本格式。

  • 例如编写一个用 Write() 方法返回字符数据的程序,该程序的具体内容如下:
package main

import (
   		"fmt"
    	"net/http"
)

func Welcome(w http.ResponseWriter, r *http.Request)  {
		w.Write([]byte("Welcome to CQUPT!"))
}

func main() {
 		http.HandleFunc("/welcome", Welcome)
    	err := http.ListenAndServe(":8080", nil)
    	if err != nil {
     			fmt.Println(err)
    	}
}

运行程序访问指定网址后得到如下结果:

运行访问结果图

  • 例如编写一个用 Write() 方法返回 HTML 文档的程序,该程序的具体内容如下:
package main

import (
   		"fmt"
    	"net/http"
)

func Home(w http.ResponseWriter, r *http.Request)  {
  		html := `<html> 
    	<head>
      		<title>Write 方法返回 HTML 文档</title>
    	</head> 
    	<body>
        	<h1>Welcome to CQUPT!
    	</body> 
    	</html>`
    	w.Write([]byte(html))
}

func main() {
    	http.HandleFunc("/", Home)
    	err := http.ListenAndServe(":8080", nil)
    	if err != nil {
    			fmt.Println(err)
    	}
}

运行程序访问指定网址后得到如下结果:

运行访问结果图

  • 例如编写一个用 Write() 方法返回 JSON 数据的程序,该程序的具体内容如下:
package main

import (
   		"encoding/json"
    	"fmt"
    	"net/http"
)

type Greeting struct {
    	Message string `json:"message"`
}
func Hello(w http.ResponseWriter, r *http.Request)  {
    	// 返回 JSON 格式数据
    	greeting := Greeting{
      			"Welcome to CQUPT!",
    	}
    	message, _ := json.Marshal(greeting)
   		w.Header().Set("Content-Type", "application/json")
    	w.Write(message)
}

func main() {
    	http.HandleFunc("/", Hello)
    	err := http.ListenAndServe(":8080", nil)
    	if err != nil {
      			fmt.Println(err)
    	}
}

运行程序访问指定网址后得到如下结果:

运行访问结果图

下面是一个综合示例:

package main

import (
		"fmt"
		"encoding/json"
		"net/http"
		"fmt"
)

func CommonWrite(w http.ResponseWriter, r *http.Request) {
		str := `<html>
		<head>
			<title>Go Web</title>
		</head>
		<body>
			<h1>Welcome to CQUPT!</h1>
		</body>
		</html>`
		w.Write([]byte(str))
}

func WriteHeader(w http.ResponseWriter,r *http.Request){
		w.WriteHeader(501)
		fmt.Fprintln(w,"not implemented service")
}

func Header(w http.ResponseWriter,r *http.Request){
		w.Header().Set("Location","https://www.baidu.com")
		w.WriteHeader(302)
}

type User struct {
		Name    string
		Friends []string
}

func JsonWrite(w http.ResponseWriter, r *http.Request) {
		var user = &User{
				Name:    "Xiong Hao",
				Friends: []string{"personA", "personB", "personC"},
		}
		w.Header().Set("Content-Type", "application/json")
		jsonData, _ := json.Marshal(user)
		w.Write(jsonData)
}

func main() {
		http.HandleFunc("/commonwrite", CommonWrite)
		http.HandleFunc("/writeheader", WriteHeader)
		http.HandleFunc("/header", Header)
		http.HandleFunc("/jsonwrite", JsonWrite)
		err := http.ListenAndServe(":8080", nil)
    	if err != nil {
      			fmt.Println(err)
    	}
}

运行程序访问指定网址后得到如下结果:

运行访问 writeheader 结果图

运行程序访问指定网址后得到如下结果:

运行访问 jsonwrite 结果图

运行程序访问指定网址后得到如下结果:
运行访问 commonwrite 结果图

程序说明:

  • Header() 这个 handler 用于设置响应的 Header ,这里设置了 302 重定向,客户端收到 302 状态码后会找 Location 字段的值,然后重定向到网址 http://www.baidu.comHeader() 方法构造响应头,构造好的 Header 会在稍后自动被 WriteHeader() 方法发送出去,如设置一个 Location 字段:
w.Header().Set("Location", "https://www.baidu.com")
  • Write() 方法发送响应数据,如发送 HTML 格式的数据,JSON
    格式的数据等。
str := `<html>
		<head>
			<title>Go Web</title>
		</head>
		<body>
			<h1>Welcome to CQUPT!</h1>
		</body>
		</html>`
w.Write([]byte(str))
  • writeheader() 这个 handler 用于显式发送 501 状态码。WriteHeader(int) 方法可以接一个数值 HTTP 状态码,同时它会将构造好的 Header 自动发送出去,如果不显式调用 WriteHeader() 方法,会自动隐式调用并发送 200 OK

  • JsonWrite() 这个 handler 用于发送 json 数据,发送之前先设置 Content-Type: application/json

  • CommonWrite() 这个处理器 handler 用于输出带 HTML 格式的数据。


Go Web 服务器



Go Web 服务器编程原理


Go Web 服务器请求和响应的流程如下:

  • 客户端发送请求。

  • 服务器端的多路复用器收到请求。

  • 多路复用器根据请求的 URL 找到注册的处理器,将请求交由处理器处理。

  • 处理器执行程序逻辑,如果必要,则与数据库进行交互,得到处理结果。

  • 处理密调用模板引擎将指定的模板和上一步得到的结果渲染成客户端可识别的数据格式(通常是 HTML 格式)。

  • 服务器端将数据通过 HTTP 响应返回给客户端。

  • 客户端拿到数据,执行对应的操作(如渲染出来呈现给用户)。


简单的 Go Web 服务器程序示例。

package main

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

func Action(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Welcome to CQUPT!")
}

func main() {
		http.HandleFunc("/action", Action)
    	if err := http.ListenAndServe(":8080", nil); err != nil {
        		log.Fatal(err)
        }
}

运行程序访问指定网址后得到如下结果:

运行访问结果图


多路复用器

多路复用器用于转发请求到处理器,会在映射中找出被请求 URL 最为匹配的 URL 将请求重定向至相应的处理器,它然后调用与之相对应的处理器的 ServeHTTP() 方法来处理请求。

DefaultServeMuxnet/http 包中默认提供的一个多路复用器,其实质是 ServeMux 的一个实例,如果没有为 Server 对象指定处理器,则服务器默认使用 DefaultServeMux 作为 ServeMux 结构体的实例。

DefualtSeveMux 也提供了 DefaultServeMux.Handle()DefaulterveMux.HandleFunc() 这两个函数,可用它们把 http.Handle()http.HandleFunc() 函数中的 patern 和 handler 绑定到 ServerMux 上,其结构体定义如下:

type ServeMux struct(
		mu sync.RWMutex			// 读写锁,由于请求涉及到并发处理
		m map[string]muxEntry	// 路由 map , pattern -> HandleFunc ,路由规则,一个 string 对应一个 mux 实体,这里的 string 就是注册的路由表达式
		hosts bool				// 是否包含 hosts
}
type muxEntry struct(
		explicit bool			// 是否精确匹配,http 包内都使用 ture
		h Handler 				// 路由对应的 Handler
		pattern string			// 路由
}

所有的路由和 Handler 的绑定最终都存储到这里,因为 net/http 包内仅支持精确匹配,所以使用标准的 net/http 包不能直接配置带参数的路由,只能配置参数前面的路径,然后在 Handler 内部再处理。

ServeMux 也是一个处理器,可以在需要时对其实施处理器串联,默认的多路复用器 DefaultServeMux 其声明语句如下:

var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux

HandleFunc() 函数用于为指定的 URL 注册一个处理器,HandleFunc() 处理器函数会在内部调用 DefaultServeMux 对象的对应方法,其内部实现如下:

// 为指定 URL 注册处理器
func HandleFunc(pattern string, handler func(ResponseWrite, *Request)) {
		DefaultServeMux.HandleFunc(pattern, handler)
}

通过上面的方法体可以看出,http.HandleFunc() 函数会将处理器注册到多路复用器中,用默认多路复用器还可以指定多个处理器,其使用方法如下:

package main

import (
  		"fmt"
    	"net/http"
)

type handle1 struct{}

func (h1 *handle1) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   		fmt.Fprintf(w, "This is handle1 !")
}

type handle2 struct{}

func (h2 *handle2) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  		fmt.Fprintf(w, "This is handle2 !")
}

func main() {
 		handle1 := handle1{}
    	handle2 := handle2{}
   		http.Handle("/handle1", &handle1)
    	http.Handle("/handle2", &handle2)
    	err := http.ListenAndServe(":8080", nil)
    	if err != nil {
      			fmt.Println(err)
    	}
}

运行程序访问指定网址后得到如下结果:

运行访问 handle1 结果图

运行程序访问指定网址后得到如下结果:

运行访问 handle2 结果图

http.Handle() 函数可以指定多个处理器,Handle() 函数的定义如下:

func Handle(pattern string, handler Handler) {
		DefaultServeMux.Handle(pattern, handler)
}

http.Handle() 函数调用了 DefaultServeMux.Handle() 方法来处理清求,服务器收到的每个请求都会调用对应多路复用器的 ServeHTTP() 方法,该方法的定义如下:

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
		handler := sh.srv.Handler
		if handler == nil {
      			handler = DefaultServeMux
 		}
		handler.ServeHTTP(rw, req)
}

DefaultServeMux.ServeHTTP 的执行流程如下:

h,_ := mux.Handler(r)
h.ServeHTTP(w,r)

Go 语言支持外部实现的路由器, ListenAndServe() 方法的第二个参数就可用以配置外部路由器,它是一个 Handler 接口,即外部路由器只要实现了 Handler 接口就可以在自己设计的路由器的 ServHTTP() 方法里面实现自定义路由功能。

ServeMux 对象的 ServeHTTP() 方法中,会根据 URL 查找注册的处理器,然后将请求交由它处理。默认的多路复用器在生产环境中不建议使用,因为 DefaultServeMux 是一个全局变量,所有代码(包括第三方代码)都可以修改它。

在实际应用中,一个 Web 服务器往往有很多的 URL 绑定,不同的URL 对应不同的处理器,服务器决定使用哪个处理器过程如下:

假如现在绑定了 3 个 URL,分别是 //hi/hi/web

  • 如果请求的 URL为 / ,则调用 / 对应的处理器。
  • 如果请求的 URL 为 /hi ,则调用 /hi 对应的处理器。
  • 如果请求的 URL 为 /hi/web ,则调用 /hi/web 对应的处理器。
package main

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

func indexHandler(w http.ResponseWriter, r *http.Request) {
  		fmt.Fprintf(w, "欢迎来到 Go Web 首页!处理器为:indexHandler!")
}

func hiHandler(w http.ResponseWriter, r *http.Request) {
   		fmt.Fprintf(w, "欢迎来到 Go Web 首页!处理器为:hiHandler!")
}

func webHandler(w http.ResponseWriter, r *http.Request) {
    	fmt.Fprintf(w, "欢迎来到 Go Web 首页!处理器为:webHandler!")
}

func main() {
  		mux := http.NewServeMux()
    	mux.HandleFunc("/", indexHandler)       // "/", "/hi/","/hi/web/"
    	mux.HandleFunc("/hi", hiHandler)        // "/hi"
    	mux.HandleFunc("/hi/web", webHandler)   // "/hi/web"

    	server := &http.Server {
       			Addr: ":8080",
        		Handler: mux,
   		}

   		if err := server.ListenAndServe(); err != nil {
        		log.Fatal(err)
    	}
}

运行程序访问指定网址后得到如下结果:

运行访问 / 结果图

运行程序访问指定网址后得到如下结果:

运行访问 /hi 结果图

运行程序访问指定网址后得到如下结果:

运行访问 /hi/web 结果图


自定义多路复用器

自定义多路复用器通过直接调用 http.NewServeMux()函数实现,然后,在新创建的多路复用器上注册处理器,使用方法如下:

mux := http.NewSeryeMux()
mux.HandleFunc("/",func_name)

这个处理器函数的第 1 个参数表示匹配的路由地址,第 2 个参数表示一个名为 func_name 的函数,用于处理具体业务逻辑,下面是一个读超时和写超时均为 5 s 的服务器示例。

package main

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

func Hello(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Time out!")
}

func main() {
 		mux := http.NewServeMux()
    	mux.HandleFunc("/hello", Hello)

    	server := &http.Server{
       			Addr: 			":8081",
       			Handler: 		mux,
        		ReadTimeout:  	5 * time.Second,
        		WriteTimeout: 	5 * time.Second,
		}

   		if err := server.ListenAndServe(); err != nil {
      			log.Fatal(err)
    	}
}

运行程序访问指定网址后得到如下结果:


处理器与处理器函数

Web 服务器在收到请求后,会根据其 URL 将请求交给相应的多路复用器,多路复用器将请求转发给处理器处理,处理器是实现了 Handler 接口,其定义如下:

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

Handler 接口中只有一个 ServeHTTP() 处理器方法,任何实现了 Handler 接口的对象,都可以被注册到多路复用器,例如:

package main

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

type WelcomeHandler struct {
     	Language string
}

func (h WelcomeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		fmt.Fprintf(w, "%s", h.Language)
}

func main() {
   		mux := http.NewServeMux()
    	mux.Handle("/cn", WelcomeHandler{Language: "欢迎一起来学 Go Web!"})
    	mux.Handle("/en", WelcomeHandler{Language: "Welcome you, let's learn Go Web!"})

    	server := &http.Server {
           		Addr:   ":8080",
            	Handler: mux,
    	}

    	if err := server.ListenAndServe(); err != nil {
          		log.Fatal(err)
    	}
}

运行程序访问指定网址后得到如下结果:

运行访问 /en 结果图

运行程序访问指定网址后得到如下结果:

运行访问 /cn 结果图

net/http 包的 Handle()HandleFunc() 这两个函数都是接收两个参数,第一个参数都是 pattern (请求路径),因为其效果都是给路径绑定处理函数,所以两个函数的作用是一样的;对于第二个参数,一个是 Handler 接口类型,也就是说只要实现了该接口的函数都可以作为第二个参数传入;另一个则是以函数类型作为参数,只要传入的函数以 func(w http.ResponseWriter , r *http.Request) 形式声明。虽然上面的两个函数在使用的时候有些区别,但对于 Go 语言的底层实现来说都是交给 DefaultServeMux 来完成处理函数和路由的绑定。

net/http 包提供了用 HandleFunc() 函数来注册处理器,如果一个函数实现了函数 func(w http.ResponseWriter,r *http.Request) ,则这个函数被为 “处理器函数”, HandleFunc() 函数内部调用了 ServeMux 对象的 Handlefunc() 方法,ServeMux 对象的 HandleFunc() 方法具体代码如下:

func (mux *ServeMux) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
  		if handler == nil {
        		panic("http: nil handler")
    	}
    	mux.Handle(pattern, HandlerFunc(handler))
}

ServeMux.HandlerFunc() 函数调用了 HandlerFunc(f) 函数,类似强制类型转换 f 成为 HandlerFunc 类型,这样 f 就拥有了 ServeHTTP() 方法,最终实现了 Handler 接口的 ServeHTTP() 方法,其实现代码如下:

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

路由器接收到请求之后调用 mux.handler(r).ServeHTTP(w, r) 方法,也就是调用对应路由的 handler 的 ServerHTTP() 方法。

mux.handler(r) 函数会根据用户请求的 URL 与路由器里面存储的 map 匹配,当匹配到之后返回存储的 handler ,调用这个 handler 的 ServeHTTP() 方法就可以执行到相应的函数了,handler 的具体代码如下:

func (mux *ServeMux) handler(r *Request) Handler {
 		mux.mu.RLock()
    	defer mux.mu.RUnlock()
    	// Host-specific pattern takes precedence over generic ones
  		h := mux.match(r.Host + r.URL.Path)
    	if h == nil {
       			h = mux.match(r.URL.Path)
    	}
    	if h == nil {
      			h = NotFoundHandler()
    	}
    	return h
}

默认的处理器函数 HandleFunc() 注册一个处理器的实现方式如下:

http.HandleFunc("/", func_name)

这个处理器函数的第 1 个参数表示匹配的路由地址,第 2 个参数表示一个名为 func_name 的函数,用于处理具体业务逻辑。例如,注册一个处理器函数,并将处理器的路由匹配到 hi 函数,用来打印一个字符串到浏览器。

func hi(w http.ResponseWriter, r *http.Request)
		fmt.Fprintf(w, "Hi Web!")

简单总结:ServerMux 实现了 http.Handler 接口的 ServeHTTP(w ResponseWriter, r *Request) 方法,HandlerFunc() 是一个处理器函数,其内部通过对 ServeMux 中一系列方法的调用,在底层实现了 Handler 处理器接口的 ServeHTTP() 方法,从而实现处理器的功能。在创建 Server 时,如果设置 Handler 为空,则使用 DefaultServeMux 作为默认的处理器,而 DefaultServeMuxServerMux 的一个全局变量。

  • Handler :处理器接口,实现了 Handler 接口的对象,可以生到多路复用器中。
  • Handle() :注册处理器过程中的调用函数。
  • HandleFunc() :处理器函数。
  • HandlerFunc :底层为 func (w ResponseWriter , r *Request) 匿名函数,实现了 Handler 处理器接口,用来连接处理器函数与处理器。

ResponseWriter

io.Wrter 是一个接口类型,如果要使用 io.Writer 接口的 Write() 方法,则需要实现 Write([]byte) (n int, err error) 方法。

在 Go 语言中,客户端请求信息都被封装在 Request 对象中,但发送给客户端的响应并不是 Response 对象,而是 ResponseWriter 接口,ResponseWriter 接口是处理器用来创建 HTTP 响应的接口的。

ResponseWriter 接口的定义如下:

type ResponseWriter interface {
    	// 用于设置或者获取所有响应头信息
		Header() Header
    	// 用于写入数据到响应体中
    	Write([]byte) (int, error)
	    // 用于设置响应状态码
    	WriteHeader(statusCode int)
}

在底层支撑 ResponseWriter 接口的是 http.response 结构体,在调用处理器处理 HTTP 请求时,会调用 readRequest() 方法,readRequest() 方法会声明 response 结构体,并且其返回值是 response 指针。这也是在处理器方法声明时 Request 是指针类型,而 ResponseWriter 不是指针类型的原因,响应对象也是指针类型,readRequest() 方法的核心代码如下:

func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
		w = &response{
				conn: c,
        		cancelCtx: cancelCtx,
        		req: req,
        		reqBody: req.Body,
        		handlerHeader: make(Header),
        		contentLength: -1,
        		closeNotifyCh: make(chan bool, 1),
        		wants10KeepAlive: req.wantsHttp10KeepAlive(),
        		wantsClose: req.wantsClose(),
        }
		if isH2Upgrade {
      			w.closeAfterReply = true
        }
    	w.cr.res = w
    	w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSizw)
    	return w, nil
}

response 结构体的定义和 ResponseWriter 接口都位于 server.go 文件中,由于 response 结构体是私有的,对外不可见,所以只能通过 ResponseWriter 接口访问它。两者之间的关系是:ResponseWriter 是一个接口,而 response 结构体实现了该接口,引用 ResponseWriter 接口,实际上引用的是 response 结构体的实例。


Response

Response 是 HTTP 请求的响应,包含返回给浏览器端的数据,其结构体的定义和相关函数及方法如下:

type Response struct {
		Status string // e.g. "200OK"
		StatusCode int // e.g. 200
		Proto string // e.g. "HTTP/1.0"
		ProtoMajor int // e.g. 1
		ProtoMinor int e.g. 0
		Header Header
		Body io.ReadCloser
		ContentLength int64
		TransferEncoding[]string
		Close bool
		Uncompressed bool
		Trailer Header
		Request *Request
		TLS *tls.ConnectionState
}
func Get(url string) (resp *Response, err error)
func Head(url string) (resp *Response, err error)
func Post(url string, contentType string, body io.Reader) (resp *Response, err error)
func PostForm(url string, data url.Values) (resp *Response, err error)
func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)
func (r *Response) Cookies() []*Cookie
func (r *Response) Location() (*url.URL, error)
func (r *Response) ProtoAtLeast(major, minor int) bool
func (r *Response) Write(w io.Writer) error

Request

Request 是 HTTP 请求,用于返回 HTTP 请求的报文,里面包含了浏览器端的相关信息, Request 结构体的定义以及相关的函数、方法如下:

type Request struct {
		Method string 		// 请求的方法
    	URL *url.URL 		// 请求报文中的 URL 地址,是指针类型
    	Proto string 		// 形如:"HTTP/1.0”
    	ProtoMajor int 		// 1
    	ProtoMinor int 		// 0
    	Header Header		// 请求头字段
    	Body io.ReadCloser	// 请求体
    	GetBody func() (io.ReadCloser, error)
    	ContentLength int64
    	TransferEncoding []string
    	Close bool
    	// 请求报文中的一些参数,包括表单字段等
    	Host string
    	Form url.Values
    	PostForm url.Values
    	MultipartForm *multipart.Form
    	Trailer Header
    	RemoteAddr string
    	RequestURI string
    	TLS *tls.ConnectionState
    	Cancel <-chan struct{}
    	Response *Response
    	ctx context.Context
}
func NewRequest(method, url string, body io.Reader) (*Request, error)
func ReadRequest(b *bufio.Reader) (*Request, error)
func (r *Request) AddCookie(c *Cookie)
func (r *Request) BasicAuth() (username, password string, ok bool)
func (r *Request) Context() context.Context
func (r *Request) Cookie(name string) (*Cookie, error)
func (r *Request) Cookies() []*Cookie
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)
func (r *Request) FormValue(key string) string
func (r *Request) MultipartReader() (*multipart.Reader, error)
func (r *Request) ParseForm() error
func (r *Request) ParseMultipartForm(maxMemory int64) error
func (r *Request) PostFormValue(key string) string
func (r *Request) ProtoAtLeast(major, minor int) bool
func (r *Request) Referer() string
func (r *Request) SetBasicAuth(username, password string)
func (r *Request) UserAgent() string
func (r *Request) WithContext(ctx context.Context) *Request
func (r *Request) Write(w io.Writer) error
func (r *Request) WriteProxy(w io.Writer) error

Request 结构体主要用于返回 HTTP 请求的响应,只有正确地解析请求数据,才能向客户端返回响应。

下方是 Go 服务器端的代码,用于解析 Request 结构体中各成员(属性)。

package main

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

func GetRequest(w http.ResponseWriter, r *http.Request) {
		// 输出到服务器端的打印信息
    	fmt.Println("Request解析")
    	// HTTP 方法
    	fmt.Println("method:", r.Method)
    	// RequestURI 是被客户端发送到服务器端的请求行中未修改的请求
    	fmt.Println("RequestURI:", r.RequestURI)
    	// URL 类型,下方分别列出 URL 的各成员
    	fmt.Println("URL.Path:", r.URL.Path)
    	fmt.Println("URL.RawQuery:", r.URL.RawQuery)
    	fmt.Println("URL.Fragment:", r.URL.Fragment)
    	// 协议版本
    	fmt.Println("Proto:", r.Proto)
    	fmt.Println("ProtoMajor:", r.ProtoMajor)
    	fmt.Println("ProtoMinor:", r.ProtoMinor)
		// HTTP 请求头
    	for k, v := range r.Header {
    			for _, vv := range v {
        				fmt.Println("header key:" + k + " value:" + vv)
        		}
    	}
    	// 判断是否为 multipart 方式
    	isMultipart := false
    	for _, v := range r.Header["Content-Type"] {
    			if strings.Index(v, "multipart/form-data") != -1 {
        				isMultipart = true
        		}
    	}
    	if isMultipart == true {
    			r.ParseMultipartForm(128)
        		fmt.Println("解析方式:ParseMultipartForm")
    	} else {
        		r.ParseForm()
        		fmt.Println("解析方式:ParseForm")
    	}
    	// HTTP Body 内容长度
    	fmt.Println("ContentLength:", r.ContentLength)
    	// 是否在回复请求后关闭连接
    	fmt.Println("Close:", r.Close)
    	// HOST
    	fmt.Println("Host:", r.Host)
    	// 该请求的来源地址
    	fmt.Println("RemoteAddr:", r.RemoteAddr)
    	fmt.Fprintf(w, "hello, let's go!")
}

func main() {
		http.HandleFunc("/getrequest", GetRequest)
    	if err := http.ListenAndServe(":8080", nil); err != nil {
    			log.Fatal("ListenAndServe:", err)
    	}
}

运行程序访问指定网址后得到如下结果:

解析结果图


Server

在配置好路由、处理函数后,启动服务使用下面的 http.ListenAndServe() 方法。

http.ListenAndServe(":8080",nil)

第一个参数是监听的端口,以字符串的形式传递;第二个参数是 handler 传入的是 nil 该语句执行完毕后就开始监听 8080 端口,该函数的定义如下:

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

可以看出该方法首先是创建 Server 的实例,并且调用其 ListenAndServe() 方法,Server 结构体定义如下:

type Server struct{
		Addr string // 监听的地址和婚口
		Handler Handler // handlerto invoke,http.DefaultServeMux if nil 
		TLSContig*tls.Config // 读超时时间
		ReadTimeout time.Duration
		ReadHeaderTimeout time.Doration // 读关文件超时时间
		WriteTimeout time.Duration // 写超时时间
		IdleTimeout time.Duration
		MaxHeaderBytes int
		TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
		ConnState func (net.Conn, ConnState)
		ErrorLog *log.Logger
		BaseContext func(net.Listener) context.Context
		ConnContext func(ctx context.Context, c net.Conn) context.Context
	
		disableKeepAlives int32  // accessed atomically
		inShutdown int32   // accessedatomically (non-zero means we're in Shutdown)
		nextProtoonce sync.Once // guards setupHTTP2 *init
		nextProtoErr error // result of http2.ConfigureServerShutdown if used
		mu sync.Mutex
		1isteners map[*net.Listener]struct{}
		activeConn map[*conn)struct{}
		doneChan struct{}
		onShutdownchan []func()
}

Server 的三个方法如下:

func(srv *Server) Serve(l net.Listener) error

func(srv *Server) ListenAndServe() error

func(srv *Server) ListenAndServeTLS(certFile, keyFile string) error
  • ListenAndServe() 方法的作用是开启 HTTP Server 服务。
  • ListenAndServeTLS() 方法的作用是开启 HTTPS Server 服务。

在 Serve 方法执行完成后,就执行 Conn (连接)的 Serve 方法,然后再通过 Conn 的 readRequest() 方法获取 Response ,从逻辑上来说 c.serve() 方法就是完成这个功能的,此处需关注以下三个接口。

  • ResponseWriter 接口
type ResponseWriter interface {
		// Header 方法返回 Response 返回的 Header 供读写
		Header() Header
		// Write 方法写 Response 的 Body
		Write([]byte) (int, error)
		// WriteHeader 方法根据 HTTP 状态码来写 Response 的 Header
		WriteHeader(int)
}

该接口的主要作用是供 Handler() 函数调用,用来生成要返回的 Response

  • Flusher 接口
type Flusher interface {
		Flush()	//刷新缓存区
}

该接口的主要作用是供 Handler 调用,将写缓存中的数据刷新到客户端。

  • Hijacker 接口
type Hijacker interface {
		Hijacker() (net.Conn, *bufio.ReadWriter, error)
}

该接口的主要作用是供 Handler 调用,用以关闭连接。

以上是 Handler 在执行过程中最常用、最重要的三个接口,正是这三个接口使 Handler 可以在处理完逻辑后把结果写回客户端。通过对 Server 可以理解在接受请求后调用 Handler 并且生成 response 的过程,response 生成以后数据是通过使用下面的方法用于把数据写到客户端。

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

该语句最终触发了路由绑定,w 是 response 的实例对象(此处的 w 是 ResponseWriter 接口),response 结构体定义如下:

type response struct {
		conn *conn // 保存此次 HTTP 连接的信息
		req *Request // 对应请求信息
		chunking bool // 是否使用 chunk
 		wroteHeader bool // header 是否已经执行过写操作
		wroteContinuebool //100 Continue response was written
		header Header  // 返回的 HTTP 的 Header
		written int64 // Body 的字节数
		contentLength int64 // Content 长度
		status int // HTTP 状态
		needSniffbool int //是否开启 sniff 。若不设置 Content-Type ,开启 sniff 能自动确定 Content-Type 
		closeAfterReply bool // 是否保持长链接。若 request 有 keep-alive,该字段就设置为 false 
		requestBodyLimitHit bool // 是否 requestBody 太大。当 requestBody 太大时,response 返回 411
}

服务器端需要返回给客户端的所有信息都包含在 response 中, response 的主要方法如下:

func(w *response) Header() Header
func(w *response) WriteHeader(code int)
func(w *response) Write(data (]byte) (n int, err error)
func(w *response) WriteString(data string) (n int, err error)
func(w *response) write(lenDataint, dataB []byte, dataS string) (n int, err error) func(w *response) finishRequest()
func(w *response) Flush()
func(w *response) Hijack() (rwcnet.Conn, buf *bufio.ReadWriter, err error)

response 实现了 ResponseWriterFlusherHijacker 三个接口,有了这三个接口,response 就可以把处理结果写回客户端。


Conn

Go 语言为了实现高并发和高性能, 使用 goroutines 处理 Conn 的读写事件,这样每个请求都能保持独立,相互不会阻塞,以高效响应网络事件。

客户端的每次请求都会创建一个 Conn ,这个 Conn 里面保存了该次请求的信息,然后再传递到对应的 handler ,该 handler 中便可以读取到相应的 header 信息,以保证了每个请求的独立性。

下面通过源码来看 Go 语言对于 HTTP 请求的处理过程。

func(srv *Server) Serve(l net.Listener) error{
		defer l.Close()
		var tempDelay time.Duration// how long to sleep on accept failure
		for{
				rw, e := l.Accept()
				if e != nil {
						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 
						}
						log.Printf("http: Accept error: 8v; retrying in Bv", e, tempDelay)
						time.Sleep(tempDelay)
						continue
						}
					return e
				}
	
				tempDelay = 0
				c, err:=srv.newConn(rw)
				if err != nil {
						continue
				}
				go c.serve()
		}
}
  • Server 的 Serve(L net.Listener) 方法监听和处理 HTTP 请求。

  • 函数中使用了一个 for 循环,通过参数 Listener 的 Accept 接收请求。

  • 在基于接收的信息新建一个 Conn ,启动一个 goroutine 来单独为一个 Conn 服务。

  • Conn 首先会解析 request ,然后获取相应的 handler ,在调用函数 ListenAndServe() 函数的第二个参数,传递的是 nil 则默认获取的路由器 handler 是 DefaultServeMux

  • 调用 http.HandleFunc() 函数注册请求的路由规则,当请求 URL 为指定的路径时路由就会转到 handle 函数,DefaultServeMux 会调用 ServeHTTP() 方法,最后通过写入 response 反馈到客户端。


Go Web 服务器运作原理


Go 语言中 net/http 包运行过程如下:

  • 创建 Listen Socket,监听指定的端口,等待客户端请求。

  • Listen Socket 接受客户端的请求, 得到 Client Socket ,接下来通过 Client Socket 与客户端通信。

  • 处理客户端的请求,首先从 Client Socket 读取HTTP请求的协议头,如果是 POST 方法,还可能要读取客户端提交的数据,然后交给相应的 handler 处理请求,handler 处理完毕准备好客户端需要的数据,通过 Client Socket 写给客户端。

net/http 包的运行示意图

整个的过程中,Go 语言主要通过 监听端口接受处理客户端请求分配 handler 来让 Web 运作起来,具体步骤如下:

  • 第一步:调用 http.HandleFunc() 函数注册路由和对应处理函数。

  • 第二步:先后调用 DefaultServeMuxHandleFunc()Handle() 函数,并且向 handler (map[string]muxEntry) 中注册路由和对象函数。

  • 第三步:实例化 Server 对象,并调用 ListenAndServe() 方法。

  • 第四步:调用 net.Listen(“tcp”,addr) 函数,等待请求,每一个请求创建一个 Conn ,并且启动一个 goroutine 处理请求。

  • 第五步:通过 readRequest() 方法读取请求内容或者说 response 的取值过程。

  • 第六步:进入 ServeHandler.ServeHTTP() 方法,在 ServeHTTP() 方法内会判断是否有自定义的 handler ,如果没有则使用默认的 DefaultServeMux

  • 第七步:调用 handlerDefaultServeMuxServeHTTP() 方法。

  • 第八步:通过 request 选择匹配的 handler ,遍历 muxEntry ,寻找满足这个 Request 的路由。如果找到满足条件的路由,调用对象 handlerServeHTTP() 方法;如果没有找到满足条件的路由,调用 NotFoundHandlerServeHTTP() 方法。

下面通过一个具体的实例来说明。

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 CQUPT !")
}
        
func main() {
		mux := &MyMux{}
   		http.ListenAndServe(":8080", mux)
}

运行程序访问指定网址后得到如下结果:

运行访问结果图

Go Web 运作原理如下:

运行流程图

(1)调用 http.HandleFunc() 函数,按顺序做如下操作:

  • 调用了 DefaultServerMux 的 HandleFunc() 函数 。
  • 调用了 DefaultServerMux 的 Handle() 方法。
  • 向 DefaultServeMux 的 map[string]muxEntry 中增加对应的 handler 和路由规则。

(2)调用 http.ListenAndServe(":8080", nil) 方法,按顺序做如下操作:

  • 实例化 Server
  • 调用 Server 的 ListenAndServe() 函数。
  • 调用 net.Listen("tcp", addr) 函数 监听端口。
  • 启动一个 for 循环,在循环体中 Accept 请求。
  • 对每个请求实例化一个 Conn ,并且开启一个 goroutine 为这个请求进行服务 go c.serve()

(3)判断 handler 是否为空,按顺序做如下操作:

  • 读取每个请求的内容,调用 c.readRequest() 函数判断 handler 是否为空,如果没有设置 handler , handler 默认为 DefaultServeMux 。
  • 调用 handler 的 ServeHTTP() 方法(在此例中,进入到 DefaultServerMux.ServeHTTP() 方法),根据 request 选择 handler ,并且进入到这个 handler 的 ServeHTTP() 方法, mux.handler(r) 方法。
  1. 选择 handler ,按顺序做如下操作:
  • 判断是否有路由能满足这个 request(循环遍历 ServerMux 的 muxEntry
  • 如果有路由满足,调用这个路由 handler 的 ServeHTTP() 方法。
  • 如果没有路由满足,调用 NotFoundHandler 的 ServeHTTP() 方法。

net/http 包的核心源码按照功能整理如下:

(1)路由注册。

// http.handlerFunc
type HandlerFunc func(ResponseWriter, *Request)

func(f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))

func(mux *Request) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) 

// http.Handle
func (mux *ServeMux) Handle(pattern string, handler Handler)

(2)接口监听。

func ListenAndServe(addr string, handler Handler) error

func (srv *Server) ListenAndServe() error

(4)接收客户端请求。

  • Server 的 Serve() 方法:
func (srv *Server) Serve(l net.Listener) error
  • Server 的 newConn() 方法:
func (srv *Server) newConn(rwc net.Conn) *conn

(5)分配 Handler 。

// c.serve()
func (c *conn) serve()

// c.readRequest()
func (c *conn) readRequest()(w *response, err error)

// ServeHTTP(w, w.req)
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request)

// DefaultServeMux
type ServeMux struct
type muxEntry struct

(6)Handler 接口。

type Handler interface

// ServeMux.ServeHTTP
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

// mux.Handler(r)
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) 

func (mux *ServeMux) handler(host, path string)(h Handler, pattern string)

  • 参考书籍:《Go Web 编程》(谢孟军 著)
  • 参考书籍:《Go Web 编程从入门到精通》(廖显东 著)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

물の韜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值