1. 程式人生 > 實用技巧 >【go語言學習】網路程式設計之HTTP

【go語言學習】網路程式設計之HTTP

一、go中HTTP服務處理流程

超文字傳輸協議(HTTP,Hyper Text Transfer Protocol)是網際網路上應用最為廣泛的一種網路傳輸協議,所有的WWW檔案都必須遵守這個標準。設計HTTP最初的目的是為了提供一種釋出和接收HTML頁面的方法。

HTTP 協議從誕生到現在,發展從1.0,1.1到2.0也不斷在進步。除去細節,理解 HTTP 構建的網路應用只要關注兩個端——客戶端(client)和服務端(server),兩個端的互動來自 client 的 request,以及server端的response。所謂的http伺服器,主要在於如何接受 client 的 request,並向client返回response。接收request的過程中,最重要的莫過於路由(router),即實現一個Multiplexer器。

Go中既可以使用內建的 multiplexer - DefaultServeMux,也可以自定義。Multiplexer路由的目的就是為了找到處理器函式(handler),後者將對request進行處理,同時構建response。

二、構建一個簡單的http服務

程式碼示例

package main

import "net/http"

func main() {
	// 1.設定路由
	// 訪問"/",呼叫indexHandleFunc函式處理
	http.HandleFunc("/", indexHandleFunc)
	// 訪問"/home",呼叫homeHandleFunc函式處理
	http.HandleFunc("/home", homeHandleFunc)
	// 2.開啟監聽
	http.ListenAndServe(":8080", nil)
}

func indexHandleFunc(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("index"))
}
func homeHandleFunc(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("home"))
}

執行程式

訪問http://localhost:8080/,頁面上顯示index
訪問http://localhost:8080/home/,頁面上顯示home

三、深入net/http包理解go語言http

1、http.Server

HTTP 伺服器在 Go 語言中是由 http.Server 結構體物件實現的。參考 http.ListenAndServe() 的實現:

// src/net/http/server.go

// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
  server := &Server{Addr: addr, Handler: handler}
  return server.ListenAndServe()
}

可見過程是先例項化 Server 物件,再完成 ListenAndServe 。其中 Serve 物件就是表示 HTTP 伺服器的物件。其結構如下 :

// src/net/http/server.go

type Server struct {
	Addr         string        // TCP 監聽地址, 留空為:":http"
	Handler      Handler       // 呼叫的 handler(路由處理器), 設為 nil 表示 http.DefaultServeMux
	ReadTimeout  time.Duration // 請求超時時長
	WriteTimeout time.Duration // 響應超時時長
	...
}

例項化了 Server 物件後,呼叫其 func (srv *Server) ListenAndServe() error 方法。該方法會監聽 srv.Addr 指定的 TCP 地址,並通過 func (srv *Server) Serve(l net.Listener) error 方法接收瀏覽器端連線請求。Serve 方法會接收監聽器 l 收到的每一個連線,併為每一個連線建立一個新的服務協程goroutine。

該 go 協程會讀取請求,然後呼叫 srv.Handler 處理並響應。srv.Handler 通常會是 nil,這意味著需要呼叫 http.DefaultServeMux 來處理請求,這個 DefaultServeMux 是預設的路由,我們使用 http.HandleFunc 就是在 http.DefaultServeMux 上註冊方法。

看一下詳細過程(不完全摘錄go原始碼):

step1:

初始化一個Server結構體例項後, 執行Server.ListenAndServer函式(其中主要呼叫net.Listen 和 Server.Serve函式)

func (srv *Server) ListenAndServe() error {
	// 呼叫net.Listen方法啟用監聽
	ln, err := net.Listen("tcp", addr)
	// 呼叫Server物件的Serve方法,將監聽物件作為引數傳入
	return srv.Serve(ln)
}

step2:

接著進入Server.Serve部分的程式碼,看看詳細的處理過程,可以看到主要是在主goroutine中用for迴圈阻塞,不斷通過Accept函式讀取連線請求,然後呼叫Server.newConn()函式,建立一個連線例項c,然後每個連線例項啟動一個新的goroutine去執行處理,從這裡也能看出, 如果併發請求很高的時候,會創建出海量的goroutine來併發處理請求。

這一步,呼叫newConn函式,會建立一個conn結構體的連線例項,其中的server成員變數是Server例項,即每個connection例項都保留了指向server的資訊。

func (srv *Server) Serve(l net.Listener) error {
	for {
		// 迴圈讀取連線請求
		rw, err := l.Accept()
		// 建立一個新的連線例項 c := &conn{server: srv, rwc: rw}
		c := srv.newConn(rw)
		// 啟動一個新的goroutine去執行處理
		go c.serve(connCtx)
	}
}

step3

接著進入每個connection處理的goroutine,看具體做了什麼工作:先呼叫connectionreadRequest方法構造一個response物件, 然後執行serverHandler{c.server}.ServeHTTP(w, w.req)進行實際的請求處理。

func (c *conn) serve(ctx context.Context) {
    for{
		...
		// 呼叫connection的readRequest函式構造一個response物件
        w, err := c.readRequest(ctx)
        ...
        req := w.req
		...
		// serverHandler{c.server}.ServeHTTP(w, w.req)進行實際的請求處理
        serverHandler{c.server}.ServeHTTP(w, w.req)
    }
}

step4

