gorrilla Context包深入学习

本文详细介绍了Gorrilla Context包的安装、使用方法以及底层原理,包括设置、获取、删除和清理等功能。通过源码剖析,展示了如何在Go web开发中安全地存储和管理key-value形式的数据,同时提到了Go 1.7后内置的Context包的使用。文章还推荐了其他相关学习资源。

导语

做过web开发的同学肯定都知道,我们经常使用 r *http.Request 这个变量来获取我们希望获得的参数,但是我们经常遇到这样一个场景,我们需要为我们的r设置更多的key-value形式的附加值,一般我们都会存储在一个Map对象中,用的时候再从里面取出,Golang考虑到这样一个场景,为我们提供了一个context,这里的context并不是上下文的意思,属于另外一个这个包github.com/gorilla/context

安装

使用这个库之前,我们同样用Golang中的经典获取包的方式go get命令来获取

go get github.com/gorilla/context

使用方法

设置
context.Set(r, "user", "wuyazi")
context.Set(r, "age", 21)

在上面的这个函数里,r即是*http.Request我们设置了一个key为user,value为wuyazi,另一个key为age,value为21。

获取
user := context.Get(r, "user").(string)
age := context.Get(r, "age").(int)

获取我们使用一个Get()的函数,该函数会返回一个interface{}类型的值,然后我们使用断言,进行类型转换,就能得到我们设置的数据了。

底层原理学习——源码剖析

Set()函数

这个函数接收了3个参数,这3个参数见名知意,这里不再过多介绍,在这个函数里,我们首先可以看到一个关于写入的锁来保证数据的安全性,接着判断我们传入的r是否已经存在了我们的data变量里。这里的data是一个双层嵌套的map,定义如下所示
make(map[*http.Request]map[interface{}]interface{})interface{}类型则表示了我们可以存入各种类型的数据,如果判断data[r]没有值,则为它开辟一个map,如果有值则直接存入,最后释放写入锁。
在此函数中我们同样看到了一个变量datat,datat是用来保存每个r对应map的声明周期,下面会讲到。

func Set(r *http.Request, key, val interface{}) {
	mutex.Lock()
	if data[r] == nil {
		data[r] = make(map[interface{}]interface{})
		datat[r] = time.Now().Unix()
	}
	data[r][key] = val
	mutex.Unlock()
}
Get()函数

同样在这个函数中我们用了一个读的锁,来保证读的安全性,里面的逻辑很简单,基本上和操作map一应,有值返回值,没有值则返回nil。

func Get(r *http.Request, key interface{}) interface{} {
	mutex.RLock()
	if ctx := data[r]; ctx != nil {
		value := ctx[key]
		mutex.RUnlock()
		return value
	}
	mutex.RUnlock()
	return nil
}
GetOk()函数

GetOk函数主要用于判断key是否存在于map对象中,但是为什么会有这个函数,可能有的同学会问,直接判断value是否为nil不就行了,其实这里需要注意的是,nil值同样可以当做value存储在map对象里,因此有这样一个函数是有必要的,这里面函数的逻辑同样和操作map类似,用golang中特有的map对象判断value的方式ok来判断是否有值,如果key不存在将会返回nil,false

func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
	mutex.RLock()
	if _, ok := data[r]; ok {
		value, ok := data[r][key]
		mutex.RUnlock()
		return value, ok
	}
	mutex.RUnlock()
	return nil, false
}
GetAll()函数

这个函数是用来获取所有的键值对,同样用上面的例子使用方法如下:

allParams:=context.GetAll(r)
user:=allParams["user"].(string)
age:=allParams["age"].(int)

首先获得map对象,然后使用断言,比较简单。在源码中,需要注意的是在进行map对象返回的时候,我们新创建了一个和context大小一样的map对象,用来进行map对象的copy,为什么用这样的方法,因为原来的context是一个引用的类型,如果返回引用类型,调用者可能会破坏原来的map结构,因此返回一个map的copy保证的map的安全性。我们在实际开发中同样应该考虑到这一点。这样我们的代码才能更加具有健壮性。

func GetAll(r *http.Request) map[interface{}]interface{} {
	mutex.RLock()
	if context, ok := data[r]; ok {
		result := make(map[interface{}]interface{}, len(context))
		for k, v := range context {
			result[k] = v
		}
		mutex.RUnlock()
		return result
	}
	mutex.RUnlock()
	return nil
}
Delete()和Clear()函数

这两个函数的实现原理和map对象相同,比较简单,不在多阐述,源码如下:

func Delete(r *http.Request, key interface{}) {
	mutex.Lock()
	if data[r] != nil {
		delete(data[r], key)
	}
	mutex.Unlock()
}

func Clear(r *http.Request) {
	mutex.Lock()
	clear(r)
	mutex.Unlock()
}

func clear(r *http.Request) {
	delete(data, r)
	delete(datat, r)
}
Purge()函数

上面我们提到了一个datat的变量,在这个函数中我们就用到了,用它来对我们的key-value进行可控的清理,如果maxAge<0则重新进行创建map,实现清理所有,如果当前时间-maxAge的值大于创建的时间戳,则清理掉数据。

func Purge(maxAge int) int {
	mutex.Lock()
	count := 0
	if maxAge <= 0 {
		count = len(data)
		data = make(map[*http.Request]map[interface{}]interface{})
		datat = make(map[*http.Request]int64)
	} else {
		min := time.Now().Unix() - int64(maxAge)
		for r := range data {
			if datat[r] < min {
				clear(r)
				count++
			}
		}
	}
	mutex.Unlock()
	return count
}
ClearHandler()函数

上面讲到了手动清理,这里则是自动清理,因为在我们进行开发时,随着key-value的增加,我们大多数情况下会忘记清理,因此我们可以使用这个函数。函数的逻辑同样很简单,用Golang提供的defer机制,当这个函数结束之前调用defer延迟,进行清理。

func ClearHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer Clear(r)
		h.ServeHTTP(w, r)
	})
}

最后

以上的包是在Go1.7引入之前经常使用,在1.7引入后,自带的Context包,即之前讲过的上下文能够很好的进行代替使用方法如下

设置数据

userContext:=context.WithValue(context.Background(),"user","wuyazi")
ageContext:=context.WithValue(userContext,"age",21)
rContext:=r.WithContext(ageContext)

获得数据

user:=r.Context().Value("user").(string)
age:=r.Context().Value("age").(int)

推荐阅读


本文欢迎转载,转载请联系作者,谢谢!
公众号【常更新】:无崖子天下无敌
GitHub:https://github.com/yuwe1
博客地址【不定期更新】:https://yuwe1.github.io/


打开微信扫一扫,关注微信公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值