YOLO813

Django初学(9)- Django操作cookie和session

    先介绍一下django操作memcached,因为接下来项目中的session数据存储将会使用到memcached。之前在memcached和redis的初步了解简单介绍了下memcached的安装和使用(那篇文章中还有一个点需要强调,如果想要别的机器能够连接的话,除了防火墙和阿里云服务器安全组放行,启动时应该加入-l 0.0.0.0)。

    memcached的使用场景:存储验证码(图形、短信验证码),登录session等不是至关重要的数据。当然并不是说一定要使用memcached才能制作这些功能,只是这样更优,做和做好还是有区别的。在内存中操作的优点:快速而且可以节省服务器的开销。顺便说一下,windows下如何查看memcached是否启动(我的memcached <1.4.5 版本),鼠标拖到最下方任务栏->右键->任务管理器->进程那一行最后一个tab栏服务->查找名称为memcached的状态是否为正在运行。

    上一次是使用telnet来操作memcached,这次简单写一下python如何操作memcached,以及Django中如何操作memcached。

python如何操作memcached

    安装python-memcached包,在python文件中import memcache,具体的使用方法,我们可以右键点击memcache的“转到定义”查看,

我们也可以顺着在里面看下set方法的定义,默认time==0,则cache forever,这些也是需要注意的。

    之前我们介绍过memcached是一个分布式内存对象缓存系统,现在我们可以测试一下它的功能了,我分别在windows电脑和阿里云Linux服务器上面安装了memcached,并启动它们,并清空里面所有的缓存记录,以红色线分割,windows电脑(左)和Linux(右)分别用stats items查看均没有了记录

    在django的视图文件中,通过Client指定本机IP和Linux服务器公网ip地址,再用set设置值至缓存中,下图

完成之后,我再在本机和云服务器的memcached中取值,测试结果如下:

可以很清楚的看见,每个值都set成功了,不是在本机的memcached上就是在阿里云服务器上的memcached上,可谓分布式。

    另外,关于memcached的安全机制的一些考虑,经过测试,当我们配置了云服务器安全组的端口(假设为11211),服务器的防火墙firewall-cmd放行了11211端口,以-l 0.0.0.0参数启动,那么此时memcached是对外公开的,很危险。有两种解决方法,一种是网站规模不大,就将memcached布置在本机服务器上,始终只有本机能够访问;另外一种还是放行端口,但是在阿里云安全组配置中设置只有允许的IP才能访问端口,因为云服务器的公网IP不像局域网的IP会经常变化(就我所知)。


Django如何操作memcached

    我们可以用python操作memcached的方法,也可以将memcached设置成Django中的缓存,以Django的方式来操作,当在settings.py配置了CACHES之后,以后使用django.core.cache.cache时,Django会默认将数据保存在相对应的缓存系统中。

#单台机器
CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': '127.0.0.1:11211',
        #     'LOCATION': [
        #    # 设置权重
        #    ('172.19.26.240:11211',10),
        #    ('172.19.26.242:11211',20),
        #     ]
        }
}

在views视图层中,导入django.core.cache.cache,这个底层的源码可以通过django.core.cache.backends.memcached.MemcachedCache翻阅:

from django.core.cache import cache
def index2(req):
    cache.set('username', 'zhangxaiofei', timeout = 100)
    return HttpResponse('memcached')

    可以看到存到memcached是这么一个东西,下图 :1:username,当然这个并不影响我们在django项目中使用cache.get('username')来取值,只是django在调用set方法时,在底层会使用一个make_key方法,加上 空格:版本号:的形式,具体的源码在MemcachedCache中可以翻阅

    如何自定义memcached中数据的键key?改写底层的KEY_FUNCTION参数,settings.py中加入如下代码:

CACHES = {
        'default': {
            ...
            'KEY_FUNCTION': lambda key,key_prefix,version : "django:"+ key
        },
}

存到memcached中的值修改成功,下图

    现在进入正题,关于cookie和session简单说下,cookie的出现是为了解决http的无状态,一般小于4KB,浏览器将其保存在本地;session也是为了存储用户相关信息,但是session是一个概念,一个服务器存储授权信息的解决方案,不同的程序语言有不同的实现方法,但目的都是为了服务器方便存储数据。session的出现是为了解决cookie存储数据不安全(即其基于cookie)的问题。


