
序言
由于本人一直從事Web服務(wù)器端的程序開(kāi)發(fā),所以在學(xué)習(xí)Golang也想從Web這里開(kāi)始學(xué)起,如果對(duì)Golang還不太清楚怎么搭建環(huán)境的朋友們可以參考我的上一篇文章 Golang的簡(jiǎn)單介紹及Windows環(huán)境下安裝、部署,這一篇我們來(lái)了解一下Golang的Web開(kāi)發(fā)入門:搭建一個(gè)簡(jiǎn)單的Go Web服務(wù)器。
注:此文借鑒了Astaxie《Go Web編程》一書中的內(nèi)容
正文
Go語(yǔ)言標(biāo)準(zhǔn)庫(kù) - net/http
在學(xué)習(xí)Go語(yǔ)言有一個(gè)很好的起點(diǎn),Go語(yǔ)言官方文檔很詳細(xì),今天我們學(xué)習(xí)的Go Web服務(wù)器的搭建就需要用到Go語(yǔ)言官方提供的標(biāo)準(zhǔn)庫(kù) net/http,通過(guò)http包提供了HTTP客戶端和服務(wù)端的實(shí)現(xiàn)。同時(shí)使用這個(gè)包能很簡(jiǎn)單地對(duì)web的路由,靜態(tài)文件,模版,cookie等數(shù)據(jù)進(jìn)行設(shè)置和操作。如果對(duì)http概念不是太清楚的朋友可以自行google。
http包建立Web服務(wù)器
package main
import (
"fmt"
"net/http"
"strings"
"log"
)
func sayhelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() //解析參數(shù),默認(rèn)是不會(huì)解析的
fmt.Println(r.Form) //這些信息是輸出到服務(wù)器端的打印信息
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!") //這個(gè)寫入到w的是輸出到客戶端的
}
func main() {
http.HandleFunc("/", sayhelloName) //設(shè)置訪問(wèn)的路由
err := http.ListenAndServe(":9090", nil) //設(shè)置監(jiān)聽(tīng)的端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
上面的代碼我們?cè)贗DE中編譯后并運(yùn)行成功后,這個(gè)時(shí)侯我們就可以在9090端口監(jiān)聽(tīng)http鏈接請(qǐng)求了。

上圖中,我們?cè)跒g覽器中輸入了 http://localhost:9090 ,可以看到瀏覽器頁(yè)面中輸入出 Hello World!
這個(gè)時(shí)侯如果我們?cè)跒g覽器地址后面加一些參數(shù)試試:http://localhost:9090?url_long=111&url_long=222,看看瀏覽器中輸出什么?服務(wù)器端輸出的又是什么?


我們看到了上面的代碼,要編寫一個(gè)Web服務(wù)器是不是很簡(jiǎn)單,只要調(diào)用http包的兩個(gè)函數(shù)就可以了。
如果以前你是.NET程序員,那你也許就會(huì)問(wèn),我們的IIS服務(wù)器不需要嗎?Go就是不需要這些,因?yàn)樗苯泳捅O(jiān)聽(tīng)了TCP端口了。
我們看到Go通過(guò)簡(jiǎn)單的幾行代碼就已經(jīng)運(yùn)行起來(lái)一個(gè)Web服務(wù)了,而且這個(gè)Web服務(wù)內(nèi)部有支持高并發(fā)的特性。現(xiàn)在Web服務(wù)已經(jīng)搭建完成了,那我們現(xiàn)在來(lái)了解一個(gè)這個(gè)服務(wù)是怎么運(yùn)行起來(lái)的呢?
Web工作方式的幾個(gè)概念
以下幾個(gè)為服務(wù)器段的概念
- Request:用戶請(qǐng)求的信息,用來(lái)解析用戶的請(qǐng)求信息,包括post、get、cookie、url等信息
- Response:服務(wù)器需要反饋給客戶端的信息
- Conn:用戶的每次請(qǐng)求鏈接
- Handler:處理請(qǐng)求和生成返回信息的處理邏輯
分析http包運(yùn)行機(jī)制

這個(gè)過(guò)程我們需要清楚以下三個(gè)問(wèn)題,則就清楚Go是如何讓W(xué)eb運(yùn)行起來(lái)了
- 如何監(jiān)聽(tīng)端口?
通過(guò)上面的代碼我們看到Go是通過(guò)一個(gè)函數(shù)ListenAndServe來(lái)處理這些事情的,這個(gè)底層其實(shí)這樣處
理的:初始化一個(gè)server對(duì)象,然后調(diào)用了net.Listen("tcp", addr),也就是底層用TCP協(xié)議搭建了一個(gè)服
務(wù),然后監(jiān)控我們?cè)O(shè)置的端口。
Go http包的源碼,這里我們可以看到整個(gè)http處理過(guò)程
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")
}
- 如何接收客戶端請(qǐng)求?
上面代碼執(zhí)行監(jiān)控端口之后,調(diào)用了srv.Serve(net.Listener)函數(shù),這個(gè)函數(shù)就是處理接收客戶端的請(qǐng)求信 息。這個(gè)函數(shù)里面起了一個(gè)for{},首先通過(guò)Listener接收請(qǐng)求,其次創(chuàng)建一個(gè) Conn,最后單獨(dú)開(kāi)了一個(gè) goroutine,把這個(gè)請(qǐng)求的數(shù)據(jù)當(dāng)做參數(shù)扔給這個(gè)conn去服務(wù):go c.serve()。這 個(gè)就是高并發(fā)體現(xiàn)了, 用戶的每一次請(qǐng)求都是在一個(gè)新的goroutine去服務(wù),相互不影響。 - 如何分配handler?
conn首先會(huì)解析request:c.readRequest(),然后獲取相應(yīng)的handler:handler := c.server.Handler,也就是我們剛才在調(diào)用函數(shù)ListenAndServe時(shí)候的第二個(gè)參數(shù),我們前面例子傳遞的是nil,也就是為空,那么默認(rèn)獲取handler = DefaultServeMux,那么這個(gè)變量用來(lái)做什么的呢?對(duì),這個(gè)變量就是一個(gè)路由器,它用來(lái)匹配url跳轉(zhuǎn)到其相應(yīng)的handle函數(shù),那么這個(gè)我們有設(shè)置過(guò)嗎?有,我們調(diào)用的代碼里面第一句不是調(diào)用了http.HandleFunc("/", sayhelloName)嘛。這個(gè)作用就是注冊(cè)了請(qǐng)求/的路由規(guī)則,當(dāng)請(qǐng)求uri為"/",路由就會(huì)轉(zhuǎn)到函數(shù)sayhelloName,DefaultServeMux會(huì)調(diào)用ServeHTTP方法,這個(gè)方法內(nèi)部其實(shí)就是調(diào)用sayhelloName本身,最后通過(guò)寫入response的信息反饋到客戶端。

至此我們的三個(gè)問(wèn)題已經(jīng)全部得到了解答,你現(xiàn)在對(duì)于Go如何讓W(xué)eb跑起來(lái)的是否已經(jīng)基本了解。