Go語言入門——實踐篇(五)

Web開發基礎

所謂Web開發,也就是我們通常說的網站後端開發。與其他語言相比,Go的Web開發具有簡單易學,併發效率高,原生標準庫支持等特點。即使是Python Web開發,也沒有Go的簡單。

學習Go的Web,是可以不需要安裝任何第三方庫的,標準庫即支持,且底層已經使用Go協程封裝了併發請求,因此Go不需要任何所謂的服務器容器的軟件,例如Java開發需要Tomcat服務器,Python需要Gunicorn,uWSGI之類的服務器,而Go語言,直接上手擼API即可,可以說Go語言是爲Web而生的,最適合後端開發。

學習Web開發,應當具備HTTP協議的基礎,請先閱讀我的另一篇文章 Web基礎(一) HTTP詳解

最簡示例

  1. 運行以下代碼
package main

import (
    "fmt"
    "net/http"
)

//定義一個名爲 handler 的處理器函數
func handler(writer http.ResponseWriter, request *http.Request) {
    fmt.Fprintf(writer, "Hello, %s!", request.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}
  1. 在瀏覽器輸入http://localhost:8080/Mary
    這時瀏覽器顯示Hello, Mary!

handler函數:事件被觸發之後,負責對事件進行處理的回調函數。該處理器函數接受兩個參數,第一個參數類型爲ResponseWriter接口 , 第二個參數爲指向Request結構的指針。

handler函數會從 Request 結構中提取相關的信息,然後創建一個HTTP響應, 最後再通過ResponseWriter接口將響應返回給客戶端。

handler函數中的Fprintf函數在被調用時,需要傳入一個ResponseWriter接口實例 ,第二個參數是帶有格式化佔位符%s的字符串,第三參數就是佔位符需要替換的內容,這裏則是將Request結構裏帶的URL路徑截取後作爲參數

Go Web工作流程

在這裏插入圖片描述

示例代碼

package main

import (
    "net/http"
	"time"
	"fmt"
)

func main() {
	fmt.Println("*** 服務器啓動,監聽端口:8080 ***")
    mux := http.NewServeMux()
    // 處理靜態資源文件
    files := http.FileServer(http.Dir("./public"))
    mux.Handle("/static/", http.StripPrefix("/static/", files))
    mux.HandleFunc("/", index)

    // 配置服務器
    server := &http.Server{
        Addr:           "0.0.0.0:8080",
        Handler:        mux,     // 設置多路複用器
        ReadTimeout:    time.Duration(10 * int64(time.Second)),
        WriteTimeout:   time.Duration(200 * int64(time.Second)),
        MaxHeaderBytes: 1 << 20, // 左移運算,等同:1*2^20,高性能乘法運算
    }
    server.ListenAndServe()
}

func index(writer http.ResponseWriter, request *http.Request) {
    fmt.Fprintln(writer, "Hello, world!")
}
  1. 創建以上代碼
  2. 在代碼所在目錄創建public文件夾
  3. public中分別創建home.htmlnote.txt文件
  4. 使用編輯器打開創建的兩個文件,分別將以下內容複製粘貼到文件中保存
<html>
    <head>
        <title>
        這是主頁
        </title>
    </head>
<body style="background:Pink">
   <h1>Home</h1>
   <p style="color:red">
      Welcome to the Go homepage!
   </p>
</body>
</html>

現在,讓我們在瀏覽器分別訪問http://127.0.0.1:8080/http://127.0.0.1:8080/static/home.htmlhttp://127.0.0.1:8080/static/note.txt
在這裏插入圖片描述
在這裏插入圖片描述
可以發現,http://127.0.0.1:8080/static/home.htmlhttp://127.0.0.1:8080/static/note.txt的結果是不一樣的,這是因爲瀏覽器能識別html標記語言,關於前端html標記語言本文不做說明,請自行學習前端相關知識。

創建多路複用器

通過NewServeMux函數來創建一個默認的多路複用器,調用HandleFunc函數將發送至根URL的請求重定向到對應的處理器。因爲所有處理器都接受一個 ResponseWriter 實例和一個指向 Request 結構的指針作爲參數,並且所有請求參數都可以通過訪問 Request 結構得到,所以程序並不需要向處理器顯式地傳入任何請求參數。

多路複用器主要負責接收 HTTP 請求,並根據請求中的 URL 將請求重定向到正確的處理器。 注意,所有引入了 net/http 標準庫的程序都可以使用一個默認的多路複用器實例,當沒有爲 Server 結構指定處理器時,服務器就會使用 DefaultServeMux

實際上,所謂多路複用器,也就是我們在開發中常說的路由的概念,根據不同的URL,調用不同的函數去處理。

處理靜態文件

使用FileServer函數創建了一個處理器,它能夠處理指定目錄中的靜態文件。最後將這個處理器傳遞給多路複用器的Handle函數

如示例代碼,當服務器接收到一個以/static/開頭的 URL 請求時,以上將URL中的/static/路徑映射到public目錄中,然後查找被請求的文件。

例如,當服務器接收到一個針對文件 http://127.0.0.1:8080/static/note.txt的請求時,它將會在public目錄中查找note.txt文件。這樣做的好處是可以將服務器上的真實文件目錄隱藏。

創建處理器函數

處理器函數實際上就是一個接受ResponseWriterRequest指針作爲參數的 Go 函數。通常該函數負責生成HTML內容並將其寫人ResponseWriter中。

func index(writer http.ResponseWriter, request *http.Request) {
    fmt.Fprintln(writer, "Hello, world!")
}

Go Web 應用基礎

簡單配置

除了可以通過ListenAndServe的參數對服務器的網絡地址和處理器進行配置之外,還可以通過 Server 結構對服務器進行更詳細的配置,如上面的例子,其中包括爲請求讀取操作設置超時時間、爲響應寫入操作設置超時時間,以及爲 Server 結構設置錯誤日誌記錄器等

最簡配置

package  main

import  (
    "net/http"
)

func  main()  {
    server := http.Server{
        Addr : "127.0.0.1:8080" ,
        Handler :  nil,
    }
    server.ListenAndServe()
}

Server結構詳細字段

type Server struct {
    Addr      string   // TCP address to listen on, ":http" if empty
    Handler   Handler  // handler to invoke, http.DefaultServeMux if nil
    TLSConfig *tls.Config // optional TLS config, used by ServeTLS and ListenAndServeTLS

    ReadHeaderTimeout time.Duration
    WriteTimeout time.Duration
    IdleTimeout time.Duration
    MaxHeaderBytes int
    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
    ConnState func(net.Conn, ConnState)
    ErrorLog *log.Logger

    disableKeepAlives int32     // accessed atomically.
    inShutdown        int32     // accessed atomically (non-zero means we're in Shutdown)
    nextProtoOnce     sync.Once // guards setupHTTP2_* init
    nextProtoErr      error     // result of http2.ConfigureServer if used

    mu         sync.Mutex
    listeners  map[net.Listener]struct{}
    activeConn map[*conn]struct{}
    doneChan   chan struct{}
    onShutdown []func()
}

處理器與處理器函數

處理器 是一個擁有ServeHTTP方法的接口,這個ServeHTTP方法需要接受兩個參數,第一個參數是一個ResponseWriter接口實例,而第二個參數則是一個指向Request結構的指針。即任何擁有ServeHTTP方法的接口,且該方法的簽名爲ServeHTTP(http.ResponseWriter, *http.Request),那麼這個接口就是一個處理器。

處理器函數 實際上是與處理器擁有相同行爲的函數,這個函數與ServeHTTP方法擁有相同的簽名,即接受ResponseWriter和指向Request結構的指針作爲參數。通過使用HandlerFunc可以把一個帶有正確簽名的函數f轉換成一個帶有方法 f的處理器實例,這個方法會與DefaultServeMux進行綁定。因此,處理器函數只不過是創建處理器的一種便利方法而已。

雖然處理器函數能夠完成跟處理器一樣的工作,並且使用處理器函數的代碼比使用處理器的代碼更爲簡潔,但是處理器函數並不能完全代替處理器。 這是因爲在某些情況下,代碼可能已經包含了某個接口或者某種類型,這時我們只需要爲它們添加 ServeHTTP方法就可以將它們轉變爲處理器,這種轉變更靈活也有助於構建出更爲模塊化的 Web 應用。

創建處理器

package main

import (
    "fmt"
    "net/http"
)

// 聲明一個結構體
type MyHandler struct{
}

// 讓結構體實現 ServeHTTP 函數
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World!")
}

