這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
先說一下web server和http server的區別。http server,顧名思義,支援http協議的伺服器;web server除了支援http協議可能還支援其他網路通訊協定。本文只討論使用golang的官方package編寫web server的幾種常用方式。
最簡單的http server
這也是最簡單的一種方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
package main
import ( "net/http" "log" )
func myHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello there!\n") }
func main(){ http.HandleFunc("/", myHandler)//設定訪問路由 log.Fatal(http.ListenAndServe(":8080", nil)) }
|
啟動程式,另開一個終端輸入命令curl localhost:8080,或者直接瀏覽器開啟localhost:8080,就可以看到Hello there!
ListenAndServe函數負責監聽並處理串連。內部處理方式是對於每個connection起一個goroutine來處理。其實這並不是一種好的處理方式。學習過作業系統的同學都知道,進程或者線程切換的代價是巨大的。雖然goroutine是使用者級的輕量級線程,切換並不會導致使用者態和核心態的切換,但是當goroutine數量巨大的時候切換的代價還是不容小覷的。更好的一種方式是使用goroutine pool,這裡暫且按住不表。
使用Handler介面
上面這種方式感覺可發揮餘地太小了,比如我想設定server的Timeout時間都不能設定了。這時候我們就可以使用自訂server了。
1 2 3 4 5 6 7 8 9 10 11 12
|
type Server struct { Addrstring//TCP address to listen on HandlerHandler//handler to invoke ReadTimeouttime.Duration//maximum duration before timing out read of the request WriteTimeout time.Duration//maximum duration before timing out write of the response TLSConfig*tls.Config ... } //Handler是一個interface,定義如下 type Handler interface { ServeHTTP(ResponseWrite, *Request) }
|
所以只要我們實現了Handler介面的方法ServeHTTP就可以自訂我們的server了。範例程式碼如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
type myHandler struct{ }
func (this myHandler) ServeHTTP(w ResponseWrite, r *Request) { ... }
func main() { server := http.Server{ Addr:":8080", Handler:&myHandler{}, ReadTimeout:3*time.Second, ... } log.Fatal(server.ListenAndServe) }
|
直接處理conn
有時候我們需要更底層一點直接處理connection,這時候可以使用net包。server端代碼簡單實現如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { //handle error } go handleConn(conn) } }
|
為了樣本方便,我這裡對於每一個connection都起了一個goroutine去處理。實際使用中,goroutine pool往往是一個更好的選擇。對於client的返回資訊我們再歇回到conn中就可以。
proxy
上面說了幾種web server的實現方式,下面簡單實現一個http proxy,用golang寫proxy很簡單只需要把conn轉寄就可以了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
//Proxy 伺服器 func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { log.Fatal(err) } for { conn, err := listener.Accept() if err != nil { //handle error } go handleConn(conn) } }
func handleConn(from net.Conn) { to, err := net.Dial("tcp", ":8001")//建立和目標伺服器的串連 if err != nil { //handle error } done := make(chan struct{}) go func() { defer from.Close() defer to.Close() io.Copy(from, to) done<-strcut{}{} }() go func() { defer from.Close() defer to.Close() io.Copy(to, from) done<-struct{}{} } <-done <-done }
|
proxy稍微強化一點就可以實現#不可描述#的目的了。