Go 实现简单的请求路由和中间件框架

go 中区分函数和方法,方法依附于对象,需要先创建对象,才能调用对象的方法;而函数是包级的,只要是公开的,那么通过包就可以访问。go 中定义新的类型有两种方式,类型别名和结构体:

// 类型别名
type Integer int
type Integer1 = int

// 结构体
type User struct {
    Name string
    Age  int
}

此外,类型别名不仅可以用在现有类型上,也可以用在方法上:

type Middleware func(handler http.Handler) http.Handler

请求路由

在 Web 框架中,router 是必备的组件,go 的 http 标准库为我们提供了 DefaultServeMux 来处理简单的路由,因此用 go 起步写一个简单的 web 服务是很容易的一件事情:

package main
import (
	"log"
	"net/http"
)

func init() {
	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		// handle request
	})
}

func main() {
	log.Println("Listening on port 8080")
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

如果希望使用我们自己实现的路由组件来分发请求,只需将默认的DefaultServeMux替换成我们自己的,http 包中定义了 Handler 接口来统一路由处理的入口:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

自定义Handler可以在ListenAndServer方法中传入。下面的示例中 app.Router 对象所属的结构体实现了ServeHTTP(ResponseWriter, *Request)方法:

func main() {
	log.Println("Listening on port 8080")
	if err := http.ListenAndServe(":8080", app.Router); err != nil {
		log.Fatal(err)
	}
}

来看下 app.Router 的实现:
在这里插入图片描述
这里只是简单的用 map 来保存 URL 和 Handler 的对应关系,使用正则来进行 url 和 route 的匹配检测。在 ListenAndServer 方法中 app.Router 已经替代了默认的DefaultServeMux,这意味着所有的请求都会首先进入 Router 的 ServeHTTP 方法中,在该方法中再根据 URL 路径找到最终的匹配的 Handler,并把请求分发给它。

URL 和其匹配 Handler 的注册在 Router 的 Path 方法中完成。

中间件

中间件主要用来分离业务代码和非业务代码,典型的需求是日志记录,请求耗时,对请求和响应进行统一处理(如压缩)等。中间件核心功能的实现在于其能在请求被最终 Handler 处理之前,以及请求被 Handler 处理之后收到通知,在一个请求生命周期的起点和终点,这两个端点上处理非业务相关的需求。

在之前请求路由的简单实现中可以看到在请求发送到最终 Handler 之前,首先到达的是 app.Router 的 ServeHTTP 方法,在该方法中会找到最终的 Handler,并调用其 ServeHTTP 方法,那么中间件需要做的就是如下的事情:


func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	handled := false
	for route, handler := range r.mux {
		// ...
		if matched {
			handled = true
			// 中间件逻辑: 请求被最终 Handler 处理前
			handler.ServeHTTP(w, req)
			// 中间件逻辑: 请求被最终 Handler 处理后
			break
		}
	}

	if !handled {
		log.Println("ERROR: no handler find: ", req.URL.Path)
	}
}

上面的写法很类似 AOP 的写法,然而在 go 中,得益于其语法特性,可以有更优雅的写法:

func Log(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Println("mw in log start")
		next.ServeHTTP(w, r)
		log.Println("mw in log end")
	})
}

上面的 Log 函数即为一个中间件的具体逻辑,在 Log 方法中,入参 next 可先暂时认为是最终的 Handler,而返回值同样也是 Handler 对象,Log 方法所做的事情就是: 对 next 进行包装,加入自己的逻辑,实现中间件的功能,之后再调用正真 Handler 的 ServeHTTP 方法。

http.HandlerFunc 类型定义如下

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

可见类型 HandlerFunc 是函数 func(ResponseWriter, *Request) 的别名,而且实现了 ServeHTTP(w ResponseWriter, r *Request)方法。也就是说 http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})func(w http.ResponseWriter, r *http.Request) {}包装为一个 Handler 对象,中间件的逻辑在 HandlerFunc 中实现。

那么对于所有需要用到的中间件我们都可以用类似的方法套在最终 Handler 外面。在 Router 的 Path 方法中最终 Handler 会被实例化,我们的包装过程就在这里进行。
在这里插入图片描述
中间件一般不会只有一个,因此应该设计为链式调用的方式。Router 的 Use 方法用于添加新的中间件到路由组件中,而 Path 方法会把中间件倒序套在最终 Handler 外面。

使用时只需按如下的方式调用 Use 方法。

func init() {
	Router.Use(middleware.Log)
	Router.Use(middleware.Cost)
}

var (
	Router = duan.NewRouter()
)

完整代码可以在–这里–找到。

展开阅读全文

有没有办法用组路由器去中间件路由器?

09-10
<div class="post-text" itemprop="text"> <p>I am beginner in beego framework, I have completed few R&D inside on it. But I need few helps related routers.</p> <p>I have created few route with middleware and group router but I need few suggestions from expert.</p> <p>Let me share example which I did.</p> <p><strong>Router.go</strong></p> <pre><code>func init() { ns := beego.NewNamespace("/api/v1", beego.NSNamespace("/front", beego.NSBefore(AuthFilter), beego.NSRouter("/user",&controllers.ObjectController{},"*:GetValueByAdmin"), beego.NSRouter("/test",&controllers.ObjectController{},"*:GetValueByAdmin"), beego.NSRouter("/test",&controllers.ObjectController{},"*:GetValueByAdmin"), beego.NSRouter("/test",&controllers.ObjectController{},"*:GetValueByAdmin"), beego.NSRouter("/product",&controllers.ObjectController{},"*:GetValueByAdmin"), ), beego.NSNamespace("/a1", beego.NSRouter("/test1",&controllers.ObjectController{},"*:GetValueByAdmin1"), beego.NSRouter("/test2",&controllers.ObjectController{},"*:GetValueByAdmin1"), beego.NSInclude( &controllers.UserController{}, ), ), ) beego.AddNamespace(ns) } var AuthFilter = func(ctx *context.Context) { // The Authorization header should come in this format: Bearer <jwt> // The first thing we do is check that the JWT exists header := strings.Split(ctx.Input.Header("Authorization"), " ") if header[0] != "Bearer" { ctx.Abort(401, "Not authorized") } } </code></pre> <p>I have created router using Namespace and It is working fine using this url (<a href="http://localhost:8080/api/v1/front/test" rel="nofollow noreferrer">http://localhost:8080/api/v1/front/test</a>). But I want to remove "front" keyword from URL.</p> <p><strong>I tried below options like:</strong></p> <ol> <li><p>I copied code inside "Front" namespace to put outside but My "NSBefore" Will apply all the method which is defined after that. I need 2 group. Before auth and after auth. In after auth, I want to add <code>beego.NSBefore(AuthFilter)</code>.</p></li> <li><p>I tried using policy but it will not work as I needed.</p> <p><code>beego.Policy("/api/v1/front/*","*", AuthFilter)</code> <code>beego.Policy("/api/v1/admin/*","*", AuthFilter)</code></p></li> </ol> <p>If I will remove front from policy then it will apply all the URL.</p> <p>Do we have any option to create group router without URL path and it will cover my concept?</p> </div>
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值