Golang 中的 Web 伺服器開發 - 從簡單到複雜

Golang 中的 Web 伺服器開發 - 從簡單到複雜

五年前我開始 開發 Gophish,這就提供了一個學習Golang的機會。 我意識到 Go 是一門強大的語言,有許多函式庫作為補充。 Go 具有多種用途:特別是,它可以毫無問題地用於開發伺服器端應用程式。

本文是關於用 Go 編寫伺服器的。 讓我們從「Hello world!」之類的簡單內容開始,以具有以下功能的應用程式結束:

- 使用 Let's Encrypt 進行 HTTPS。
— 以 API 路由器工作。
— 使用中介軟體。
— 靜態文件的處理。
— 正確關閉。

技能箱推薦: 實踐課程 《從零開始的Python開發》.

提醒: 對於“Habr”的所有讀者 - 使用“Habr”促銷代碼註冊任何 Skillbox 課程可享受 10 盧布的折扣。

您好,世界!

您可以非常快速地在 Go 中建立一個 Web 伺服器。 以下是一個使用處理程序傳回上面承諾的「Hello, world!」的範例。

package main
 
import (
"fmt"
"net/http"
)
 
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World!")
})
http.ListenAndServe(":80", nil)
}

之後,如果您運行應用程式並打開頁面 本地,然後你會立即看到文字“Hello, world!” (當然,如果一切正常的話)。

稍後我們將多次使用該處理程序,但首先讓我們了解一切是如何運作的。

網絡/http

此範例使用了包 net/http,它是 Go 中用於開發伺服器和 HTTP 用戶端的主要工具。 為了理解程式碼,我們先來理解三個重要元素的意義:http.Handler、http.ServeMux 和 http.Server。

HTTP 處理程式

當我們收到請求時,處理程序會分析它並產生回應。 Go 中的處理程序實作如下:

type Handler interface {
        ServeHTTP(ResponseWriter, *Request)
}

第一個範例使用 http.HandleFunc 輔助函數。 它包裝了另一個函數,該函數又將 http.ResponseWriter 和 http.Request 放入 ServeHTTP 中。

換句話說,Golang 中的處理程序呈現在單一介面中,這為程式設計師提供了許多選擇。 例如,中間件是使用處理程序實現的,其中 ServeHTTP 首先執行某些操作,然後呼叫另一個處理程序的 ServeHTTP 方法。

如上所述,處理程序只是產生對請求的回應。 但是在特定的時間點應該使用哪個特定的處理程序?

請求路由

若要做出正確的選擇,請使用 HTTP 多工器。 在許多庫中,它被稱為複用器或路由器,但它們都是相同的東西。 多工器的作用是分析請求路徑並選擇適當的處理程序。

如果您需要支援複雜的路由,那麼最好使用第三方程式庫。 一些最先進的—— 大猩猩/多路復用器 и 戈吉,這些庫使得可以毫無問題地實現中間處理。 在他們的幫助下,您可以設定通配符路由並執行許多其他任務。 它們的優點是與標準 HTTP 處理程序相容。 因此,您可以編寫將來可以修改的簡單程式碼。

在正常情況下使用複雜的框架將需要非標準解決方案,這使得預設處理程序的使用變得非常複雜。 要創建絕大多數應用程序,預設庫和簡單路由器的組合就足夠了。

查詢處理

此外,我們需要一個元件來「偵聽」傳入連線並將所有請求重新導向到正確的處理程序。 http.Server 可以輕鬆處理這個任務。

下面顯示伺服器負責與連線處理相關的所有任務。 例如,這可以使用 TLS 協定進行工作。 為了實作 http.ListenAndServer 調用,需要使用標準 HTTP 伺服器。

現在讓我們來看看更複雜的範例。

新增讓我們加密

預設情況下,我們的應用程式運行在HTTP協定上,但建議使用HTTPS協定。 這在 Go 中可以毫無問題地完成。 如果您已收到憑證和私鑰,那麼使用正確的憑證和金鑰檔案註冊 ListenAndServeTLS 就足夠了。

http.ListenAndServeTLS(":443", "cert.pem", "key.pem", nil)

你總是可以做得更好。

讓我們加密 提供免費證書並自動續約。 為了使用該服務,您需要一個包 autocert.

最簡單的設定方法是將 autocert.NewListener 方法與 http.Serve 結合使用。 此方法可讓您在 HTTP 伺服器處理請求時取得和更新 TLS 憑證:

http.Serve(autocert.NewListener("example.com"), nil)

