場景說明
- go 中使用官方的http server方法的話,缺少統(tǒng)一的方法調(diào)用邻寿,無法對用戶的權限等進行統(tǒng)一的驗證
- http.HandleFunc("/ws", wsHandler) 官方的這種路由方式蝎土,無法靈活的進行應用
- php 中可以使用 __construct 對訪問的方法進行統(tǒng)一的驗證视哑,而直接使用go的官方方法并沒有類似的
創(chuàng)建簡單的 http server
func main() {
// /ws 是url路徑
http.HandleFunc("/ws", wsHandler)
http.HandleFunc("/test", testHandler)
http.ListenAndServe("0.0.0.0:8000", nil)
}
// 用戶ws連接處理方法
func wsHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
}
func testHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("testHandler"))
}
瀏覽器訪問
- 訪問: http://127.0.0.1:8000/ws 可以得到 hello world
- 訪問: http://127.0.0.1:8000/test 可以得到 testHandler
這時候如果這兩個方法都要調(diào)用一個公共方法進行權限驗證的話都需要在每個方法內(nèi)部進行調(diào)用
這樣在方法越來越多的情況下,會造成非常嚴重的代碼冗余和難以維護
解決方法誊涯,重寫官方的方法
創(chuàng)建結構
// 定義結構挡毅,對這個結構綁定方法就可以創(chuàng)建屬于自己的http server
type application struct {
// 路由部分,其中保存訪問路徑與方法的對應關系
routes map[string]func(http.ResponseWriter, *http.Request) string
// 白名單部分暴构,在白名單中的路徑我們不去驗證用戶的相關權限
while map[string]bool
}
定義創(chuàng)建這個結構的方法
// 創(chuàng)建application
func Create() *application {
return &application{
routes: make(map[string]func(http.ResponseWriter, *http.Request) string),
while: make(map[string]bool),
}
}
定義創(chuàng)建路由的方法
func (app *application) Router(path string, controller func(http.ResponseWriter, *http.Request) string) {
app.routes[path] = controller
}
定義創(chuàng)建白名單的方法
// 在這個變量中的路徑跪呈,我們不去驗證權限
func (app *application) Exclude(path string) {
app.while[path] = true
}
定義靜態(tài)文件目錄,注意這個目錄是以當前項目路徑為根目錄
const documentRoot = "public"
func isDocumentRoot(url string) (bool, string) {
// 定義用戶訪問 / 根目錄的話 跳轉到靜態(tài)目錄
if url == "/" {
return true, "/" + documentRoot + "/"
}
// 判斷訪問的路徑是否是靜文件地址
return strings.Contains(url, "/"+documentRoot), ""
}
重點來了取逾,定義啟動方法 和 ServeHTTP
// 啟動服務
func (app *application) Start(bindPort string) {
// 使用自定義 handler
err := http.ListenAndServe(bindPort, app)
if err != nil {
// 服務器創(chuàng)建失敗
panic("服務器創(chuàng)建失敗")
}
}
// 將此方法綁定到應用程序結構
// Handler 方法結構 { ServeHTTP(ResponseWriter, *Request) }
func (app *application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 獲取訪問的url
path := r.URL.Path
// 如果是靜態(tài)路徑的話庆械,返回靜態(tài)資源給客戶端
isFile, redirectPath := isDocumentRoot(path)
if isFile == true {
if redirectPath != "" {
// set the redirect address and modify the original request address
r.URL.Path = redirectPath
}
http.StripPrefix("/"+documentRoot+"/", http.FileServer(http.Dir(documentRoot))).ServeHTTP(w, r)
return
}
// 如果訪問路徑不在白名單里,那么久驗證權限
if app.while[path] != true {
// 這里去驗證一下公共的東西菌赖,比如用戶是否登錄缭乘,是否有訪問路徑的權限
message, err := controller.PermissionCheck(w, r)
// 驗證不通過,返回失敗消息
if err != nil {
w.Write([]byte(message))
return
}
}
if _, function := app.routes[path]; function {
// 返回數(shù)據(jù)給客戶端
w.Write([]byte(app.routes[path](w, r)))
return
}
// 404 未找到用戶訪問的地址
w.Write([]byte(NotFind(w, r)))
return
}
main方法調(diào)用
func main() {
app := Create()
app.Router("/test", Test)
app.Router("/white", WhiteTest)
app.Exclude("/white")
app.Start("0.0.0.0:8000")
}
func Test(w http.ResponseWriter, r *http.Request) string {
return "我是測試"
}
func WhiteTest(w http.ResponseWriter, r *http.Request) string {
return "我白名單方法"
}
func PermissionCheck(w http.ResponseWriter, r *http.Request) (string, error) {
// 我是公共驗證權限的,每個不在白名單中的請求都會訪問到我
return "不允許通過", errors.New("不允許通過")
}
調(diào)用結果展示,未加入白名單的方法被公共方法攔截琉用,而白名單方法不受影響
完整代碼
package main
import (
"errors"
"net/http"
"strings"
)
// 定義結構堕绩,對這個結構綁定方法就可以創(chuàng)建屬于自己的http server
type application struct {
// 路由部分,其中保存訪問路徑與方法的對應關系
routes map[string]func(http.ResponseWriter, *http.Request) string
// 白名單部分邑时,在白名單中的路徑我們不去驗證用戶的相關權限
while map[string]bool
}
// 創(chuàng)建application
func Create() *application {
return &application{
routes: make(map[string]func(http.ResponseWriter, *http.Request) string),
while: make(map[string]bool),
}
}
func (app *application) Router(path string, controller func(http.ResponseWriter, *http.Request) string) {
app.routes[path] = controller
}
// 在這個變量中的路徑奴紧,我們不去驗證權限
func (app *application) Exclude(path string) {
app.while[path] = true
}
const documentRoot = "public"
func isDocumentRoot(url string) (bool, string) {
// 定義用戶訪問 / 根目錄的話 跳轉到靜態(tài)目錄
if url == "/" {
return true, "/" + documentRoot + "/"
}
// 判斷訪問的路徑是否是靜文件地址
return strings.Contains(url, "/"+documentRoot), ""
}
// 啟動服務
func (app *application) Start(bindPort string) {
// 使用自定義 handler
err := http.ListenAndServe(bindPort, app)
if err != nil {
// 服務器創(chuàng)建失敗
panic("服務器創(chuàng)建失敗")
}
}
// 將此方法綁定到應用程序結構
// Handler 方法結構 { ServeHTTP(ResponseWriter, *Request) }
func (app *application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 獲取訪問的url
path := r.URL.Path
// 如果是靜態(tài)路徑的話,返回靜態(tài)資源給客戶端
isFile, redirectPath := isDocumentRoot(path)
if isFile == true {
if redirectPath != "" {
// set the redirect address and modify the original request address
r.URL.Path = redirectPath
}
http.StripPrefix("/"+documentRoot+"/", http.FileServer(http.Dir(documentRoot))).ServeHTTP(w, r)
return
}
// 如果訪問路徑不在白名單里晶丘,那么久驗證權限
if app.while[path] != true {
// 這里去驗證一下公共的東西黍氮,比如用戶是否登錄,是否有訪問路徑的權限
message, err := PermissionCheck(w, r)
// 驗證不通過,返回失敗消息
if err != nil {
w.Write([]byte(message))
return
}
}
if _, function := app.routes[path]; function {
// 返回數(shù)據(jù)給客戶端
w.Write([]byte(app.routes[path](w, r)))
return
}
// 404 未找到用戶訪問的地址
w.Write([]byte(NotFind(w, r)))
return
}
func main() {
app := Create()
app.Router("/test", Test)
app.Router("/white", WhiteTest)
app.Exclude("/white")
app.Start("0.0.0.0:8000")
}
func Test(w http.ResponseWriter, r *http.Request) string {
return "我是測試"
}
func WhiteTest(w http.ResponseWriter, r *http.Request) string {
return "我白名單方法"
}
func PermissionCheck(w http.ResponseWriter, r *http.Request) (string, error) {
// 我是公共驗證權限的浅浮,每個不在白名單中的請求都會訪問到我
return "不允許通過", errors.New("不允許通過")
}
// 404 未找到
func NotFind(w http.ResponseWriter, r *http.Request) string {
return "未找到訪問的內(nèi)容"
}
這時候我們就可以很輕松的對每個訪問都進行統(tǒng)一的處理不需要重復調(diào)用摸一個方法,完成如用戶的登錄校驗沫浆,權限校驗