1. 程式人生 > >Go語言net/http包解析

Go語言net/http包解析

首先來看一個用net/http包寫的web伺服器:兩個函式實現http伺服器

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 world!") //這個寫入到w的是輸出到客戶端的
}

func main() {
	http.HandleFunc("/", sayhelloName) //設定訪問的路由
	err := http.ListenAndServe(":9090", nil) //設定監聽的埠
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

函式1:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
//HandleFunc註冊一個處理器函式handler和對應的模式pattern(註冊到DefaultServeMux)。ServeMux的文件解釋了模式的匹配機制。
var DefaultServeMux = NewServeMux()
//DefaultServeMux是用於Serve的預設ServeMux。
func NewServeMux() *ServeMux
//NewServeMux建立並返回一個新的*ServeMux

函式2:

func ListenAndServe(addr string, handler Handler) error
//ListenAndServe監聽TCP地址addr,並且會使用handler引數呼叫Serve函式處理接收到的連線。
//handler引數一般會設為nil,此時會使用DefaultServeMux(預設的路由器)。

--------------------------------------第一個函式解析-------------------------------------------------------------

1、HTTP包預設的路由器:DefaultServeMux

type ServeMux struct {  //ServeMux就是路由器型別
	mu sync.RWMutex   //鎖,由於請求涉及到併發處理,因此這裡需要一個鎖機制
	m  map[string]muxEntry  // 路由規則,一個string對應一個mux實體,這裡的string就是註冊的路由表示式
	hosts bool // 是否在任意的規則中帶有host資訊
}
//ServeMux型別是HTTP請求的多路轉接器。
//它會將每一個接收的請求的URL與一個註冊模式的列表進行匹配,並呼叫和URL最匹配的模式的處理器。
type muxEntry struct {
	explicit bool   // 是否精確匹配
	h        Handler // 這個路由表示式對應哪個handler
	pattern  string  //匹配字串
}
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)  // 路由器實現
}
//實現了Handler介面的物件可以註冊到HTTP服務端,為特定的路徑及其子樹提供服務。
//ServeHTTP應該將回復的頭域和資料寫入ResponseWriter介面然後返回。返回標誌著該請求已經結束,
//HTTP服務端可以轉移向該連線上的下一個請求。


路由器:就是訪問特定URI的時候,執行該URI指定的函式(controller)。

上面講到HandleFunc函式的作用是把一個模式(URI)對應的處理函式(controller)註冊到HTTP包預設的路由器-DefaultServeMux中。那麼這個註冊的過程是怎麼進行的呢?


註冊“模式-處理函式”對到HTTP預設路由器中:

註冊“模式-處理函式”對到路由器中,程式碼中就是把模式字串如"/",和函式名寫入到ServeMux的例項中-DefaultServeMux。也就是寫到map[string]muxEntry這個map裡面。模式字串對應string,而處理函式對應muxEntry結構體的Handler型別。從上面的幾個struct和interface中可以看出,如果要把我們自己寫的函式寫入到muxEntry的h中,函式必須要實現Handler這個介面。然而,從最上面的HTTP伺服器程式碼中看到sayHelloName這個函式並沒有實現Handler這個結構啊,這是怎麼實現的呢?

呼叫邏輯①

這裡就涉及到上面講到的HandleFunc這個函數了:

// HandleFunc registers the handler function for the given pattern in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	DefaultServeMux.HandleFunc(pattern, handler)
}

為什麼sayhelloName函式並沒有實現ServeHTTP這個介面,卻能新增到預設路由器DefaultServeMux裡面呢?從上面HandleFunc函式可以看出,當我們把自定義的處理函式如sayhelloName作為引數傳遞的時候,go語言上會有一個函式型別的轉換!

呼叫邏輯②

HandleFunc這個函式呼叫了 (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request) 這個方法:

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	mux.Handle(pattern, HandlerFunc(handler))
}

這裡面就有 HandlerFunc(handler) 這個型別轉換的過程。下面來看一下HandlerFunc這個型別:

// The HandlerFunc type is an adapter to allow the use of ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

所以,我們寫的sayHelloName這個處理函式就強制轉換成了HandlerFunc型別,而HandlerFunc型別實現了ServeHTTP這個方法,即HandlerFunc實現了type Handler interface這個介面。故而sayHelloName實現了type Handler interface介面。即我們可以把我們定義的“模式-處理函式”對寫入到預設路由器DefaultServeMux中。