如果我們在瀏覽器中打開 example.com,我們將收到 HTTPS 回應“Hello, world!”

如果您需要更詳細的配置,那麼您應該使用 autocert.Manager 管理員。 然後我們建立自己的 http.Server 實例(到目前為止我們預設使用它)並將管理器新增到 TLSConfig 伺服器:

m := &autocert.Manager{
Cache:      autocert.DirCache("golang-autocert"),
Prompt:     autocert.AcceptTOS,
HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"),
}
server := &http.Server{
    Addr:      ":443",
    TLSConfig: m.TLSConfig(),
}
server.ListenAndServeTLS("", "")

這是透過自動憑證更新實現完整 HTTPS 支援的簡單方法。

新增自訂路線

標準庫中包含的預設路由器很好,但它非常基礎。 大多數應用程式需要更複雜的路由,包括嵌套和通配符路由,或設定路徑模式和參數的過程。

在這種情況下,值得使用套件 大猩猩/多路復用器 и 戈吉。 我們將學習如何使用後者 - 下面顯示了一個範例。

給出的檔案 api/v1/api.go 包含我們的 API 的路由:

/ HelloResponse is the JSON representation for a customized message
type HelloResponse struct {
Message string `json:"message"`
}
 
// HelloName returns a personalized JSON message
func HelloName(w http.ResponseWriter, r *http.Request) {
name := chi.URLParam(r, "name")
response := HelloResponse{
Message: fmt.Sprintf("Hello %s!", name),
}
jsonResponse(w, response, http.StatusOK)
}
 
// NewRouter returns an HTTP handler that implements the routes for the API
func NewRouter() http.Handler {
r := chi.NewRouter()
r.Get("/{name}", HelloName)
return r
}

我們在主檔案中設定路由的 api/vq 前綴。

然後我們可以將其安裝到我們主應用程式中 api/v1/ 前綴下的主路由器:

// NewRouter returns a new HTTP handler that implements the main server routes
func NewRouter() http.Handler {
router := chi.NewRouter()
    router.Mount("/api/v1/", v1.NewRouter())
    return router
}
http.Serve(autocert.NewListener("example.com"), NewRouter())

Go 可以輕鬆處理複雜的路由,從而可以簡化大型複雜應用程式的建置和維護。

使用中介軟體

分段涉及將一個 HTTP 處理程序與另一個處理程序包裝在一起,從而可以快速執行身份驗證、壓縮、日誌記錄和其他一些功能。

作為範例,讓我們來看看 http.Handler 介面;我們將使用它來編寫一個對服務使用者進行身份驗證的處理程序。

func RequireAuthentication(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if !isAuthenticated(r) {
            http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
            return
        }
        // Assuming authentication passed, run the original handler
        next.ServeHTTP(w, r)
    })
}

有第三方路由器(例如 chi)可讓您擴展中間件功能。

處理靜態文件

Go 標準函式庫包含處理靜態內容的功能,包括圖片、JavaScript 和 CSS 檔案。 可以透過 http.FileServer 函數存取它們。 它會傳回一個處理程序,為特定目錄中的檔案提供服務。

func NewRouter() http.Handler {
    router := chi.NewRouter()
    r.Get("/{name}", HelloName)
 
// Настройка раздачи статических файлов
staticPath, _ := filepath.Abs("../../static/")
fs := http.FileServer(http.Dir(staticPath))
    router.Handle("/*", fs)
    
    return r

絕對值得記住的是,如果 http.Dir 不包含主 index.html 文件,則它會顯示目錄的內容。 在這種情況下,為了防止目錄被破壞,您應該使用該包 unindexed.

正確關機

Go 還有一個稱為 HTTP 伺服器正常關閉的功能。 這可以使用 Shutdown() 方法來完成。 伺服器在goroutine中啟動,然後監聽channel接收中斷訊號。 一旦收到訊號,伺服器就會關閉,但不是立即關閉,而是在幾秒鐘後關閉。

handler := server.NewRouter()
srv := &http.Server{
    Handler: handler,
}
 
go func() {
srv.Serve(autocert.NewListener(domains...))
}()
 
// Wait for an interrupt
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
 
// Attempt a graceful shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
srv.Shutdown(ctx)

作為結論

Go 是一種功能強大的語言,擁有幾乎通用的標準函式庫。 它的預設功能非常廣泛,並且可以使用介面進行增強 - 這使您可以開發真正可靠的 HTTP 伺服器。

技能箱推薦:

來源: www.habr.com

添加評論