接下來(lái)就是重點(diǎn)了,我們的HTTP服務(wù)器,這個(gè)大家都不陌生,HTTP是最常用的方式之一,通用性很強(qiáng),跨團(tuán)隊(duì)協(xié)作上也比較受到推薦,排查問(wèn)題也相對(duì)來(lái)說(shuō)簡(jiǎn)單。
我們接下來(lái)以3種方式來(lái)展現(xiàn)Golang的HTTP服務(wù)器的簡(jiǎn)潔和強(qiáng)大。
- 寫一個(gè)簡(jiǎn)單的HTTP服務(wù)器
- 寫一個(gè)稍微復(fù)雜帶路由的HTTP服務(wù)器
- 分析源碼,然后實(shí)現(xiàn)一個(gè)自定義Handler的服務(wù)器
然后我們對(duì)照net/http包來(lái)進(jìn)行源碼分析,加強(qiáng)對(duì)http包的理解。
1、寫一個(gè)簡(jiǎn)單的HTTP服務(wù)器:
package main;
import (
"net/http"
)
funchello(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Hello"))
}
funcsay(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Hello"))
}
funcmain() {
http.HandleFunc("/hello", hello);
http.Handle("/handle",http.HandlerFunc(say));
http.ListenAndServe(":8001", nil);
select{};
}


是不是很簡(jiǎn)單,我用2種方式演示了這個(gè)例子,HandleFunc和Handle方式不同,卻都能實(shí)現(xiàn)一個(gè)路由的監(jiān)聽,其實(shí)很簡(jiǎn)單,但是很多人看到這都會(huì)有疑惑,別著急,咱們?cè)创a分析的時(shí)候你會(huì)看到。
2、寫一個(gè)稍微復(fù)雜帶路由的HTTP服務(wù)器:
對(duì)著上面的例子想一個(gè)問(wèn)題,我們?cè)陂_發(fā)中會(huì)遇到很多問(wèn)題,比如handle/res,handle/rsa…等等路由,這兩個(gè)路由接受的參數(shù)都不一樣,我們應(yīng)該怎么寫。我先來(lái)個(gè)圖展示下運(yùn)行結(jié)果。

是不是挺驚訝的,404了,路由沒(méi)有匹配到。可是我們寫handle這個(gè)路由了。
問(wèn)題:
- 什么原因?qū)е碌穆酚墒?/span>
- 如何解決這種問(wèn)題,做一個(gè)可以用Controller來(lái)控制的路由
問(wèn)題1:
我們?cè)谠创a閱讀分析的時(shí)候會(huì)解決。
問(wèn)題2:
我們可以設(shè)定一個(gè)控制器Handle,它有2個(gè)action,我們的執(zhí)行handle/res對(duì)應(yīng)的結(jié)果是調(diào)用Handle的控制器下的res方法。這樣是不是很酷。
來(lái)我們先上代碼:
靜態(tài)目錄:
- css
- js
- image
靜態(tài)目錄很好實(shí)現(xiàn),只要一個(gè)函數(shù)http.FileServer(),這個(gè)函數(shù)從文字上看就是文件服務(wù)器,他需要傳遞一個(gè)目錄,我們常以http.Dir("Path")來(lái)傳遞。
其他目錄大家自己實(shí)現(xiàn)下,我們來(lái)實(shí)現(xiàn)問(wèn)題2,一個(gè)簡(jiǎn)單的路由。
我們來(lái)看下代碼
package main;
import (
"net/http"
"strings"
"reflect"
"fmt"
)
funchello(w http.ResponseWriter, req *http.Request) {
w.Write([]byte("Hello"));
}
type Handlers struct{
}
func(h *Handlers) ResAction(w http.ResponseWriter, req *http.Request) {
fmt.Println("res");
w.Write([]byte("res"));
}
funcsay(w http.ResponseWriter, req *http.Request) {
pathInfo := strings.Trim(req.URL.Path, "/");
parts := strings.Split(pathInfo, "/");
varaction = "";
fmt.Println(strings.Join(parts,"|"));
if len(parts) >1 {
action = strings.Title(parts[1]) + "Action";
}
fmt.Println(action);
handle := &Handlers{};
controller := reflect.ValueOf(handle);
method := controller.MethodByName(action);
r := reflect.ValueOf(req);
wr := reflect.ValueOf(w);
method.Call([]reflect.Value{wr, r});
}
funcmain() {
http.HandleFunc("/hello", hello);
http.Handle("/handle/",http.HandlerFunc(say));
http.ListenAndServe(":8081", nil);
select{};
}
上面代碼就可以實(shí)現(xiàn)handle/res,handle/rsa等路由監(jiān)聽,把前綴相同的路由業(yè)務(wù)實(shí)現(xiàn)放在一個(gè)文件里,這樣也可以解耦合,是不是清爽多了。其實(shí)我們可以在做的更加靈活些。在文章最后我們放出來(lái)一個(gè)流程圖,按照流程圖做你們就能寫出一個(gè)簡(jiǎn)單的mvc路由框架。接下來(lái)看運(yùn)行之后的結(jié)果。
如下圖:
(點(diǎn)擊放大圖像)

