【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,看具體做了什麼工作:先呼叫connection
的readRequest
方法構造一個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
呼叫 ServeMux
的 HandleFunc
方法
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
接著呼叫 ServeMux
的 Handle
方法向 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)
成員方法,實現 request
和 response
示例程式碼:
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)
成員方法,實現 request
和 response
,詳細步驟如下(不完全摘錄go原始碼):
step1:
呼叫 ServeHTTP(w, r){}
實現 request
和 response
,先呼叫 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].h
的 ServeHTTP(w, r)
方法。
參考文章
https://my.oschina.net/u/943306/blog/151293
https://studygolang.com/articles/16179