golang創(chuàng)建屬于自己的HttpServer,統(tǒng)一的權(quán)限驗證

場景說明
  • go 中使用官方的http server方法的話,缺少統(tǒng)一的方法調(diào)用,無法對用戶的權(quán)限等進(jìn)行統(tǒng)一的驗證
  • http.HandleFunc("/ws", wsHandler) 官方的這種路由方式,無法靈活的進(jìn)行應(yīng)用
  • php 中可以使用 __construct 對訪問的方法進(jìn)行統(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"))
}

瀏覽器訪問

這時候如果這兩個方法都要調(diào)用一個公共方法進(jìn)行權(quán)限驗證的話都需要在每個方法內(nèi)部進(jìn)行調(diào)用
這樣在方法越來越多的情況下,會造成非常嚴(yán)重的代碼冗余和難以維護(hù)

解決方法,重寫官方的方法

創(chuàng)建結(jié)構(gòu)

// 定義結(jié)構(gòu),對這個結(jié)構(gòu)綁定方法就可以創(chuàng)建屬于自己的http server
type application struct {
    // 路由部分,其中保存訪問路徑與方法的對應(yīng)關(guān)系
    routes map[string]func(http.ResponseWriter, *http.Request) string
    // 白名單部分,在白名單中的路徑我們不去驗證用戶的相關(guān)權(quán)限
    while map[string]bool
}

定義創(chuàng)建這個結(jié)構(gòu)的方法

// 創(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)建白名單的方法

// 在這個變量中的路徑,我們不去驗證權(quán)限
func (app *application) Exclude(path string) {
    app.while[path] = true
}

定義靜態(tài)文件目錄,注意這個目錄是以當(dāng)前項目路徑為根目錄

const documentRoot = "public"
func isDocumentRoot(url string) (bool, string) {
    // 定義用戶訪問 / 根目錄的話 跳轉(zhuǎn)到靜態(tài)目錄
    if url == "/" {
        return true, "/" + documentRoot + "/"
    }
    // 判斷訪問的路徑是否是靜文件地址
    return strings.Contains(url, "/"+documentRoot), ""
}

重點來了,定義啟動方法 和 ServeHTTP

// 啟動服務(wù)
func (app *application) Start(bindPort string) {
    // 使用自定義 handler
    err := http.ListenAndServe(bindPort, app)
    if err != nil {
        // 服務(wù)器創(chuàng)建失敗
        panic("服務(wù)器創(chuàng)建失敗")
    }
}

// 將此方法綁定到應(yīng)用程序結(jié)構(gòu)
// Handler 方法結(jié)構(gòu) {  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
    }

    // 如果訪問路徑不在白名單里,那么久驗證權(quán)限
    if app.while[path] != true {
        // 這里去驗證一下公共的東西,比如用戶是否登錄,是否有訪問路徑的權(quán)限
        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) {
    // 我是公共驗證權(quán)限的,每個不在白名單中的請求都會訪問到我
    return "不允許通過", errors.New("不允許通過")
}

調(diào)用結(jié)果展示,未加入白名單的方法被公共方法攔截,而白名單方法不受影響

image.png

完整代碼

package main

import (
    "errors"
    "net/http"
    "strings"
)

// 定義結(jié)構(gòu),對這個結(jié)構(gòu)綁定方法就可以創(chuàng)建屬于自己的http server
type application struct {
    // 路由部分,其中保存訪問路徑與方法的對應(yīng)關(guān)系
    routes map[string]func(http.ResponseWriter, *http.Request) string
    // 白名單部分,在白名單中的路徑我們不去驗證用戶的相關(guān)權(quán)限
    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
}

// 在這個變量中的路徑,我們不去驗證權(quán)限
func (app *application) Exclude(path string) {
    app.while[path] = true
}

const documentRoot = "public"

func isDocumentRoot(url string) (bool, string) {
    // 定義用戶訪問 / 根目錄的話 跳轉(zhuǎn)到靜態(tài)目錄
    if url == "/" {
        return true, "/" + documentRoot + "/"
    }
    // 判斷訪問的路徑是否是靜文件地址
    return strings.Contains(url, "/"+documentRoot), ""
}

// 啟動服務(wù)
func (app *application) Start(bindPort string) {
    // 使用自定義 handler
    err := http.ListenAndServe(bindPort, app)
    if err != nil {
        // 服務(wù)器創(chuàng)建失敗
        panic("服務(wù)器創(chuàng)建失敗")
    }
}

// 將此方法綁定到應(yīng)用程序結(jié)構(gòu)
// Handler 方法結(jié)構(gòu) {  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
    }

    // 如果訪問路徑不在白名單里,那么久驗證權(quán)限
    if app.while[path] != true {
        // 這里去驗證一下公共的東西,比如用戶是否登錄,是否有訪問路徑的權(quán)限
        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) {
    // 我是公共驗證權(quán)限的,每個不在白名單中的請求都會訪問到我
    return "不允許通過", errors.New("不允許通過")
}

// 404 未找到
func NotFind(w http.ResponseWriter, r *http.Request) string {
    return "未找到訪問的內(nèi)容"
}

這時候我們就可以很輕松的對每個訪問都進(jìn)行統(tǒng)一的處理不需要重復(fù)調(diào)用摸一個方法,完成如用戶的登錄校驗,權(quán)限校驗

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,667評論 1 32
  • 因為要結(jié)局swift3.0中引用snapKit的問題,看到一篇介紹Xcode8,swift3變化的文章,覺得很詳細(xì)...
    uniapp閱讀 4,872評論 0 12
  • 《名人傳》讀后感 大寨一中 高元節(jié) 本書作者是法國...
    元元有話說閱讀 241評論 0 0
  • 日子帶了些秋天的味道,慵懶愜意。 躺在安靜的小屋,聆聽時光滴答滴答前行的步音,任由心緩緩舒展,流露出靜靜的歡喜。立...
    一只來自霍爾沃滋的鯨魚閱讀 431評論 0 0
  • 淺水灣里沉睡著張乃瑩 呼蘭河里埋著她的祖父 當(dāng)剛讀完矛盾所寫的序言 我感受到了她當(dāng)時的內(nèi)心 她寫呼蘭河傳時內(nèi)心無疑...
    珞珈_3667閱讀 342評論 0 0

友情鏈接更多精彩內(nèi)容