3、分析源碼,然后實(shí)現(xiàn)一個(gè)自定義Handler的服務(wù)器
現(xiàn)在我們利用這個(gè)例子來(lái)分析下http包的源碼(只是服務(wù)器相關(guān)的,Request我們此期不講,簡(jiǎn)單看看就行。)
其實(shí)使用Golang做web服務(wù)器的方式有很多,TCP也是一種,net包就可以實(shí)現(xiàn),不過(guò)此期我們不講,因?yàn)镠TTP服務(wù)器如果不懂,TCP會(huì)讓你更加不明白。
我們從入口開始,首先看main方法里的http.HandleFunc和http.Handle這個(gè)綁定路由的方法,上面一直沒(méi)解釋有啥區(qū)別。現(xiàn)在我們來(lái)看一下。
funcHandleFunc(pattern string, handler func(ResponseWriter, *Request))
funcHandle(pattern string, handler Handler)
Handle 和HandleFunc都是注冊(cè)路由,從上面也能看出來(lái)這兩個(gè)函數(shù)都是綁定注冊(cè)路由函數(shù)的。如何綁定的呢?我們來(lái)看下。
上面2個(gè)函數(shù)通過(guò)DefaultServeMux.handle,DefaultServeMux.handleFunc把pattern和HandleFunc綁定到ServeMux的Handle上。
為什么DefaultServeMux會(huì)把路由綁定到ServeMux上呢?
varDefaultServeMux = NewServeMux()
因?yàn)镈efaultServeMux就是ServeMux的實(shí)例對(duì)象。導(dǎo)致我們就把路由和執(zhí)行方法綁注冊(cè)好了。不過(guò)大家請(qǐng)想下handle/res的問(wèn)題?
從上面的分析我們要知道幾個(gè)重要的概念。
HandlerFunc
type HandlerFuncfunc(ResponseWriter, *Request)
func(f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
上面的大概意思是,定義了一個(gè)函數(shù)適配器(可以理解成函數(shù)指針)HandleFunc,通過(guò)HandlerFunc(f)來(lái)進(jìn)行適配。其實(shí)調(diào)用的實(shí)體是f本身。
package main
import "fmt"
type A func(int, int)
func(f A)Serve() {
fmt.Println("serve2")
}
funcserve(int,int) {
fmt.Println("serve1")
}
funcmain() {
a := A(serve)
a(1,2)
a.Serve()
}
上面結(jié)果是serve1,serve2
Golang的源碼里用了很多HandleFunc這個(gè)適配器。
接下來(lái)我們看第二個(gè),ServeMux結(jié)構(gòu),最終我們是綁定它,也是通過(guò)它來(lái)解析。
type ServeMuxstruct{
mu sync.RWMutex
m map[string]muxEntry
hosts bool
}
type muxEntrystruct{
explicit bool
h Handler
pattern string
}
看到explicit的時(shí)候是不是就明白為啥handle/res不能用handle來(lái)監(jiān)聽了?原來(lái)如此。大致綁定流程大家看明白了嗎?如果不理解可以回去再看一遍。
接下來(lái)我們來(lái)看實(shí)現(xiàn)“啟動(dòng)/監(jiān)聽/觸發(fā)”服務(wù)器的代碼。
http.ListenAndServe(":8081", nil);
上面這句就是,”:8081”是監(jiān)聽的端口,也是socket監(jiān)聽的端口,第二個(gè)參數(shù)就是我們的Handler,這里我們寫nil。
funcListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
從這個(gè)代碼看出來(lái),Server這個(gè)結(jié)構(gòu)很重要。我們來(lái)看看他是什么。
type Server struct {
Addr string
Handler Handler
ReadTimeouttime.Duration
WriteTimeouttime.Duration
MaxHeaderBytesint
TLSConfig *tls.Config
...
}
Server提供的方法有:
func(srv *Server) Serve(l net.Listener) error
func(srv *Server) ListenAndServe() error
func(srv *Server) ListenAndServeTLS(certFile, keyFile string) error
Server的ListenAndServe方法通過(guò)TCP的方式監(jiān)聽端口,然后調(diào)用Serve里的實(shí)現(xiàn)等待client來(lái)accept,然后開啟一個(gè)協(xié)程來(lái)處理邏輯(go c.serve)。
它的格式
func(srv *Server) ListenAndServe() error
看到這里我們要了解幾個(gè)重要的概念。
ResponseWriter:生成Response的接口
Handler:處理請(qǐng)求和生成返回的接口
ServeMux:路由,后面會(huì)說(shuō)到ServeMux也是一種Handler
Conn : 網(wǎng)絡(luò)連接
這幾個(gè)概念看完之后我們下面要用。
type conn struct
這個(gè)結(jié)構(gòu)是一個(gè)網(wǎng)絡(luò)間接。我們暫時(shí)忽略。
這個(gè)c.serve里稍微有點(diǎn)復(fù)雜,它有關(guān)閉這次請(qǐng)求,讀取數(shù)據(jù)的,刷新緩沖區(qū)的等實(shí)現(xiàn)。這里我們主要關(guān)注一個(gè)c.readRequest(),通過(guò)redRequest可以得到Response,就是輸出給客戶端數(shù)據(jù)的一個(gè)回復(fù)者。
它里面包含request。如果要看懂這里的實(shí)現(xiàn)就要搞懂三個(gè)接口。
ResponseWriter, Flusher, Hijacker
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
}
type Flusher interface {
Flush()
}
type Hijacker interface {
Hijack() (net.Conn, *bufio.ReadWriter, error)
}
而我們這里的w也就是ResponseWriter了。而調(diào)用了下面這句方法,就可以利用它的Write方法輸出內(nèi)容給客戶端了。
serverHandler{c.server}.ServeHTTP(w, w.req)
這句就是觸發(fā)路由綁定的方法了。要看這個(gè)觸發(fā)器我們還要知道幾個(gè)接口。
具體我們先看下如何實(shí)現(xiàn)這三個(gè)接口的,因?yàn)楹竺嫖覀円从|發(fā)路由執(zhí)行邏輯片段。實(shí)現(xiàn)這三個(gè)接口的結(jié)構(gòu)是response
response
type response struct {
conn *conn
req *Request
chunking bool
wroteHeaderbool
wroteContinuebool
header Header
written int64
contentLength int64
status int
needSniffbool
closeAfterReplybool
requestBodyLimitHitbool
}
在response中是可以看到
func(w *response) Header() Header
func(w *response) WriteHeader(code int)
func(w *response) Write(data []byte) (n int, err error)
func(w *response) WriteString(data string) (n int, err error)
func(w *response) write(lenDataint, dataB []byte, dataS string) (n int, err error)
func(w *response) finishRequest()
func(w *response) Flush()
func(w *response) Hijack() (rwcnet.Conn, buf *bufio.ReadWriter, err error)
我簡(jiǎn)單羅列一些,從上面可以看出,response實(shí)現(xiàn)了這3個(gè)接口。
接下來(lái)我們請(qǐng)求真正的觸發(fā)者也就是serverHandle要觸發(fā)路由(hijacked finishRequest暫且不提)。先看一個(gè)接口。
Handler
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
實(shí)現(xiàn)了handler接口,就意味著往server端添加了處理請(qǐng)求的邏輯函數(shù)。
serverHandle調(diào)用ServeHttp來(lái)選擇觸發(fā)的HandleFunc。這里面會(huì)做一個(gè)判斷,如果你傳遞了Handler,就調(diào)用你自己的,如果沒(méi)傳遞就用DefaultServeMux默認(rèn)的。到這整體流程就結(jié)束了。
過(guò)程是:
DefaultServeMux.ServeHttp執(zhí)行的簡(jiǎn)單流程.
- h, _ := mux.Handler(r)
- h.ServeHTTP(w, r) //執(zhí)行ServeHttp函數(shù)
查找路由,mux.handler函數(shù)里又調(diào)用了另外一個(gè)函數(shù)mux.handler(r.Host, r.URL.Path)。
還記得我們的ServeMux里的hosts標(biāo)記嗎?這個(gè)函數(shù)里會(huì)進(jìn)行判斷。
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
上面就是匹配查找pattern和handler的流程了
我們來(lái)總結(jié)一下。
首先調(diào)用Http.HandleFunc
按順序做了幾件事:
- 調(diào)用了DefaultServerMux的HandleFunc
- 調(diào)用了DefaultServerMux的Handle
- 往DefaultServeMux的map[string]muxEntry中增加對(duì)應(yīng)的handler和路由規(guī)則
別忘記DefaultServerMux是ServeMux的實(shí)例。其實(shí)都是圍繞ServeMux,muxEntry2個(gè)結(jié)構(gòu)進(jìn)行操作綁定。
其次調(diào)用http.ListenAndServe(":12345", nil)
按順序做了幾件事情:
- 實(shí)例化Server
- 調(diào)用Server的ListenAndServe()
- 調(diào)用net.Listen("tcp", addr)監(jiān)聽端口,啟動(dòng)for循環(huán),等待accept請(qǐng)求
- 對(duì)每個(gè)請(qǐng)求實(shí)例化一個(gè)Conn,并且開啟一個(gè)goroutine處理請(qǐng)求。
- 如:go c.serve()
- 讀取請(qǐng)求的內(nèi)容w, err := c.readRequest(),也就是response的取值過(guò)程。
- 調(diào)用serverHandler的ServeHTTP,ServeHTTP里會(huì)判斷Server的屬性里的header是否為空,如果沒(méi)有設(shè)置handler,handler就設(shè)置為DefaultServeMux,反之用自己的(我們后面會(huì)做一個(gè)利用自己的Handler寫服務(wù)器)
- 調(diào)用DefaultServeMux的ServeHttp( 因?yàn)槲覀儧](méi)有自己的Handler,所以走默認(rèn)的)
- 通過(guò)request選擇匹配的handler:
A request匹配handler的方式。Hosts+pattern或pattern或notFound
B 如果有路由滿足,返回這個(gè)handler
C 如果沒(méi)有路由滿足,返回NotFoundHandler
- 根據(jù)返回的handler進(jìn)入到這個(gè)handler的ServeHTTP
大概流程就是這個(gè)樣子,其實(shí)在net.Listen("tcp", addr)里也做了很多事,我們下期說(shuō)道TCP服務(wù)器的時(shí)候回顧一下他做了哪些。
通過(guò)上面的解釋大致明白了我們綁定觸發(fā)的都是DefaultServeMux的Handler。現(xiàn)在我們來(lái)實(shí)現(xiàn)一個(gè)自己的Handler,這也是做框架的第一步。我們先來(lái)敲代碼。
package main;
import (
"fmt"
"net/http"
"time"
)
type customHandlerstruct{
}
func(cb *customHandler) ServeHTTP( w http.ResponseWriter, r *http.Request ) {
fmt.Println("customHandler!!");
w.Write([]byte("customHandler!!"));
}
funcmain() {
varserver *http.Server = &http.Server{
Addr: ":8080",
Handler: &customHandler{},
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
MaxHeaderBytes: 1 <<20,
}
server.ListenAndServe();
select {
}
}

是不是很酷,我們可以利用自己的handler做一個(gè)智能的路由出來(lái)。
不過(guò)還是建議使用國(guó)內(nèi)Golang語(yǔ)言框架beego,已開源。一款非常不錯(cuò)的框架,謝大維護(hù)的很用心,絕對(duì)良心框架,而且文檔支持,社區(qū)也很不錯(cuò)。
最后附上一張最早設(shè)計(jì)框架時(shí)候的一個(gè)流程圖(3年前)。大家可以簡(jiǎn)單看看,當(dāng)然也可以嘗試的動(dòng)動(dòng)手。起碼收獲很多。
(點(diǎn)擊放大圖像)

[1]: http://item.jd.com/11573034.html
[2]: https://github.com/astaxie/beego