场景说明
  • go 中使用官方的http server方法的话,缺少统一的方法调用,无法对用户的权限等进行统一的验证
  • http.HandleFunc("/ws", wsHandler) 官方的这种路由方式,无法灵活的进行应用
  • php 中可以使用 __construct 对访问的方法进行统一的验证,而直接使用go的官方方法并没有类似的
创建简单的 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

这时候如果这两个方法都要调用一个公共方法进行权限验证的话都需要在每个方法内部进行调用
这样在方法越来越多的情况下,会造成非常严重的代码冗余和难以维护

解决方法,重写官方的方法

创建结构

// 定义结构,对这个结构绑定方法就可以创建属于自己的http server
type application struct {
	// 路由部分,其中保存访问路径与方法的对应关系
	routes map[string]func(http.ResponseWriter, *http.Request) string
	// 白名单部分,在白名单中的路径我们不去验证用户的相关权限
	while map[string]bool
}

定义创建这个结构的方法

// 创建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) {
	// 定义用户访问 / 根目录的话 跳转到静态目录
	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 {
	    // 服务器创建失败
		panic("服务器创建失败")
	}
}

// 将此方法绑定到应用程序结构
// Handler 方法结构 {  ServeHTTP(ResponseWriter, *Request) }
func (app *application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 获取访问的url
	path := r.URL.Path
	// 如果是静态路径的话,返回静态资源给客户端
	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 {
		// 返回数据给客户端
		w.Write([]byte(app.routes[path](w, r)))
		return
	}
	// 404 未找到用户访问的地址
	w.Write([]byte(NotFind(w, r)))
	return
}

main方法调用

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("不允许通过")
}

调用结果展示,未加入白名单的方法被公共方法拦截,而白名单方法不受影响

完整代码

package main

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

// 定义结构,对这个结构绑定方法就可以创建属于自己的http server
type application struct {
	// 路由部分,其中保存访问路径与方法的对应关系
	routes map[string]func(http.ResponseWriter, *http.Request) string
	// 白名单部分,在白名单中的路径我们不去验证用户的相关权限
	while map[string]bool
}

// 创建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) {
	// 定义用户访问 / 根目录的话 跳转到静态目录
	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 {
		// 服务器创建失败
		panic("服务器创建失败")
	}
}

// 将此方法绑定到应用程序结构
// Handler 方法结构 {  ServeHTTP(ResponseWriter, *Request) }
func (app *application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 获取访问的url
	path := r.URL.Path
	// 如果是静态路径的话,返回静态资源给客户端
	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 {
		// 返回数据给客户端
		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 "未找到访问的内容"
}

这时候我们就可以很轻松的对每个访问都进行统一的处理不需要重复调用摸一个方法,完成如用户的登录校验,权限校验