呼叫邏輯③

回到上面提到的(ServeMux) HandleFunc(pattern, handler) 這個方法,這個方法裡面呼叫了(mux *ServeMux) Handle(pattern string, handler Handler) 這個方法:

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) { //這裡的mux就是上面HandleFunc傳入的DefaultServeMux,預設路由器
	mux.mu.Lock()
	defer mux.mu.Unlock()

	if pattern == "" {
		panic("http: invalid pattern")
	}
	if handler == nil {
		panic("http: nil handler")
	}
	if _, exist := mux.m[pattern]; exist {
		panic("http: multiple registrations for " + pattern)
	}

	if mux.m == nil {  //說明map[string]muxEntry 這個不存在
		mux.m = make(map[string]muxEntry)
	}
	mux.m[pattern] = muxEntry{h: handler, pattern: pattern}

	if pattern[0] != '/' {
		mux.hosts = true
	}
}

上面這個函式就是“URI-處理函式”的註冊過程。

--------------------------------------第一個函式解析完成--------------------------------------------------------

--------------------------------------第二個函式解析-------------------------------------------------------------

第二個函式:http.ListenAndServe(":9090", nil)

func ListenAndServe(addr string, handler Handler) error
//ListenAndServe監聽TCP地址addr,並且會使用handler引數呼叫Serve函式處理接收到的連線。
//handler引數一般會設為nil,此時會使用DefaultServeMux。

函式體:

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

例項化一個Server物件,Server型別定義了執行HTTP服務端的引數,Server的零值是合法的配置。Server其實是type Server struct,即一個結構體,具體欄位如下:

type Server struct {
    Addr           string        // 監聽的TCP地址,如果為空字串會使用":http"
    Handler        Handler       // 呼叫的處理器,如為nil會呼叫http.DefaultServeMux
    ReadTimeout    time.Duration // 請求的讀取操作在超時前的最大持續時間
    WriteTimeout   time.Duration // 回覆的寫入操作在超時前的最大持續時間
    MaxHeaderBytes int           // 請求的頭域最大長度,如為0則用DefaultMaxHeaderBytes
    TLSConfig      *tls.Config   // 可選的TLS配置,用於ListenAndServeTLS方法
    // TLSNextProto(可選地)指定一個函式來在一個NPN型協議升級出現時接管TLS連線的所有權。
    // 對映的鍵為商談的協議名;對映的值為函式,該函式的Handler引數應處理HTTP請求,
    // 並且初始化Handler.ServeHTTP的*Request引數的TLS和RemoteAddr欄位(如果未設定)。
    // 連線在函式返回時會自動關閉。
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
    // ConnState欄位指定一個可選的回撥函式,該函式會在一個與客戶端的連線改變狀態時被呼叫。
    // 參見ConnState型別和相關常數獲取細節。
    ConnState func(net.Conn, ConnState)
    // ErrorLog指定一個可選的日誌記錄器,用於記錄接收連線時的錯誤和處理器不正常的行為。
    // 如果本欄位為nil,日誌會通過log包的標準日誌記錄器寫入os.Stderr。
    ErrorLog *log.Logger
    // 內含隱藏或非匯出欄位
}

然後呼叫 (Server) ListenAndServe()方法進行監聽:

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
// If srv.Addr is blank, ":http" is used.
// ListenAndServe always returns a non-nil error.
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)})
}

net.Listen函式(dial.go): —— 監聽埠

func Listen(net, laddr string) (Listener, error)
返回在一個本地網路地址laddr上監聽的Listener。網路型別引數net必須是面向流的網路:
"tcp"、"tcp4"、"tcp6"、"unix"或"unixpacket"。參見Dial函式獲取laddr的語法。
func Listen(network, address string) (Listener, error) {
	addrs, err := DefaultResolver.resolveAddrList(context.Background(), "listen", network, address, nil)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
	}
	var l Listener
	switch la := addrs.first(isIPv4).(type) {
	case *TCPAddr:
		l, err = ListenTCP(network, la)
	case *UnixAddr:
		l, err = ListenUnix(network, la)
	default:
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
	}
	if err != nil {
		return nil, err // l is non-nil interface containing nil pointer
	}
	return l, nil
}

