net/http包的使用模式和原始碼解析
目錄:
Handler介面:所有請求的處理器、路由ServeMux都滿足該介面;
1
2
3
|
type Handler interface {
ServeHTTP(ResponseWriter,
*Request)
}
|
- http包有一個包級別變數DefaultServeMux,表示預設路由:var DefaultServeMux = NewServeMux(),使用包級別的http.Handle()、http.HandleFunc()方法註冊處理器時都是註冊到該路由中;
- ServeMux結構體有ServeHTTP()方法(滿足Handler介面),主要用於間接呼叫它所儲存的處理器的ServeHTTP()方法
1
2
3
4
5
|
type HandlerFunc func(ResponseWriter,
*Request)
//實現Handler介面的ServeHTTP方法
func (f
HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w,
r) //呼叫自身
}
|
二、HTTP伺服器的使用模式:
處理函式:只要函式的簽名為 func(w http.ResponseWriter, r *http.Request) ,均可作為處理函式,即它可以被轉換為http.HandlerFunc函式型別;
模式一:使用預設的路由來註冊處理函式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
var addr
= flag.String("addr", ":8080", "http
server address")
//1.不帶引數處理函式
func serveHome(w
http.ResponseWriter, r *http.Request) {
if r.URL.Path
!= "/" {
http.NotFound(w,
r)
return
}
http.ServeFile(w,
r, "home.html")
}
//2.帶引數處理函式,閉包函式隱式轉換為http.HandlerFunc函式型別
func myHandler(s
string) http.HandlerFunc {
return func(w
http.ResponseWriter, r *http.Request) {
if r.URL.Path
!= "/" {
http.NotFound(w,
r)
return
}
http.ServeFile(w,
r, s) //使用引數s
}
}
func main()
{
flag.Parse()
//向預設路由註冊處理器函式
http.HandleFunc("/",
serveHome) //或http.Handle("/",
http.HandlerFunc(serveHome))
http.Handle("/file",myHandler("somefile"))
err
:= http.ListenAndServe(*addr, nil) //啟動監聽,第二個引數nil表示使用預設路由DefaultServeMux中註冊的處理器
if err
!= nil {
log.Fatalln("ListenAndServe:
",
err)
}
}
|
模式二:使用自定義的路由來註冊處理函式:
1
2
3
4
5
6
7
8
9
10
|
func main()
{
mux
:= http.NewServeMux() //新建一個自定義的路由
mux.Handle("/file",myHandler("somefile"))
mux.HandleFunc("/",
serveHome)
err
:= http.ListenAndServe(*addr,mux) //啟動監聽
if err
!= nil {
log.Fatalln("ListenAndServe:
",
err)
}
}
|
模式三:直接自定義一個Server例項:該模式可以很方便的管理服務端的行為
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
mux
:= http.NewServeMux()
mux.Handle("/file",myHandler("somefile"))
mux.HandleFunc("/",
serveHome)
s
:= &http.Server{
Addr: ":8080",
Handler:
mux, //指定路由或處理器,不指定時為nil,表示使用預設的路由DefaultServeMux
ReadTimeout:
10 * time.Second,
WriteTimeout:
10 * time.Second,
MaxHeaderBytes:
1 << 20,
ConnState: //指定連線conn的狀態改變時的處理函式
//....
}
log.Fatal(s.ListenAndServe())
|
接下來,我們就跟蹤原始碼來仔細的分析下整個執行過程。
三、HTTP伺服器的執行過程:
1.使用http.ListenAndServe()方法啟動服務,它根據給定引數構造Server型別,然後呼叫server.ListenAndServe()
1
2
3
4
|
func ListenAndServe(addr
string, handler Handler) error {
server
:= &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
|
2.而server.ListenAndServe()方法內部呼叫net.Listen("tcp", addr),該方法內部又呼叫net.ListenTCP()建立並返回一個監聽器net.Listener,如下的ln;
1
2
3
4
5
6
7
8
9
10
11
|
func (srv
*Server) ListenAndServe() error {
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)})
}
|
3.然後把監聽器 ln 斷言轉換為 TCPListener 型別,並根據它構造一個 tcpKeepAliveListener 物件並傳遞給server.Serve()方法;
- 因為TCPListener實現了Listener介面,所以tcpKeepAliveListener也實現了Listener介面,並且它重寫了Accept()方法,目的是為了呼叫SetKeepAlive(true),讓作業系統為收到的每一個連線啟動傳送keepalive訊息(心跳,為了保持連線不斷開)。
1
2
3
4
5
6
7
8
9
10
11
12
|
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln
tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc,
err := ln.AcceptTCP()
if err
!= nil {
return
}
tc.SetKeepAlive(true) //傳送心跳
tc.SetKeepAlivePeriod(3
* time.Minute) //傳送週期
return tc,
nil
}
|
4.server.Serve()方法呼叫tcpKeepAliveListener 物件的 Accept() 方法返回一個連線conn(該連線啟動了心跳),併為每一個conn建立一個新的go程執行conn.server()方法:具體見程式碼中我加的註釋說明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
func (srv
*Server) Serve(l net.Listener) error {
defer l.Close()
if fn
:= testHookServerServe; fn != nil {
fn(srv,
l)
}
var tempDelay
time.Duration //重試間隔
if err
:= srv.setupHTTP2_Serve(); err != nil {
return err
}
srv.trackListener(l,
true) //快取該監聽器
defer srv.trackListener(l,
false) //從快取中刪除當前監聽器
baseCtx
:= context.Background()
ctx
:= context.WithValue(baseCtx, ServerContextKey, srv) //新建一個context用來管理每個連線conn的Go程
for {
rw,
e := l.Accept() //呼叫tcpKeepAliveListener物件的
Accept() 方法
if e
!= nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed //退出Serve方法,並執行延遲呼叫(從快取中刪除當前監聽器)
default:
}
//如果發生了net.Error錯誤,則隔一段時間就重試一次,間隔時間每次翻倍,最大為1秒
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) //該方法根據net.Conn、srv構造了一個新的http.conn型別
c.setState(c.rwc,
StateNew) //快取該連線的狀態,如果方法:Server.ConnState(net.Conn,
ConnState)不為nil,就根據當前連線的狀態執行它
go c.serve(ctx)
}
}
|
5.而conn.server()方法會讀取請求,然後根據conn內儲存的server來構造一個serverHandler型別,並呼叫它的ServeHTTP()方法:serverHandler{c.server}.ServeHTTP(w, w.req),該方法的原始碼如下:
1
2
3
4
5
6
7
8
9
10
|
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)
}
|
6.如上原始碼可以看到,當 handler == nil 時使用預設的DefaultServeMux路由,否則使用在第1步中為Serve指定了的Handler;然後呼叫該Handler的ServeHTTP方法(該Handler一般被設定為路由ServeMux型別);
7.而路由ServeMux的ServeHTTP方法則會根據當前請求提供的資訊來查詢最匹配的Handler(這裡為):
1
2
3
4
5
6
7
8
9
10
11
|
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) //規範化請求的路徑格式,查詢最匹配的Handler
h.ServeHTTP(w,
r)
}
|
8.以上查詢到的Handler介面值h就是我們事先註冊到路由中與請求匹配的Handler;而h的動態型別是HandlerFunc型別(它也滿足Handler介面);
所以,以上 h.ServeHTTP(w, r) 實際上呼叫的是介面值h中持有的動態值(也就是我們定義的處理函式)
1
2
3
4
5
|
type HandlerFunc func(ResponseWriter,
*Request)
//實現Handler介面的ServeHTTP方法
func (f
HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w,
r) //呼叫自身
}
|
至此,整個呼叫過程講解完畢,至於業務層的處理邏輯,則由各個處理函式實現
1
2
3
4
5
6
7
8
|
func main()
{
mux
:= http.NewServeMux()
mux.Handle("/to",http.RedirectHandler("http://example.org",
307))
err
:= http.ListenAndServe(*addr,mux) //啟動監聽
if err
!= nil {
log.Fatalln("ListenAndServe:
",
err)
}
}
|
好了,本文就暫時講關於http包關於HTTP服務端方面的東西,至於客戶端方面的就簡單引用一下官方文件說明吧,畢竟客戶端很少用Go實現。
五、客戶端的實現:
Get、Head、Post和PostForm函式發出HTTP/ HTTPS請求。
1
2
3
4
5
6
|
resp,
err := http.Get("http://example.com/")
...
resp,
err := http.Post("http://example.com/upload", "image/jpeg",
&buf)
...
resp,
err := http.PostForm("http://example.com/form",
url.Values{"key":
{"Value"}, "id":
{"123"}})
|
程式在使用完回覆後必須關閉回覆的主體。
1
2
3
4
5
6
7
|
resp,
err := http.Get("http://example.com/")
if err
!= nil {
//
handle error
}
defer resp.Body.Close()
body,
err := ioutil.ReadAll(resp.Body)
//
...
|
要管理HTTP客戶端的頭域、重定向策略和其他設定,建立一個Client:
1
2
3
4
5
6
7
8
9
10
|
client
:= &http.Client{
CheckRedirect:
redirectPolicyFunc,
}
resp,
err := client.Get("http://example.com")
//
...
req,
err := http.NewRequest("GET", "http://example.com",
nil)
//
...
req.Header.Add("If-None-Match",
`W/"wyzzy"`)
resp,
err := client.Do(req)
//
...
|
要管理代理、TLS配置、keep-alive、壓縮和其他設定,建立一個Transport:
1
2
3
4
5
6
|
tr
:= &http.Transport{
TLSClientConfig:
&tls.Config{RootCAs: pool},
DisableCompression:
true,
}
client
:= &http.Client{Transport: tr}
resp,
err := client.Get("https://example.com")
|
Client和Transport型別都可以安全的被多個go程同時使用。出於效率考慮,應該一次建立、儘量重用。
相關文章
- http server原始碼解析HTTPServer原始碼
- Picasso的使用和原始碼解析原始碼
- EventBus的使用和原始碼解析原始碼
- RxBinding使用和原始碼解析原始碼
- PyTorch ResNet 使用與原始碼解析PyTorch原始碼
- Binder的使用方法和原始碼解析原始碼
- 介紹 golang net/http 原始碼GolangHTTP原始碼
- YTKNetwork原始碼解析原始碼
- spark的基本運算元使用和原始碼解析Spark原始碼
- Retrofit2使用方式和原始碼解析原始碼
- Gson原始碼解析和它的設計模式原始碼設計模式
- 原始碼包和rpm包的區別原始碼
- 原始碼解析.Net中DependencyInjection的實現原始碼
- 使用Netty實現HTTP2伺服器/客戶端的原始碼和教程 - BaeldungNettyHTTP伺服器客戶端原始碼
- Go For Web:Golang http 包詳解(原始碼剖析)WebGolangHTTP原始碼
- redis原始碼解析----epoll的使用Redis原始碼
- .net core下優秀的日誌框架使用解析,附原始碼框架原始碼
- Netty原始碼深度解析(九)-編碼Netty原始碼
- Spring @Profile註解使用和原始碼解析Spring原始碼
- okhttp 原始碼解析 – http 協議的實現 – 重定向HTTP原始碼協議
- okhttp 原始碼解析 - http 協議的實現 - 重定向HTTP原始碼協議
- RxJava 原始碼解析之觀察者模式RxJava原始碼模式
- .Net Core中的配置檔案原始碼解析原始碼
- 原始碼解析.Net中Middleware的實現原始碼
- Netty原始碼解析2-ReactorNetty原始碼React
- Netty系列(一):NioEventLoopGroup原始碼解析NettyOOP原始碼
- Netty原始碼解析5-ChannelHandlerNetty原始碼
- 認真的 Netty 原始碼解析(二)Netty原始碼
- 認真的 Netty 原始碼解析(一)Netty原始碼
- 追蹤解析 Netty IntObjectHashMap 原始碼NettyObjectHashMap原始碼
- AFNetworking原始碼解析系列(1)原始碼
- AFNetworking原始碼解析系列(2)原始碼
- iOS原始碼解析—AFNetworking(RequestSerializer)iOS原始碼
- TextWatcher的使用及原始碼解析原始碼
- 設計模式(十)——組合模式(HashMap原始碼解析)設計模式HashMap原始碼
- gin原始碼閱讀之一 – net/http的大概流程原始碼HTTP
- GYHttpMock:使用及原始碼解析HTTPMock原始碼
- Laravel HTTP—— 重定向的使用與原始碼分析LaravelHTTP原始碼