用 Go 創(chuàng)建一個(gè) Web 應(yīng)用
客戶端/服務(wù)器
假設(shè)你開發(fā)了一個(gè)使用 Go 語言編寫的程序來管理你的照片庫,這個(gè)程序可以在你的電腦上處理你的照片。你可能想與其他家庭成員分享這些照片,為此,你可以通過電子郵件發(fā)送包含所有照片的附件。但如果你拍了一萬張照片,這種解決方案可能是不可行的。
您可以將內(nèi)容自動上傳到您最喜愛的社交網(wǎng)絡(luò)。如果您必須一次處理一張照片,該操作可能會變得非常耗時(shí)。另一種解決方案可能是將您的程序直接插入社交網(wǎng)絡(luò)系統(tǒng),以編程方式上傳圖片。
我們可以借助社交網(wǎng)絡(luò)公開的 API 來做到這一點(diǎn)。您可以通過直接調(diào)用它們的 API 將圖片推送到 for 循環(huán)中。
在這種情況下,您將使用一個(gè) API。你是客戶端。社交網(wǎng)絡(luò)代表服務(wù)器。
調(diào)用 API 意味著按照精確的文檔向 Web 服務(wù)器發(fā)出 HTTP(s) 請求。客戶端和服務(wù)器這兩個(gè)術(shù)語很重要,您必須記住它們。作為客戶端,我們使用(或消費(fèi))API。服務(wù)器是一個(gè)計(jì)算機(jī)程序,旨在接受和響應(yīng)客戶端的 API 調(diào)用。
Go 語言在創(chuàng)建簡單高效的 Web 服務(wù)器方面很有優(yōu)勢。Go 語言提供了內(nèi)置的 HTTP 包,其中包含了快速創(chuàng)建 Web 或文件服務(wù)器所需的實(shí)用工具。這使得使用 Go 語言創(chuàng)建 Web 服務(wù)器和 Web 服務(wù)變得簡單和高效。
創(chuàng)建 server.go 文件
在上一篇文章中,我們學(xué)習(xí)了 net/http 包中的 Request 和 Response ,所以這里我們可以直接使用:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello!")
})
fmt.Printf("Starting server at port 8088\n")
if err := http.ListenAndServe(":8088", nil); err != nil {
log.Fatal(err)
}
}
運(yùn)行 go run server.go ,可以在終端上看到如下輸出:
Starting server at port 8088
在此階段,我們將創(chuàng)建一個(gè)實(shí)際在端口 8088 上提供服務(wù)并可以響應(yīng)傳入 GET 請求的 Web 服務(wù)器。讓我們在端口 8088 啟動 Web 服務(wù)器。ListenAndServe() 方法由我們在第一步中導(dǎo)入的 http 數(shù)據(jù)包導(dǎo)出。此方法允許我們啟動 Web 服務(wù)器并指定端口以偵聽傳入請求。請注意,端口參數(shù)需要作為以冒號標(biāo)點(diǎn)符號開頭的字符串傳遞。第二個(gè)參數(shù)接受一個(gè)處理程序來為 HTTP/2 配置服務(wù)器,將 nil 作為第二個(gè)參數(shù)傳遞。
我們將使用 HandleFunc() 函數(shù)將路由處理程序添加到 Web 服務(wù)器。第一個(gè)參數(shù)接受它需要監(jiān)聽的路徑 /hello。在這里,您告訴服務(wù)器監(jiān)聽對 http://localhost:8088/hello 的任何傳入請求。第二個(gè)參數(shù)接受一個(gè)函數(shù),該函數(shù)包含正確響應(yīng)請求的業(yè)務(wù)邏輯。
默認(rèn)情況下,此函數(shù)接受 ResponseWriter 以發(fā)回響應(yīng),并接受 Request 對象以提供有關(guān)請求本身的更多信息。例如,您可以訪問有關(guān)已發(fā)送標(biāo)頭的信息,這對于驗(yàn)證請求很有用。
如您所見,處理程序發(fā)送了一個(gè)“Hello!”消息,因?yàn)槲覀儗⒋隧憫?yīng)傳遞給 ResponseWriter。
路由添加基本校驗(yàn)
不用說,安全很重要。讓我們探索一些增強(qiáng) Go Web 服務(wù)器安全性的基本策略。在我們這樣做之前,我們應(yīng)該花點(diǎn)時(shí)間來提高代碼的可讀性。讓我們創(chuàng)建 helloHandler 函數(shù),它包含與 /hello 請求相關(guān)的所有邏輯。
func helloHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/hello" {
http.Error(w, "404 not found.", http.StatusNotFound)
return
}
if r.Method != "GET" {
http.Error(w, "Method is not supported.", http.StatusNotFound)
return
}
fmt.Fprintf(w, "Hello!")
}
此處理程序使用 Request 對象檢查請求的路徑是否正確。這是一個(gè)非?;镜氖纠?,說明如何使用 Request 對象。
如果路徑不正確,服務(wù)器將向用戶返回 StatusNotFound 錯(cuò)誤。要向用戶寫入錯(cuò)誤,您可以使用 http.Error 方法。請注意,StatusNotFound 代碼對應(yīng)于 404 錯(cuò)誤。所有狀態(tài)代碼都可以在 Golang 文檔中找到。
接下來,我們添加一個(gè)檢查來驗(yàn)證請求的類型。如果該方法不對應(yīng)于 GET,則服務(wù)器返回一個(gè)新錯(cuò)誤。當(dāng)兩個(gè)檢查都通過時(shí),服務(wù)器返回其成功響應(yīng)“Hello!”。
我們需要做的最后一件事是修改 main() 函數(shù)中的 handleFunc 函數(shù)以接受上面的 helloHandler 函數(shù)。
http.HandleFunc("/hello", helloHandler)
完整的 server.go 文件如下:
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/hello" {
http.Error(w, "404 not found", http.StatusNotFound)
return
}
if r.Method != "GET" {
http.Error(w, "Method is not supported", http.StatusNotFound)
return
}
fmt.Fprintf(w, "Hello!")
}
func main() {
http.HandleFunc("/hello", helloHandler)
fmt.Printf("Starting server at port 8088\n")
if err := http.ListenAndServe(":8088", nil); err != nil {
log.Fatal(err)
}
}
接下來,我們將使用 go run server.go 啟動 Go Web 服務(wù)器。您可以使用 Postman 或 cURL 等工具向 http://localhost:8088/hello 發(fā)送 POST 請求來測試您的安全性。我們將得到與上一次相同的結(jié)果。
啟動靜態(tài) Web 服務(wù)器
在這一步中,我們將創(chuàng)建一個(gè)簡單的文件服務(wù)器來托管靜態(tài)文件。這將是對 Web 服務(wù)器的一個(gè)非常簡單的添加。為確保我們有內(nèi)容可在 Web 服務(wù)器上提供服務(wù),讓我們新建一個(gè)位于 static 文件夾中的 index.html 文件。為了簡單起見,只需在文件中添加一個(gè)標(biāo)題為“Go Server”的標(biāo)題。如果您愿意,可以添加更多文件或樣式文件以使您的 Web 服務(wù)器看起來更漂亮一些。
新建一個(gè) index.html 文件:
<html>
<head>
<title>Learn Go</title>
</head>
<body>
<h2>Go Server</h2>
</body>
</html>
要為 static 文件夾提供服務(wù),您必須向 server.go 添加兩行代碼。第一行代碼使用 FileServer 函數(shù)創(chuàng)建文件服務(wù)器對象。此函數(shù)接受 http.Dir 類型的路徑。因此,我們必須將字符串路徑“./static”轉(zhuǎn)換為 http.Dir 路徑類型。
不要忘記指定 Handle 路由,它接受路徑和文件服務(wù)器。此函數(shù)的作用與 HandleFunc 函數(shù)相同,但有一些細(xì)微差別。有關(guān) FileServer 對象的更多信息,請查看文檔。
func main() {
fileServer := http.FileServer(http.Dir("./static"))
http.Handle("/", fileServer)
http.HandleFunc("/hello", helloHandler)
fmt.Printf("Starting server at port 8088\n")
if err := http.ListenAndServe(":8088", nil); err != nil {
log.Fatal(err)
}
}
是時(shí)候嘗試代碼了。使用 go run server.go 啟動服務(wù)器并訪問 http://localhost:8088/。你應(yīng)該看到:
接受表單提交 POST 請求
最后,Web 服務(wù)器必須響應(yīng)表單提交。讓我們向 static 文件夾中的 form.html 文件添加一些內(nèi)容。請注意,表單操作已發(fā)送到 /form。這意味著來自表單的 POST 請求將被發(fā)送到 http://localhost:8088/form。表單本身要求輸入兩個(gè)變量:name 和 address。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<div>
<form method="POST" actinotallow="/form">
<label>Name</label><input name="name" type="text" value="" />
<label>Address</label><input name="address" type="text" value="" />
<input type="submit" value="submit" />
</form>
</div>
</body>
</html>
下一步是創(chuàng)建處理程序來接受 /form 請求。 form.html 文件已經(jīng)通過 FileServer 提供,可以通過 http://localhost:8088/form.html 訪問。
首先,該函數(shù)必須調(diào)用 ParseForm() 來解析原始查詢并更新 r.PostForm 和 r.Form。這將允許我們通過 r.FormValue 方法訪問 name 和 address 值。
在函數(shù)的末尾,我們使用 fmt.Fprintf 將這兩個(gè)值寫入 ResponseWriter。
func formHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
fmt.Fprintf(w, "ParseForm() err: %v", err)
return
}
fmt.Fprintf(w, "POST request successful")
name := r.FormValue("name")
address := r.FormValue("address")
fmt.Fprintf(w, "Name = %s\n", name)
fmt.Fprintf(w, "Address = %s\n", address)
}
不要忘記將新的表單處理程序路由添加到 main() 函數(shù)。
http.HandleFunc("/form", formHandler)
最后的 server.go 文件如下:
package main
import (
"fmt"
"log"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/hello" {
http.Error(w, "404 not found", http.StatusNotFound)
return
}
if r.Method != "GET" {
http.Error(w, "Method is not supported", http.StatusNotFound)
return
}
fmt.Fprintf(w, "Hello!")
}
func formHandler(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
fmt.Fprintf(w, "ParseForm() err: %v", err)
return
}
fmt.Fprintf(w, "POST request successful\n")
name := r.FormValue("name")
address := r.FormValue("address")
fmt.Fprintf(w, "Name = %s\n", name)
fmt.Fprintf(w, "Address = %s\n", address)
}
func main() {
// http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
// fmt.Fprintf(w, "Hello!")
// })
fileServer := http.FileServer(http.Dir("./static"))
http.Handle("/", fileServer)
http.HandleFunc("/hello", helloHandler)
http.HandleFunc("/form", formHandler)
fmt.Printf("Starting server at port 8088\n")
if err := http.ListenAndServe(":8088", nil); err != nil {
log.Fatal(err)
}
}
表單處理程序測試
我們可以通過使用 go run server.go 啟動服務(wù)器來測試表單。當(dāng)服務(wù)器啟動時(shí),訪問 http://localhost:8088/form.html。您應(yīng)該看到兩個(gè)輸入字段和一個(gè)提交按鈕。
填寫完表格后,點(diǎn)擊提交按鈕。服務(wù)器應(yīng)處理您的 POST 請求并在 http://localhost:8088/form 響應(yīng)頁面上向您顯示結(jié)果,例如以下響應(yīng):
如果你看到上面的結(jié)果,你已經(jīng)成功地創(chuàng)建了你的第一個(gè) Golang 網(wǎng)絡(luò)和文件服務(wù)器。恭喜!如果您想進(jìn)一步探索 Golang Web 服務(wù)器,Golang HTTP 包文檔中有很多很好的例子。
官方文檔:Writing Web Applications