函式 srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}):——接收客戶端請求

func (srv *Server) Serve(l net.Listener) error {
	defer l.Close()
	var tempDelay time.Duration // how long to sleep on accept failure
	for {
		rw, e := l.Accept() //通過Listener接收請求
		if e != nil {
			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
				}
				log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
				time.Sleep(tempDelay)
				continue
			}
			return e
		}
		tempDelay = 0
		c, err := srv.newConn(rw) //建立一個Conn。Conn是net包裡面的一個介面 type Conn interface。Conn介面代表通用的面向流的網路連線。多個執行緒可能會同時呼叫同一個Conn的方法。
		if err != nil {
			continue
		}
		go c.serve() //Go語言高併發的體現
	}
}

具體底層的net.Listen和srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})就不再解析了,具體可以看原始碼。

--------------------------------------第二個函式解析完成--------------------------------------------------------

總結:利用net/http包建立一個HTTP伺服器

①首先要定義一個路由器,這裡可以自定一個路由器(如:mux := http.NewServeMux()),也可以使用http包預設的DefaultServeMux路由器。

②然後我們把要註冊的“模式(URI)-處理函式”對新增到mux中。模式好辦,就是一個string型別的值。對於處理函式,因為ServeMux路由器型別中map[string]muxEntry型別,裡面muxEntry型別,的處理函式是Handler型別,而Handler型別是介面型別,如下:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)  // 路由器實現
}

所以我們的處理函式必須要實現Handler這個介面,也就是實現ServeHTTP這個方法。最後通過

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

這個方法把“模式-處理函式”對新增到自定義/http包預設路由器中。

③最後一步是呼叫http.ListenAndServe(":9090",mux)函式,對指定的addr進行監聽就可以了。這個函式第二個引數選取的規則是:如果是自定義路由,傳入自定路由;如果是http包預設路由,傳入nil值即可。

附錄:

一個使用自定義路由的HTTP伺服器:

package main

import (
	"net/http"
	"log"
	"io"
)

type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter,r *http.Request){
	io.WriteString(w,"URL:"+r.URL.String()+"Method: "+r.Method)
}

func main() {
	mux:= http.NewServeMux()

	mux.Handle("/", &myHandler{}) //為什麼要傳指標過去呢?因為是指標實現了這個介面
	err:=http.ListenAndServe(":9090",mux)
	if err!=nil {
		log.Fatal(err)
	}
}
用http包實現http伺服器,使用高層封裝:第一個版本
*******************************************************************
package main

import (
	"net/http"
	"io"
	"log"
)

func main(){
	//設定路由
	http.HandleFunc("/", sayHello) //第二個引數指定了傳入函式的格式

	err:=http.ListenAndServe(":8081",nil)//使用http包預設的路由器DefaultServeMux時,傳入nil即可。
	if err!=nil{
		log.Fatal(err)
	}
}

func sayHello(w http.ResponseWriter, r *http.Request){ //這樣才能註冊進去路由
	io.WriteString(w, "hello world, this is version 1")
	//只要實現了writer介面就可以這麼傳。
}


	
註釋:http.ResponseWriter對Writer介面的實現。http包server.go檔案
type ResponseWriter interface {
	Header() Header
	Write([]byte) (int, error)
	WriteHeader(statusCode int)
}
	
func (w *response) Write(data []byte) (n int, err error) {
	return w.write(len(data), data, "")
}

*******************************************************************





用http包實現http伺服器,使用中層封裝:第二個版本
*********************************************************************
package main

import (
	"net/http"
	"io"
	"log"
	"os"
)

//對比第一版本:傳入的handler是nil,接下來我們要把nil換掉,換成我們自己實現的handler
//其實並不是一個handler,真實是ServerMux(路由型別),首先需要實現一個mux

