go語言大部分時候,做爲後端出現. 那麼它的最基本行爲就是關於"http請求"的發送與接收. 用久了不免會想知道這裏面究竟是怎麼工做的, 怎麼它就能接收請求了, 怎麼就能發送請求了.後端
下面這個片斷是一個簡單的小服務器, 咱們從下面這個簡單的小函數開始, 描述一下在這其中裏面到底都發生了什麼.服務器
func handleRequest(w http.ResponseWriter, r *http.Request) {
// ..
}
func main() {
http.HandleFunc("/", handleRequest)
_ = http.ListenAndServe(":8099", nil)
}
複製代碼
兩者組合在一塊兒,表示一個服務器,這個服務器具備多路選擇的功能, 能根據不一樣的狀況作出不一樣的處理 → http請求中連接的路徑不一樣作出不一樣的反應.併發
我是這樣理解的, 這個選擇器是一個登記中心, 執行http.HandleFunc("<path>", func(ResponseWriter, *Request))
的時候其實就是往這個登記中內心註冊一下這個函數, 稍後服務中心開始工做的時候會拿着登記冊裏的註冊信息, 根據路徑找處處理函數,來作出反應.app
type HandlerFunc func(ResponseWriter, *Request) 複製代碼
咱們都知道,經過調用第一行的函數,咱們能夠將一個函數註冊進去, 咱們要求這個函數能夠接收請求並寫一個返回值, 作一個能作響應的函數. 只有這樣的函數才能往登記中內心註冊tcp
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
func (mux *ServeMux) ServeHTTP(w,r) {}
func (f HandlerFunc) ServeHTTP(w,r) {
f(w, r)
}
複製代碼
處理工具的本質是一個用於處理HTTP請求的工具, 在後面, 咱們會給出咱們在什麼時候調用處理工具來處理HTTP請求,咱們對於這個工具惟一的指望, 就是但願這個"處理工具"可以實現ServeHTTP函數, 作到函數
r *Request
請求, 找處處理函數w Response
, 作出反饋ok, 回到上面工具
事實上, 在服務中心接收到請求的時候, 咱們直接找的"處理工具",而不是"登記中心", 只是恰巧, 在這個例子中. 咱們用的是"登記中心"ui
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry
hosts bool
}
type muxEntry struct {
h Handler
pattern string
}
複製代碼
// Part-1
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
// Part-2
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
// Part-3
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
// 作一些驗證工做
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
}
複製代碼
DefaultServeMux
登記, 在後面咱們能看到, 在啓動服務中心的時候咱們會使用這個默認的登記中心在已經有了登記中心之後,咱們會說說服務中心是怎麼開始工做的spa
_ = http.ListenAndServe(":9090", nil)
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
複製代碼
咱們拿着主機+端口信息, Handler=nil
不指定處理工具的方式, 生成一個服務中心, 讓這個服務中心開始工做code
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
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)})
}
複製代碼
負責監聽的是監聽器'ln', 服務中心只有有了監聽器才能收集到請求, 咱們設置好監聽地址,請求類型=tcp → 開始監聽
func (srv *Server) Serve(l net.Listener) error {
if fn := testHookServerServe; fn != nil {
fn(srv, l) // call hook with unwrapped listener
}
l = &onceCloseListener{Listener: l}
defer l.Close()
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
if !srv.trackListener(&l, true) {
return ErrServerClosed
}
defer srv.trackListener(&l, false)
var tempDelay time.Duration // how long to sleep on accept failure
baseCtx := context.Background() // base is always background, per Issue 16220
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
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)
}
}
複製代碼
收到新的請求時, 開啓一個協程, 生成一個Context上下文用於存儲數據, 這個協程負責去讀取請求以及作出反饋, 這個函數能夠理解爲, 只負責接收請求, 每次接收到請求, 就負責找人(協程)處理, 而它本身則迴歸原位繼續等下一個請求
// 簡化後, 這個函數在作什麼
func (c *conn) serve(ctx context.Context) {
for {
// PART-1:讀取請求正文,請求裏包含了什麼信息
w, err := c.readRequest(ctx)
// PART-2:找人手去處理這個請求
serverHandler{c.server}.ServeHTTP(w, w.req)
//PART-3: 處理完了,關閉請求,善後
w.finishRequest()
}
}
複製代碼
詳細的介紹一下,這裏是什麼一個場景, 首先須要明白,如今咱們還站在服務中心的維度上, 咱們面對的仍是一個鏈接對象, 這個函數的主體發起人仍是 c *conn
, 是一個鏈接對象
回顧HTTP協議,在HTTP協議中一個很是重要的概念叫作"鏈接", 有了鏈接再延伸一下就有了諸如長鏈接,鏈接等待一系列HTTP屬性, 幫你們回憶一下, 長鏈接是這麼辦的:
外圍這個大的for循環表明一個長鏈接,循環的讀取發來的請求,每次請求能夠分紅三步:
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{}
}
// 這個就是服務中心的處理工具, 它要處理HTTP請求
handler.ServeHTTP(rw, req)
}
複製代碼
從這裏開始, 咱們已經有了請求裏的正文, 咱們接下來開始找Server對象裏的處理工具, 用於處理請求.
在一開始, 在生成Server對象的時候, 咱們只給了監聽地址, 可是把處理工具設置爲nil,所以在下面的代碼中, 咱們要開始使用默認的登記中心, 做爲咱們的處理工具
拿到了處理工具, 咱們開始對着服務中心的處理工具,處理請求. 咱們調用ServeHTTP方法, 按照ServeHTTP方法的定義, 它必須能接收一個請求,而且能寫一個反饋, 能作響應.
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.ServeHTTP(w, r)
}
複製代碼
如今咱們已經回到登記中心了, 咱們要作的第一件事, 是根據請求內容, 找到對應的處理函數.
在上面咱們說過, 登記中心的任何函數都必須實現ServeHTTP方法, 所以咱們的第二件事就是執行函數ServeHTTP, 也就是執行這個執行函數自己.
// 第一步, 解析請求內容
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
...
host := stripHostPort(r.Host)
...
return mux.handler(host, r.URL.Path)
}
// 第二步, 嘗試找到匹配的函數
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// 開始匹配
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) {
// 看看這個路徑能不能直接匹配上
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
// 若是找不到直接匹配上, 找出最爲接近的
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
複製代碼