func main() {
    handler := MyHandler{}
    
    server := http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: &handler,  //指定處理器
    }
    server.ListenAndServe()
}

創建多個處理器

不用在Server結構的Handler字段中指定處理器,而是讓服務器使用默認的 DefaultServeMux作爲處理器, 然後通過http.Handle函數將處理器綁定至 DefaultServeMux

需要說明的是,雖然Handle函數來源於http包,但它實際上是ServeMux結構的方法,這些函數是爲了操作便利而創建的函數,調用它們等同於調用DefaultServeMux的方法。 比如調用http.Handle實際上就是在調用DefaultServeMuxHandle方法

package main

import (
    "fmt"
    "net/http"
)

type HelloHandler struct{}

func (h *HelloHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello!")
}

type WorldHandler struct{}

func (h *WorldHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "World!")
}

func main() {
    hello := HelloHandler{}
    world := WorldHandler{}

    server := http.Server{
        Addr: "127.0.0.1:8080",
    }

    http.Handle("/hello", &hello)
    http.Handle("/world", &world)

    server.ListenAndServe()
}

多路複用器

ServeMux是一個HTTP請求多路複用器,它負責接收HTTP請求並根據請求中的 URL將請求重定向到正確的處理器。ServeMux結構包含了一個映射,這個映射會將URL映射至相應的處理器。因爲ServeMux結構也實現了ServeHTTP方法,所以它也是一個處理器

