Go HTTP源码的学习笔记
(参考自build-web-application-with-golang)
假设有如下代码:
package main
import (
"fmt"
"net/http"
"strings"
"log"
)
func sayhelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() //解析参数,默认是不会解析的
fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Println(r.Form["url_long"])
for k, v := range r.Form {
fmt.Println("key:", k)
fmt.Println("val:", strings.Join(v, ""))
}
fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
}
func main() {
http.HandleFunc("/", sayhelloName) //设置访问的路由
err := http.ListenAndServe(":9090", nil) //设置监听的端口,第二个参数是路由,为空
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
我们从http.ListenAndServe()
开始深入探索。
先给出HTTP包工作的的大致流程,如下
http.HandleFunc("/", sayhelloName) //设置访问的路由
http.ListenAndServe(":9090", nil)
server := &Server{Addr: addr, Handler: handler} // addr即参数":9090",handler即参数nil
server.ListenAndServe()
ln, err := net.Listen("tcp", addr) // 调用net包函数, Listen
server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
for {
rw, err := ln.Accept() // TCP中的Accept
c := server.newConn(rw) // 建立新连接
go c.serve() // serve,这里的goroutine支持高并发
for {
w, err := c.readRequest()
serverHandler{c.server}.ServeHTTP(w, w.req)
// 这里令serverHandler{c.server}为sh吧
handler := sh.srv.Handler
if handler == nil { // 因为本例传入的handler参数为nil
handler = DefaultServeMux // 故调用默认路由,DefaultServeMux见下文
}
handler.ServeHTTP(rw, req) // rw就是传入的参数w,req就是参数w.req
}
}
Go是通过一个函数ListenAndServe
来完成Web运作的,这个底层是这样处理的:初始化一个server
对象,然后调用了net.Listen("tcp", addr)
,也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。
监控端口之后,调用了srv.Serve(net.Listener)
函数,这个函数就是处理接收客户端的请求信息。简化后,关键代码如下:
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
for { // Listen之后,循环(Accept->newConn->serve)
rw, err := l.Accept() // Accept
c, err := srv.newConn(rw) // 创建Connection,这里的c保存了每次的请求信息
go c.serve() // 处理请求,相当于创建一个新线程(go routine),实现高并发
}
}
若路由为空,则使用默认路由DefaultServeMux
,我们来看下其定义
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
DefaultServeMux
是一个ServeMux
类型
阅读ServeMux
的注释,可以知道:
- ServeMux是HTTP 请求的multiplexer. 它将每个请求的URL与注册过的路径(pattern)进行最长匹配。例如刚才有
http.HandleFunc("/", sayhelloName)
, 则"/"
是注册过的路径;当访问http://localhost/
时, 路由器将会把该URL与"/"
匹配。 - 最长匹配是指,当有handlers分别注册了路径
/repos/image/
和/repos/
时,当你访问/repos/image/
开头的路径,前者handler会被调用,当你访问/image/
时,后者handler才被调用。 - 路径(pattern)最好都以
/
结尾(我这样理解)。因为,假设路径/image/
被注册了,当你访问/image
时(没有/
结尾),路由器会帮你重定向至/image/
路径,除非/image
被注册过。
type ServeMux struct {
mu sync.RWMutex //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
m map[string]muxEntry // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
hosts bool // whether any patterns contain hostnames
}
其中muxEntry定义为:
type muxEntry struct {
explicit bool // 是否精确匹配;如果是,说明该pattern被注册过了,不能再注册
h Handler // 这个路由表达式对应哪个handler
pattern string //匹配字符串
}
其中Handler定义为:
type Handler interface {
ServeHTTP(ResponseWriter, *Request) // 路由实现器
}
可见其面向接口编程思想。
给DefaultServeMux
的handler
赋值的是http.HandleFunc("/", sayHelloName)
这一函数。
http.HandleFunc()
大致过程如下:
http.HandleFunc("/", sayhelloName) // 设置访问的路由
// 函数原型是HandleFunc(pattern string, handler func(ResponseWriter, *Request))
// DefaultServeMux在包中已被声明
DefaultServeMux.HandleFunc(pattern, handler) // pattern="/", handler=sayhelloName
DefaultServeMux.Handle(pattern, HandlerFunc(handler)) // 第二个参数是类型转换,见下文
// 这里用mux代替DefaultServeMux
mux.mu.Lock() // 加锁
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
// 将pattern与函数匹配,在刚才的例子中,是将"/"和sayhelloName匹配
mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
...
其中,HandlerFunc是一个类型声明
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
所以刚才DefaultServeMux.Handle(pattern, HandlerFunc(handler))
是把handler(即sayhelloName)强制转换为HandlerFunc
类型。
看回文章开头的流程,里面的handler.ServeHTTP(rw, req)
就会调用到这个f(w, r)
了
待续