1. 程式人生 > >go編寫web server的幾種方式

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稍微強化一點就可以實現#不可描述#的目的了。