在這裏插入圖片描述

ServeMux是一個結構體而不是一個接口,因此DefaultServeMuxServeMux的一個實例而不ServeMux的實現。

前面提到的http.ListenAndServe(":8080", nil)函數接受的第二個參數是一個處理器,但它的默認值卻是一個多路複用器DefaultServeMux,這是因爲DefaultServeMux多路複用器是ServeMux結構的一個實例,而ServeMux也擁有ServeHTTP方法,也就是說DefaultServeMux既是ServeMux結構的實例,也是Handler結構的實例。

第三方多路複用器

ServeMux的一個缺陷是無法使用變量實現URL模式匹配。例如在瀏覽器請求/threads的時候,ServeMux可以很好地獲取並顯示所有帖子,但當瀏覽器請求的是/threads/123時,那麼要獲取並顯示ID爲123的帖子就會變得非常困難。

創建自定義的多路複用器來代替net/http包中的ServeMux是可行的,並且目前市面上已經出現了很多第三方的多路複用器可供使用,而HttpRouter就是一個功能強大的輕量級第三方多路複用器。

執行以下命令安裝 HttpRouter

go get github.corn/julienschrnidt/httprouter

這表示從GitHub上下載HttpRouter包源碼,並將其保存到GOPATH/src目錄中

使用示例

package main

import (
    "fmt"
    "github.com/julienschmidt/httprouter"
    "net/http"
)

func hello(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
    fmt.Fprintf(w, "hello, %s!\n", p.ByName("name"))
}

func main() {
    //創建多路複用器
    mux := httprouter.New()
    //將處理器函數與給定的HTTP方法進行綁定
    mux.GET("/hello/:name", hello)

    server := http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: mux,
    }
    server.ListenAndServe()
}

以上代碼不再使用HandleFunc綁定處理器函數,而是直接把處理器函數與給定的 HTTP方法進行綁定。當瀏覽器向這個URL發送GET請求時,hello函數就會被調用,但當瀏覽器向這個URL發送其他請求時,hello函數不會被調用。

可以看到被綁定的URL包含了具名參數(named parameter),這些具名參數會被 URL中的具體值所代替,並且程序可以在處理器裏面獲取這些值。此時的處理器函數hello接收三個參數,而第三個參數Params就包含了具名參數,其值可以
通過ByName方法獲取。

如,運行程序後,瀏覽器輸入localhost:8080/hello/fox,則顯示 hello,foxp.ByName成功獲取到URL中的fox字段。

歡迎關注我的公衆號:編程之路從0到1

編程之路從0到1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章