使用 gorilla/mux 進行 HTTP 請求路由和驗證
gorilla/mux 包以直觀的 API 提供了 HTTP 請求路由、驗證和其它服務。
Go 網路庫包括 http.ServeMux
結構型別,它支援 HTTP 請求多路複用(路由):Web 伺服器將託管資源的 HTTP 請求與諸如 /sales4today
之類的 URI 路由到程式碼處理程式;處理程式在傳送 HTTP 響應(通常是 HTML 頁面)之前執行適當的邏輯。 這是該體系的草圖:
+-----------+ +--------+ +---------+
HTTP 請求---->| web 伺服器 |---->| 路由 |---->| 處理程式 |
+-----------+ +--------+ +---------+
呼叫 ListenAndServe
方法後啟動 HTTP 伺服器:
http.ListenAndServe(":8888", nil) // args: port & router
第二個引數 nil
意味著 DefaultServeMux
用於請求路由。
gorilla/mux
庫包含 mux.Router
型別,可替代 DefaultServeMux
或自定義請求多路複用器。 在 ListenAndServe
呼叫中,mux.Router
例項將代替 nil
作為第二個引數。 下面的示例程式碼很好的說明了為什麼 mux.Router
如此吸引人:
1、一個簡單的 CRUD web 應用程式
crud web 應用程式(見下文)支援四種 CRUD(建立/讀取/更新/刪除)操作,它們分別對應四種 HTTP 請求方法:POST、GET、PUT 和 DELETE。 在這個 CRUD 應用程式中,所管理的資源是套話與反套話的列表,每個都是套話及其反面的的套話,例如這對:
Out of sight, out of mind. Absence makes the heart grow fonder.
可以新增新的套話對,可以編輯或刪除現有的套話對。
CRUD web 應用程式:
package main
import (
"gorilla/mux"
"net/http"
"fmt"
"strconv"
)
const GETALL string = "GETALL"
const GETONE string = "GETONE"
const POST string = "POST"
const PUT string = "PUT"
const DELETE string = "DELETE"
type clichePair struct {
Id int
Cliche string
Counter string
}
// Message sent to goroutine that accesses the requested resource.
type crudRequest struct {
verb string
cp *clichePair
id int
cliche string
counter string
confirm chan string
}
var clichesList = []*clichePair{}
var masterId = 1
var crudRequests chan *crudRequest
// GET /
// GET /cliches
func ClichesAll(res http.ResponseWriter, req *http.Request) {
cr := &crudRequest{verb: GETALL, confirm: make(chan string)}
completeRequest(cr, res, "read all")
}
// GET /cliches/id
func ClichesOne(res http.ResponseWriter, req *http.Request) {
id := getIdFromRequest(req)
cr := &crudRequest{verb: GETONE, id: id, confirm: make(chan string)}
completeRequest(cr, res, "read one")
}
// POST /cliches
func ClichesCreate(res http.ResponseWriter, req *http.Request) {
cliche, counter := getDataFromRequest(req)
cp := new(clichePair)
cp.Cliche = cliche
cp.Counter = counter
cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)}
completeRequest(cr, res, "create")
}
// PUT /cliches/id
func ClichesEdit(res http.ResponseWriter, req *http.Request) {
id := getIdFromRequest(req)
cliche, counter := getDataFromRequest(req)
cr := &crudRequest{verb: PUT, id: id, cliche: cliche, counter: counter, confirm: make(chan string)}
completeRequest(cr, res, "edit")
}
// DELETE /cliches/id
func ClichesDelete(res http.ResponseWriter, req *http.Request) {
id := getIdFromRequest(req)
cr := &crudRequest{verb: DELETE, id: id, confirm: make(chan string)}
completeRequest(cr, res, "delete")
}
func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
crudRequests<-cr
msg := <-cr.confirm
res.Write([]byte(msg))
logIt(logMsg)
}
func main() {
populateClichesList()
// From now on, this gorountine alone accesses the clichesList.
crudRequests = make(chan *crudRequest, 8)
go func() { // resource manager
for {
select {
case req := <-crudRequests:
if req.verb == GETALL {
req.confirm<-readAll()
} else if req.verb == GETONE {
req.confirm<-readOne(req.id)
} else if req.verb == POST {
req.confirm<-addPair(req.cp)
} else if req.verb == PUT {
req.confirm<-editPair(req.id, req.cliche, req.counter)
} else if req.verb == DELETE {
req.confirm<-deletePair(req.id)
}
}
}()
startServer()
}
func startServer() {
router := mux.NewRouter()
// Dispatch map for CRUD operations.
router.HandleFunc("/", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesAll).Methods("GET")
router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")
router.HandleFunc("/cliches", ClichesCreate).Methods("POST")
router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")
router.HandleFunc("/cliches/{id:[0-9]+}", ClichesDelete).Methods("DELETE")
http.Handle("/", router) // enable the router
// Start the server.
port := ":8888"
fmt.Println("\nListening on port " + port)
http.ListenAndServe(port, router); // mux.Router now in play
}
// Return entire list to requester.
func readAll() string {
msg := "\n"
for _, cliche := range clichesList {
next := strconv.Itoa(cliche.Id) + ": " + cliche.Cliche + " " + cliche.Counter + "\n"
msg += next
}
return msg
}
// Return specified clichePair to requester.
func readOne(id int) string {
msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n"
index := findCliche(id)
if index >= 0 {
cliche := clichesList[index]
msg = "\n" + strconv.Itoa(id) + ": " + cliche.Cliche + " " + cliche.Counter + "\n"
}
return msg
}
// Create a new clichePair and add to list
func addPair(cp *clichePair) string {
cp.Id = masterId
masterId++
clichesList = append(clichesList, cp)
return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}
// Edit an existing clichePair
func editPair(id int, cliche string, counter string) string {
msg := "\n" + "Bad Id: " + strconv.Itoa(id) + "\n"
index := findCliche(id)
if index >= 0 {
clichesList[index].Cliche = cliche
clichesList[index].Counter = counter
msg = "\nCliche edited: " + cliche + " " + counter + "\n"
}
return msg
}
// Delete a clichePair
func deletePair(id int) string {
idStr := strconv.Itoa(id)
msg := "\n" + "Bad Id: " + idStr + "\n"
index := findCliche(id)
if index >= 0 {
clichesList = append(clichesList[:index], clichesList[index + 1:]...)
msg = "\nCliche " + idStr + " deleted\n"
}
return msg
}
//*** utility functions
func findCliche(id int) int {
for i := 0; i < len(clichesList); i++ {
if id == clichesList[i].Id {
return i;
}
}
return -1 // not found
}
func getIdFromRequest(req *http.Request) int {
vars := mux.Vars(req)
id, _ := strconv.Atoi(vars["id"])
return id
}
func getDataFromRequest(req *http.Request) (string, string) {
// Extract the user-provided data for the new clichePair
req.ParseForm()
form := req.Form
cliche := form["cliche"][0] // 1st and only member of a list
counter := form["counter"][0] // ditto
return cliche, counter
}
func logIt(msg string) {
fmt.Println(msg)
}
func populateClichesList() {
var cliches = []string {
"Out of sight, out of mind.",
"A penny saved is a penny earned.",
"He who hesitates is lost.",
}
var counterCliches = []string {
"Absence makes the heart grow fonder.",
"Penny-wise and dollar-foolish.",
"Look before you leap.",
}
for i := 0; i < len(cliches); i++ {
cp := new(clichePair)
cp.Id = masterId
masterId++
cp.Cliche = cliches[i]
cp.Counter = counterCliches[i]
clichesList = append(clichesList, cp)
}
}
為了專注於請求路由和驗證,CRUD 應用程式不使用 HTML 頁面作為請求響應。 相反,請求會產生明文響應訊息:套話對的列表是對 GET 請求的響應,確認新的套話對已新增到列表中是對 POST 請求的響應,依此類推。 這種簡化使得使用命令列實用程式(如 curl)可以輕鬆地測試應用程式,尤其是 gorilla/mux
元件。
gorilla/mux
包可以從 GitHub 安裝。 CRUD app 無限期執行;因此,應使用 Control-C
或同等命令終止。 CRUD 應用程式的程式碼,以及自述檔案和簡單的 curl 測試,可以在我的網站上找到。
2、請求路由
mux.Router
擴充套件了 REST 風格的路由,它賦給 HTTP 方法(例如,GET)和 URL 末尾的 URI 或路徑(例如 /cliches
)相同的權重。 URI 用作 HTTP 動詞(方法)的名詞。 例如,在HTTP請求中有一個起始行,例如:
GET /cliches
意味著得到所有的套話對,而一個起始線,如:
POST /cliches
意味著從 HTTP 正文中的資料建立一個套話對。
在 CRUD web 應用程式中,有五個函式充當 HTTP 請求的五種變體的請求處理程式:
ClichesAll(...) # GET: 獲取所有的套話對
ClichesOne(...) # GET: 獲取指定的套話對
ClichesCreate(...) # POST: 建立新的套話對
ClichesEdit(...) # PUT: 編輯現有的套話對
ClichesDelete(...) # DELETE: 刪除指定的套話對
每個函式都有兩個引數:一個 http.ResponseWriter
用於向請求者傳送一個響應,一個指向 http.Request
的指標,該指標封裝了底層 HTTP 請求的資訊。 使用 gorilla/mux
包可以輕鬆地將這些請求處理程式註冊到Web伺服器,並執行基於正規表示式的驗證。
CRUD 應用程式中的 startServer
函式註冊請求處理程式。 考慮這對註冊,router
作為 mux.Router
例項:
router.HandleFunc("/", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesAll).Methods("GET")
這些語句意味著對單斜線 /
或 /cliches
的 GET 請求應該路由到 ClichesAll
函式,然後處理請求。 例如,curl 請求(使用 %
作為命令列提示符):
% curl --request GET localhost:8888/
會產生如下結果:
1: Out of sight, out of mind. Absence makes the heart grow fonder.
2: A penny saved is a penny earned. Penny-wise and dollar-foolish.
3: He who hesitates is lost. Look before you leap.
這三個套話對是 CRUD 應用程式中的初始資料。
在這句註冊語句中:
router.HandleFunc("/cliches", ClichesAll).Methods("GET")
router.HandleFunc("/cliches", ClichesCreate).Methods("POST")
URI 是相同的(/cliches
),但動詞不同:第一種情況下為 GET 請求,第二種情況下為 POST 請求。 此註冊舉例說明了 REST 樣式的路由,因為僅動詞的不同就足以將請求分派給兩個不同的處理程式。
註冊中允許多個 HTTP 方法,儘管這會影響 REST 風格路由的精髓:
router.HandleFunc("/cliches", DoItAll).Methods("POST", "GET")
除了動詞和 URI 之外,還可以在功能上路由 HTTP 請求。 例如,註冊
router.HandleFunc("/cliches", ClichesCreate).Schemes("https").Methods("POST")
要求對 POST 請求進行 HTTPS 訪問以建立新的套話對。以類似的方式,註冊可能需要具有指定的 HTTP 頭元素(例如,認證憑證)的請求。
3、 Request validation
gorilla/mux
包採用簡單,直觀的方法透過正規表示式進行請求驗證。 考慮此請求處理程式以獲取一個操作:
router.HandleFunc("/cliches/{id:[0-9]+}", ClichesOne).Methods("GET")
此註冊排除了 HTTP 請求,例如:
% curl --request GET localhost:8888/cliches/foo
因為 foo 不是十進位制數字。該請求導致熟悉的 404(未找到)狀態碼。 在此處理程式註冊中包含正規表示式模式可確保僅在請求 URI 以十進位制整數值結束時才呼叫 ClichesOne
函式來處理請求:
% curl --request GET localhost:8888/cliches/3 # ok
另一個例子,請求如下:
% curl --request PUT --data "..." localhost:8888/cliches
此請求導致狀態程式碼為 405(錯誤方法),因為 /cliches URI 在 CRUD 應用程式中僅在 GET 和 POST 請求中註冊。 像 GET 請求一樣,PUT 請求必須在 URI 的末尾包含一個數字 id:
router.HandleFunc("/cliches/{id:[0-9]+}", ClichesEdit).Methods("PUT")
4、併發問題
gorilla/mux
路由器作為單獨的 Go 協程執行對已註冊的請求處理程式的每次呼叫,這意味著併發性被內建於包中。 例如,如果有十個同時發出的請求,例如
% curl --request POST --data "..." localhost:8888/cliches
然後 mux.Router
啟動十個 Go 協程來執行 ClichesCreate
處理程式。
GET all、GET one、POST、PUT 和 DELETE 中的五個請求操作中,最後三個改變了所請求的資源,即包含套話對的共享 clichesList
。 因此,CRUD app 需要透過協調對 clichesList
的訪問來保證安全的併發性。 在不同但等效的術語中,CRUD app 必須防止 clichesList
上的競爭條件。 在生產環境中,可以使用資料庫系統來儲存諸如 clichesList
之類的資源,然後可以透過資料庫事務來管理安全併發。
CRUD 應用程式採用推薦的Go方法來實現安全併發:
- 只有一個 Go 協程,資源管理器在 CRUD app
startServer
函式中啟動,一旦 Web 伺服器開始偵聽請求,就可以訪問clichesList
。 - 諸如
ClichesCreate
和ClichesAll
之類的請求處理程式向 Go 通道傳送(指向)crudRequest
例項(預設情況下是執行緒安全的),並且資源管理器單獨從該通道讀取。 然後,資源管理器對clichesList
執行請求的操作。
安全併發體系結構繪製如下:
crudRequest 讀/寫
請求處理程式 -------------> 資源託管者 ------------> 套話列表
在這種架構中,不需要顯式鎖定 clichesList
,因為一旦 CRUD 請求開始進入,只有一個 Go 協程(資源管理器)訪問 clichesList
。
為了使 CRUD 應用程式儘可能保持併發,在一方請求處理程式與另一方的單一資源管理器之間進行有效的分工至關重要。 在這裡,為了審查,是 ClichesCreate
請求處理程式:
func ClichesCreate(res http.ResponseWriter, req *http.Request) {
cliche, counter := getDataFromRequest(req)
cp := new(clichePair)
cp.Cliche = cliche
cp.Counter = counter
cr := &crudRequest{verb: POST, cp: cp, confirm: make(chan string)}
completeRequest(cr, res, "create")
}
ClichesCreate
呼叫實用函式 getDataFromRequest
,它從 POST 請求中提取新的套話和反套話。 然後 ClichesCreate
函式建立一個新的 ClichePair
,設定兩個欄位,並建立一個 crudRequest
傳送給單個資源管理器。 此請求包括一個確認通道,資源管理器使用該通道將資訊返回給請求處理程式。 所有設定工作都可以在不涉及資源管理器的情況下完成,因為尚未訪問 clichesList
。
請求處理程式呼叫實用程式函式,該函式從 POST 請求中提取新的套話和反套話。 然後,該函式建立一個新的,設定兩個欄位,並建立一個 crudRequest 傳送到單個資源管理器。 此請求包括一個確認通道,資源管理器使用該通道將資訊返回給請求處理程式。 所有設定工作都可以在不涉及資源管理器的情況下完成,因為尚未訪問它。
completeRequest
實用程式函式在 ClichesCreate
函式和其他請求處理程式的末尾呼叫:
completeRequest(cr, res, "create") // shown above
透過將 crudRequest
放入 crudRequests
頻道,使資源管理器發揮作用:
func completeRequest(cr *crudRequest, res http.ResponseWriter, logMsg string) {
crudRequests<-cr // 向資源託管者傳送請求
msg := <-cr.confirm // 等待確認
res.Write([]byte(msg)) // 向請求方傳送確認
logIt(logMsg) // 列印到標準輸出
}
對於 POST 請求,資源管理器呼叫實用程式函式 addPair
,它會更改 clichesList
資源:
func addPair(cp *clichePair) string {
cp.Id = masterId // 分配一個唯一的 ID
masterId++ // 更新 ID 計數器
clichesList = append(clichesList, cp) // 更新列表
return "\nCreated: " + cp.Cliche + " " + cp.Counter + "\n"
}
資源管理器為其他 CRUD 操作呼叫類似的實用程式函式。 值得重複的是,一旦 Web 伺服器開始接受請求,資源管理器就是唯一可以讀取或寫入 clichesList
的 goroutine。
對於任何型別的 Web 應用程式,gorilla/mux
包在簡單直觀的 API 中提供請求路由、請求驗證和相關服務。 CRUD web 應用程式突出了軟體包的主要功能。
via: https://opensource.com/article/18/8/http-request-routing-validation-gorillamux
作者:Marty Kalin 選題:lujun9972 譯者:yongshouzhang 校對:wxy
本文由 LCTT 原創編譯,Linux中國 榮譽推出
相關文章
- 使用 gorilla/mux 增強 Go HTTP 伺服器的路由能力GoUXHTTP伺服器路由
- Go Web 路由處理利器 gorilla/mux 庫GoWeb路由UX
- 使用 $fetch 進行 HTTP 請求HTTP
- 使用 http-proxy 對網路請求進行代理HTTP
- 使用Socket進行HTTP請求與報文講解HTTP
- 使用 Laravel 請求類來驗證表單請求Laravel
- 合併HTTP請求vs並行HTTP請求,到底誰更快?HTTP並行
- 合併HTTP請求 vs 並行HTTP請求,到底誰更快?HTTP並行
- 使用retrofit進行網路請求
- HTTP請求格式和HTTP響應格式HTTP
- 使用CloseableHttpClient 訪問 http 和https 的get請求HTTPclient
- 使用 request 和 cheerio 庫來傳送 HTTP 請求HTTP
- 使用jMeter構造大量併發HTTP請求進行微服務效能測試JMeterHTTP微服務
- 使用Feign傳送HTTP請求HTTP
- 使用 Netcat 模擬 HTTP 請求HTTP
- 前端進階(2)使用fetch/axios時, 如何取消http請求前端iOSHTTP
- SpringBoot:使用AOP對API請求授權驗證 - GeorgeSpring BootAPI
- GOLANG Web請求引數驗證GolangWeb
- 申請SSL證書如何進行操作DNS域名驗證DNS
- nodejs使用request傳送http請求NodeJSHTTP
- golang使用fasthttp 發起http請求GolangASTHTTP
- HttpClient 進行soap請求HTTPclient
- go http請求GoHTTP
- http請求概述HTTP
- Jsoup http請求JSHTTP
- 如何高效定義和驗證restful請求的引數REST
- Laravel 使用 ApiToken 認證請求LaravelAPI
- 使用IDEA的 HTTP request發請二進位制資料流訪問請求IdeaHTTP
- Android開發 - Retrofit 2 使用自簽名的HTTPS證書進行API請求AndroidHTTPAPI
- 使用requests庫來傳送HTTP請求HTTP
- windows中使用cmd發起http請求WindowsHTTP
- 請問在一個介面內怎麼根據請求方式對引數進行分別驗證呢?
- Spring Boot使用JWT進行token驗證Spring BootJWT
- Laravel 自定義表單請求驗證忽略某些欄位驗證Laravel
- 中止請求和超時 跨域的HTTP請求 認證方式 JSONP跨域HTTPJSON
- HTTP請求報文HTTP
- Cookie 與 HTTP請求CookieHTTP
- python做http請求PythonHTTP