Go語言標准庫 - net/http
在學習Go語言有一個很好的起點,Go語言官方文檔很詳細,今天我們學習的Go Web服務器的搭建就需要用到Go語言官方提供的標准庫 net/http
,通過http包提供了HTTP客戶端和服務端的實現。同時使用這個包能很簡單地對web的路由,靜態文件,模版,cookie等數據進行設置和操作。
下面是用http包建立web服務器的例子:
1、使用http.HandleFunc
package main import ( "fmt" "net/http" "strings" "log" ) func doRequest(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 Wrold!") //這個寫入到w的是輸出到客戶端的 } func main() { http.HandleFunc("/", doRequest) //設置訪問的路由 err := http.ListenAndServe(":9090", nil) //設置監聽的端口 if err != nil { log.Fatal("ListenAndServe: ", err) } }
上面的代碼我們在IDE中編譯后並運行成功后,這個時侯我們就可以在9090端口監聽http鏈接請求了。
我們看到了上面的代碼,要編寫一個Web服務器是不是很簡單,只要調用http包的兩個函數就可以了。
在main函數中,我們從net/http
包中調用了一個http.HandleFucn
函數來注冊一個處理函數,這個函數接受兩個參數。第一個是字符串,這個就是進行路由匹配,我這里是/路由。第二個參數是一個func (ResponseWriter, Request)
的簽名。
我們的doRequest函數就是這樣的簽名。下一行中的http.ListenAndServe(":8000", nil)
,表示監聽localhost的9090端口,暫時忽略掉nil。
在doRequest函數中我們有兩個參數,一個是http.ResponseWriter
類型的。它類似響應流,實際上是一個接口類型。
第二個是http.Request
類型,類似於HTTP 請求。我們不必使用所有的參數,如果只是簡單的輸出,那么我們只需要使用http.ResponseWriter,io.WriteString,將會把輸出流寫入數據。
我們再稍微改下,大家請注意修改的部分(這里我們只調整 main函數部分代碼)
2、使用ServeMux
func main() { mux := http.NewServeMux() mux.HandleFunc("/", doRequest) err := http.ListenAndServe(":9090", mux) //設置監聽的端口 if err != nil { log.Fatal("ListenAndServe: ", err) } }
這個例子中,我們不再在函數http.ListenAndServe
使用nil
了。這個例子跟上面的例子其實是一樣的。使用http注冊hanlder 函數模式就是用的ServeMux。
3、使用http.Server,而不是ServeMux。它們都是用net/http包運行了服務器。
package main import ( "fmt" "io" "log" "net/http" ) var mux map[string]func(http.ResponseWriter, *http.Request) func main() { server := http.Server{ Addr: ":8000", Handler: &doHandler{}, } mux = make(map[string]func(http.ResponseWriter, *http.Request)) mux["/test"] = doRequest err := server.ListenAndServe() if err != nil { log.Fatal("ListenAndServe: ", err) } } type doHandler struct{} func (*doHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if res, ok := mux[r.URL.String()]; ok { res(w, r) return } io.WriteString(w, "url params: "+r.URL.String()) } func doRequest(w http.ResponseWriter, r *http.Request) { r.ParseForm() //解析url傳遞的參數,對於POST則解析響應包的主體(request body) fmt.Fprintf(w, "service start...") //這個寫入到w的是輸出到客戶端的 也可以用下面的 io.WriteString對象 }
如果以前你是.NET程序員或java,那你也許就會問,我們的IIS服務器,tomcat服務器不需要嗎?Go就是不需要這些,因為他直接就監聽了TCP端口了。
二、Web工作方式的幾個概念
我們看到Go通過簡單的幾行代碼就已經運行起來一個Web服務了,而且這個Web服務內部有支持高並發的特性。現在Web服務已經搭建完成了,那我們現在來了解一個這個服務是怎么運行起來的呢?
以下幾個為服務器段的概念
- Request:用戶請求的信息,用來解析用戶的請求信息,包括post、get、cookie、url等信息
- Response:服務器需要反饋給客戶端的信息
- Conn:用戶的每次請求鏈接
- Handler:處理請求和生成返回信息的處理邏輯
分析http包運行機制
Go實現Web服務的工作模式流程圖
這個過程我們需要清楚以下三個問題,則就清楚Go是如何讓Web運行起來了
- 如何監聽端口?
通過上面的代碼我們看到Go是通過一個函數ListenAndServe
來處理這些事情的,這個底層其實這樣處理的:初始化一個server
對象,然后調用了net.Listen("tcp", addr)
,也就是底層用TCP協議搭建了一個服務,然后監控我們設置的端口。
Go http包的源碼,這里我們可以看到整個http處理過程
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() 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 if srv.ReadTimeout != 0 { rw.SetReadDeadline(time.Now().Add(srv.ReadTimeout)) } if srv.WriteTimeout != 0 { rw.SetWriteDeadline(time.Now().Add(srv.WriteTimeout)) } c, err := srv.newConn(rw) if err != nil { continue } go c.serve() } panic("not reached") }
- 如何接收客戶端請求?
上面代碼執行監控端口之后,調用了srv.Serve(net.Listener)
函數,這個函數就是處理接收客戶端的請求信 息。這個函數里面起了一個for{}
,首先通過Listener接收請求,其次創建一個 Conn,最后單獨開了一個 goroutine,把這個請求的數據當做參數扔給這個conn去服務:go c.serve()
。這 個就是高並發體現了, 用戶的每一次請求都是在一個新的goroutine去服務,相互不影響。 - 如何分配handler?
conn首先會解析request:c.readRequest()
,然后獲取相應的handler:handler := c.server.Handler
,也就是我們剛才在調用函數ListenAndServe
時候的第二個參數,我們前面例子傳遞的是nil,也就是為空,那么默認獲取handler = DefaultServeMux
,那么這個變量用來做什么的呢?對,這個變量就是一個路由器,它用來匹配url跳轉到其相應的handle函數,那么這個我們有設置過嗎?有,我們調用的代碼里面第一句不是調用了http.HandleFunc("/", sayhelloName)
嘛。這個作用就是注冊了請求/的路由規則,當請求uri為"/",路由就會轉到函數sayhelloName,DefaultServeMux會調用ServeHTTP方法,這個方法內部其實就是調用sayhelloName本身,最后通過寫入response的信息反饋到客戶端。
至此我們的三個問題已經全部得到了解答,你現在對於Go如何讓Web跑起來的是否已經基本了解。
轉:https://www.cnblogs.com/franklee97/p/7131551.html