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