serverHandler是一個結構體,有一個成員屬性srv *Server,結合step 3可以看出,serverHandler{c.server}.ServeHTTP(w, w.req)就是例項化了一個serverHandler結構體,然後執行其成員方法 ServeHTTP,在ServeHTTP成員方法的定義中,可以看到,主要是呼叫了初始化server時的Handler成員方法,執行handler.ServeHTTP(rw, req)進行呼叫。

type serverHandler struct {
	srv *Server
}

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)
}
2、http.Handler
// Handler 介面
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)

任何結構體,只要實現了ServeHTTP方法,這個結構就可以稱之為Handler物件。

程式碼示例:

package main

import (
	"net/http"
)

type a struct{}

func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello world"))
}

func main() {
	// 從go的原始碼中可以發現函式的第二個引數是一個Handler物件,a實現了Handler的方法
	// a就可以看做是一個Handler物件傳入。
	http.ListenAndServe(":8080", &a{})
}

執行程式

訪問http://localhost:8080/,頁面上顯示hello world

3、http.HandleFunc

http.HandleFunc註冊路由,從原始碼中可以看到,http.HandleFunc的功能是向http包的DefaultServeMux加入新的處理路由。

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

DefaultServeMux 是一個 ServeMux 結構體的例項。

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux
5、http.ServeMux

ServeMux是一個結構體,其中有個m屬性是一個map型別,存放了不同 URL pattern 的處理Handler。同時ServeMux 也實現了 ServeHTTP 函式,是 http.Handler 介面的實現。

type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	hosts bool 
}

type muxEntry struct {
	h       Handler
	pattern string
}

// ServeMux實現了Handler介面
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)
}

DefaultServeMux 呼叫 ServeMuxHandleFunc 方法

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

接著呼叫 ServeMuxHandle 方法向 ServeMux 結構體的 m 欄位寫入資訊 map[pattern] = muxEntry{h: handler, pattern: pattern}

func (mux *ServeMux) Handle(pattern string, handler Handler) {}

四、go語言http的其他實現方式

1、自定義server

建立一個Server物件,可以設定server的其他引數。

package main

import (
	"net/http"
	"time"
)

type a struct{}

func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello world"))
}

func main() {
	// 建立一個新的Server物件
	s := http.Server{
		// 監聽埠
		Addr: "localhost:8080",
		// 處理路由
		Handler: &a{},
		// 設定超時
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
	}
	s.ListenAndServe()
}
2、使用自定義的serveMux
package main

import (
	"net/http"
)

type a struct{}

func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello world"))
}

func index(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("index"))
}
func page(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("page"))
}

func main() {
	mux := http.NewServeMux()
	// 通過Handle將a這個實現Handler介面的結構體,註冊到map表中
	mux.Handle("/", &a{})
	//通過HandleFunc方法去向mux中handler中註冊處理函式
	//其底層仍然是通過mux.Handle方法實現的
	mux.HandleFunc("/index", index)
	// 直接呼叫Handle方法去註冊處理函式,
	// 這裡的http.HandleFunc 是一個函式型別,這裡將page轉換為一個Handler介面的實現
	mux.Handle("/page", http.HandlerFunc(page))
	mux.HandleFunc("/home", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("home"))
	})

	http.ListenAndServe("localhost:8080", mux)
}

五、http的執行過程

1、傳入自己實現Handler介面的物件

http.ListenAndServe("localhost:8080", &a{}) 第二個引數傳入的是自己實現Handler介面的物件,http的server會呼叫這個物件的 ServeHTTP(w, r) 成員方法,實現 requestresponse

示例程式碼:

package main

import (
	"fmt"
	"net/http"
)

type a struct{}

func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	fmt.Println(r.URL.Path)
	switch r.URL.Path {
	case "/":
		w.Write([]byte("<h1>hello world</h1>"))
	case "/index":
		w.Write([]byte("<h1>index</h1>"))
	case "/home":
		w.Write([]byte(`<div style="color:red;font-size: 20px;">This is home page</div>`))
	default:
		w.Write([]byte(`<h1 style="color:red">404 NOT FOUND</h1>`))
	}
}

func main() {
	http.ListenAndServe("localhost:8080", &a{})
}
2、傳入自定義的ServeMux或預設的DefaultServeMux

http.ListenAndServe("localhost:8080", mux) 第二個引數傳入的是自定義的ServeMux或預設的DefaultServeMux(就是傳入 nil ),http的server會呼叫ServeMux的 ServeHTTP(w, r) 成員方法,實現 requestresponse ,詳細步驟如下(不完全摘錄go原始碼):

step1:
呼叫 ServeHTTP(w, r){} 實現 requestresponse ,先呼叫 mux.Handler(r) 方法返回一個Handler物件,呼叫其 ServeHTTP(w, r) 方法

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	h, _ := mux.Handler(r)
	h.ServeHTTP(w, r)
}

step2:在呼叫 mux.Handler(r *Request) 方法時,是接著呼叫
mux.handler(host, r.URL.Path) 方法

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
	return mux.handler(host, r.URL.Path)
}

step3:在呼叫 mux.handler(host, path string) 方法,是接著呼叫mux.match(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
}

step4: 在呼叫 mux.match(path) 方法時,返回 mux.m[path].h 一個Handler物件,其實就是註冊的路由器處理函式,並且和 r.URL.Path 相匹配,返回的 mux.m[path].pattern 就是路由地址 r.URL.Path

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}
	return nil, ""
}

step5:回到step1呼叫 mux.m[path].hServeHTTP(w, r) 方法。

參考文章
https://my.oschina.net/u/943306/blog/151293
https://studygolang.com/articles/16179