go編寫web server的幾種方式
先說一下web server和http server的區別。http server,顧名思義,支援http協議的伺服器;web server除了支援http協議可能還支援其他網路協議。本文只討論使用golang的官方package編寫web server的幾種常用方式。
最簡單的http server
這也是最簡單的一種方式。
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了。
type Server struct {
Addr string //TCP address to listen on
Handler Handler //handler to invoke
ReadTimeout time.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了。示例程式碼如下。
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端程式碼簡單實現如下。
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轉發就可以了。
//代理伺服器
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稍微強化一點就可以實現#不可描述#的目的了。