Django如何操作Cookie

    首先我们需要知道的是,cookie是设置给浏览器的,之前我在Django初学(4)- 视图写过视图层的response对象和request对象,所以我们应该在response上面来设置cookie,在request对象上面来获取cookie。我们可以翻阅HttpResponseBase源码里面有set_cookie和delete_cookie方法,看的更仔细一点,可以看到delete_cookie其实也是set_cookie,只不过把expires设置成了一个过期时间。

    设置cookie视图层函数示例:

def index(req):
    response = HttpResponse('cookie')
    response.set_cookie(key='username', value='zhangxiaofei',max_age=120,path='cook/')
    return response

    set_cookie方法中,主要有两个参数容易混淆,分别是path 和domain,path 是对域名下哪个路径有效,默认是根域名下所有路径,上面我针对的是cook/路径下的路径才可有此缓存;domain针对的是哪个域名,如果需要单独配置某个子域名下的缓存可以使用此参数。

    expires参数主要是需要注意使用aware time,否则时间上会出现8个小时的误差。与max_age同时使用时以expires为准,推荐使用max_age,可以看下底层源码。

    httponly参数如果为True,则客户端不能通过JavaScript操作cookie。

    secure如果为True,那么只能在https协议下才可用。

    

    我们获取cookie是从浏览器获取,所以使用的是request对象。获取cookie视图层函数示例如下:

def get_cookie(req):
    res = req.COOKIES.get('username')
    return HttpResponse('get_cookie')

    如果需要删除cookie,调用delete_cookie函数即可,但是我在测试中发现,有一点需要注意,如果set_cookie中设置了path,那么删除时需设置对应的path,否则删除无效。


Django如何操作Session

    当我们在django项目中执行python manage.py migrate时,django项目中会自动在数据库里面创建几张表,其中的django_session表就是来默认存储session相关数据的。由于我们需要将sessionid存放在cookie当中,所以session的操作也是在request对象上进行的。session数据一般分为key,data和expire_date,其中key保存在浏览器的的cookie当中,实验如下:

# views.py
def index(req):
    req.session['username'] = 'zhangxaiofei'
    return HttpResponse('session!')

django_session表当中存储的数据经过加密如下,默认过期时间为两周

浏览器中,我们打开相关的cookie设置,可以看到sessionid的内容和我们django_session表当中的session_key的值一模一样

    当然,我们并非一定要将session数据存储在数据库,也可以存放在客户端浏览器的cookie当中,但是需要做详细的加密和控制session大小(cookie大小不能超过4KB),这样可以减少服务器的开销。

    我们可以将req.session的类型打印出来看下

    session常见的方法有get,pop,clear(这个方法只会将相对应sessionid的data设置为空,但是不会删除该条数据,代码方法释义如下:

req.session.clear()

而且,即使你同时设置了req.session['username']='zhangxaiofei'和req.session['pwd']='123',django_session表也只有一条数据,不要惊讶,session数据还是可以正常取出来的。


    flush方法,删除该条sessionid的记录,底层源码

    set_expiry,设置过期时间

    clear_expired,清理过期的session,django不会清除默认的session,需定期手动清理或者在终端运行python manage.py clearsessions进行清除。如果在终端测试clearsessions命令,请记得为session设置过期时间,这条命令是用来清理过期的session,如果没有过期,是不会清理的,测试了半天没过期的session,懵逼!


修改Session的存储机制

    上面介绍了django默认将会话session存储在数据库中,每次浏览器提交cookie的时候都要从数据库里面取值,会增加服务器的开销。django也提供其它几种方式来保存会话。

    以下只介绍一种,多余的可以透过下方的参考文档翻阅。

    settings.py中配置SESSION_ENGINE为...backends.cached_db,即使用缓存+数据库的形式来存储会话,这样即使内存崩溃也可以从数据库里面取到正确的session数据。前提是已经正确配置了缓存系统CACHES ,例如memcached。

SESSION_ENGINE='django.contrib.sessions.backends.cached_db'

    视图层存入session,memcached存入的数据如下:

    可以很清楚的看到存入的session键值相同。

参考文档:

https://docs.djangoproject.com/zh-hans/3.1/topics/http/sessions/#using-database-backed-sessions
https://docs.djangoproject.com/zh-hans/3.1/topics/cache/