func main(){
	mux:=http.NewServeMux() //例項化一個mux,返回一個mux
	//設定handler操作,不可以用HandlerFunc這個函式,這個函式用的是預設的路由器DefaultServeMux
	//這裡用的是預設的的handler進行註冊,要自己實現handler,註冊到mux中
	//
	mux.Handle("/",&myHandler{})
	mux.HandleFunc("/hello", sayHello)

//實現檔案伺服器-簡易靜態檔案實現
	wd,err:=os.Getwd()  //Getwd返回一個對應當前工作目錄的根路徑
	if err!=nil{
		log.Fatal(err)
	}
	mux.Handle("/static/",http.StripPrefix("/static/",
		http.FileServer(http.Dir(wd))))


	err =http.ListenAndServe(":9090",mux)  //使用自定義的路由器mux時,用http包的ListenAndServe函式,此時要傳入mux
	if err!=nil{
		log.Fatal(err)
	}
}

type myHandler struct {}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
	io.WriteString(w, "URL:"+r.URL.String())
}

func sayHello(w http.ResponseWriter, r * http.Request){
	io.WriteString(w, "hello world, this is version 2")
}
********************************************************************

從這裡就可以看出來,我們還需要一個mux來控制我們的訪問!這個函式用了兩個路由器,一個DefaultServeMux和一個mux。
為什麼這裡沒有ListenAndServe設定nil也可以訪問到預設路由DefaultServeMux設定的URL呢?
答:注意這一句mux.HandleFunc("/hello", sayHello),這裡是呼叫的HandleFunc這個方法,不是用的HandleFunc函式;所以這裡也是操作的mux這個自定義的路由器!





用http包實現http伺服器,使用底層封裝:第三個版本
*********************************************************************

package main

import (
	"net/http"
	"time"
	"io"
	"log"
)
//通過map儲存註冊的handler
//然後通過底層的serveHTTP進行轉發,這是效率最高的,因為沒有進任何封裝
//參考beego的那些路由 
var mux map[string]func(http.ResponseWriter, *http.Request)

func main(){
	server := http.Server{
		Addr: ":8080",
		Handler: &myHandler{},
		ReadHeaderTimeout: 5*time.Second,
	}

	mux = make(map[string]func(http.ResponseWriter, *http.Request))
	mux["/hello"]=sayHello
	mux["/bye"]=sayBye

	err:=server.ListenAndServe()  //使用自定義的map來實現路由時,使用ListenAndServe方法,上面用的是ListenAndServe函式。
	if err!=nil{
		log.Fatal(err)
	}
}


type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request){
	if h,ok := mux[r.URL.String()] ; ok {
		h(w,r) //根據URL從map中取出函式名,然後呼叫。
		return
	}

	io.WriteString(w, "Version 3 "+"URL: "+r.URL.String())
}

func sayHello(w http.ResponseWriter, r *http.Request){
	io.WriteString(w, "Version 3 "+"Hello ")
}

func sayBye(w http.ResponseWriter, r *http.Request){
	io.WriteString(w, "Version 3 "+"Bye")
}


********************************************************************************

這個程式只是用了ListenAndServe方法,把前面兩個版本的http.ListenAndServe函式中的封裝解開,直接設定server這個結構體。
而上面那兩個版本,是先設定一個路由器(預設的或者是自定義的),然後把路由器傳到ListenAndServe函式,ListenAndServe函式再通過傳入的路由器呼叫ListenAndServe方法,其實底層是一樣的。

相關推薦

Go語言net/http解析

首先來看一個用net/http包寫的web伺服器:兩個函式實現http伺服器package main import ( "fmt" "net/http" "strings" "log" ) func sayhelloName(w http.ResponseWrit

go語言net/http解析http body之坑

在Server端解析HTTP請求的時候, func ProcScaleOutReq(rsp http.ResponseWriter, req *http.Request) { req.ParseForm() //bodyStr := [1024]byte{

go語言netudp socket的使用

tcp clas 請求方式 return fmt 讀取數據 print 簡單 cep udp與tcp的不同在於客戶端請求方式不同,udp缺少Accept函數。 一個簡單的udp客戶端: package main; import ( "net" "log

Go語言-識別符號,,可見性

1 識別符號 識別符號即各種名字。 檔名小寫,可以通過下劃線 分隔 識別符號區分大小寫,UTF-8編碼,首字元可以用_,不可以用數字,不可以用go關鍵字,不可以用運算子 語句不要用分號結束,編譯器會自動加上 2 包 程式 <- 包

Go語言10-http和mysql

http 程式設計 Go 原生支援http: import "net/http" Go 的http服務效能和nginx比較接近:就是說用Go寫的Web程式上線,程式前面不需要再部署nginx的Web伺服器,這裡省掉的是Web伺服器。如果伺服器上部署了多個Web應用,還是需要反向代理的,一般這也是ngin

區塊鏈技術基礎語言(三十):Go語言常用工具(下)

原文連結:區塊鏈技術基礎語言(三十):Go語言常用工具包(下) 一、JSON處理 JSON(JavaScript Object Notation)是一種輕量級的資料交換格式,方便人們閱讀和編寫,也方便程式地解析和生成。雖然JSON是JavaScript的子集,但其格式完全獨立於程式語言,表現

區塊鏈技術語言(二十九)—Go語言常用工具(上)

原文連結:區塊鏈技術語言(二十九)—Go語言常用工具包(上) 常用工具包分為兩節內容。本節介紹格式化輸入輸出和對字串處理的常用工具包和函式;下節介紹JSON處理和對文字的幾種操作。   一、格式化輸入輸出 fmt包提供了格式化的輸入和輸出的操作。 1.1

Golang核心程式設計(8)-net/http的使用

文章目錄 一、net/http包 1.1、Get請求 1.2、Do方法 1.3、Post請求 1.4、PostForm方法 更多關

Go框架net/http整合Sentry

Raven Go提供了可與stdlib net / http庫一起使用的中介軟體,以自動處理在http請求期間發生的Pansic。 一、安裝 通過go get安裝raven-go。 $ go get github.com/getsentry/raven-go 二、配置

golang net/http部分實現原理詳解

net/http包在編寫golang web應用中有很重要的作用,它主要提供了基於HTTP協議進行工作的client實現和server實現,可用於編寫HTTP服務端和客戶端。 其使用方法也跟其他面嚮物件語言很相似,我們可以先從它的一些基礎用法來感受一下: 以下是

使用Go語言實現http服務端指定路徑的檔案.

package main import ( "io" "net/http" ) func main() { http.HandleFunc("/", router) http.Listen

【golang】Go語言學習-time

go語言的time包 組成 time.Duration(時長,耗時)time.Time(時間點)time.C(放時間點的管道)[ Time.C:=make(chan time.Time) ] time包裡有2個東西,一個是時間點,另一個是時長  時間點的意思就是“某一

Go語言基礎-sync

Golang 推薦通過 channel 進行通訊和同步,但在實際開發中 sync 包用得也非常的多,在以太坊的原始碼中也有很多這類應用的體現。 Go sync包提供了:sync.Mutex,sync.RMutex,sync.Once,sync.Cond,sync.Waitgroup,sync

golang net\http簡單的使用

HTTP服務端: package main import ( "fmt" "net/http" ) func HandConn(w http.ResponseWriter, r *http.Request) { //使用者請求方法 fmt.Println(r.Method) //使用者請

go語言基礎 數學math

介紹幾個math包下常用的方法 func main() { /* math包: */ i := -100 fmt.Println(math.Abs(float64(i))) //絕對值 fmt.Println(math.Ceil(5.1))

go語言,第三方相對路徑匯入引起的問題及解決方案(goquery)

對go語言而言,跟蹤init很顯然包有且僅有一次被匯入的可能。 但是重複引用了goquery包,後編譯出現問題  專案涉及相關目錄 ├── main.go└── parse    └── parse.go parse包和main.go都匯入了 goquery包 main

Go語言模擬http伺服器與客戶端資料互動

廢話少說:實現伺服器列印輸出客戶端的請求引數,客戶端列印伺服器返回的資料 伺服器: package main import ( "flag" "fmt" "net/http" ) func main() { host := flag.String("ho

go語言實現http

Server package main import ( "net/http" "os" "io" ) func main() { http.HandleFunc("/",handle) http.ListenAndServe("127.0.0.1:8005

Go語言使用http下載檔案

package main import ( "fmt" "io" "net/http" "os" ) var ( url = "http://127.0.0.1:1789/src/qq.

Gonet/http標準庫

很多框架認為自己提供的約定和模式是最佳實踐(best practice),如果開發者沒有正確理解這些最佳實踐,不瞭解約定和模式的用法,可能會盲目地使用它們。一個好的框架是快速構造可擴充套件且健壯的Web應用的最好方法,但是隱藏在下面的概念和基礎也是非常重要的。