Redis+Django(Session,Cookie、Cache)的用戶系統
轉自 http://www.cnblogs.com/BeginMan/p/3890761.html
一.Django authentication
django authentication 提供了一個便利的user api接口,無論在py中 request.user
,參見 Request and response objects .還是模板中的 {{user}}
都能隨時隨地使用,如果從web開發角度來看,其實無非就是cookie與session的運用.
在項目首頁,在登陸和註銷狀態下分別輸出所有session,如:
print request.session.items()
# 登陸狀態下輸出
[(‘domain‘, ‘http://beginman.sinaapp.com‘), (‘_auth_user_backend‘, ‘django.contrib.auth.backends.ModelBackend‘), (‘_auth_user_id‘, 1L)]
# 註銷狀態下輸出
[(‘domain‘, ‘http://beginman.sinaapp.com‘)]
從輸出結果中可知曉,如果項目中settings.py配置如下:
#中間件
MIDDLEWARE_CLASSES = (
‘django.middleware.common.CommonMiddleware‘,
‘django.contrib.sessions.middleware.SessionMiddleware‘, #看這裏
# ‘django.middleware.csrf.CsrfViewMiddleware‘,
‘django.contrib.auth.middleware.AuthenticationMiddleware‘, #看這裏
‘django.contrib.messages.middleware.MessageMiddleware‘,
‘django.middleware.gzip.GZipMiddleware‘, # 處理gzip壓縮,減輕服務器壓力
‘pagination.middleware.PaginationMiddleware‘, # django 第三方分頁
‘common.mymiddleware.Mymiddleware‘,
# Uncomment the next line for simple clickjacking protection:
# ‘django.middleware.clickjacking.XFrameOptionsMiddleware‘,
)
#TEMPLATE_CONTEXT_PROCESSORS
# 註意django1.5的這個玩意兒與低版本的不同
# 參考:https://docs.djangoproject.com/en/1.3/ref/settings/#std:setting-TEMPLATE_CONTEXT_PROCESSORS
TEMPLATE_CONTEXT_PROCESSORS = (
"django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.core.context_processors.request",
"django.contrib.messages.context_processors.messages"
)
INSTALLED_APPS = (
‘django.contrib.auth‘, #this
‘django.contrib.contenttypes‘, #this
‘django.contrib.sessions‘, #this
‘django.contrib.sites‘,
‘django.contrib.messages‘,
‘django.contrib.staticfiles‘,
# Uncomment the next line to enable the admin:
‘django.contrib.admin‘,
# Uncomment the next line to enable admin documentation:
# ‘django.contrib.admindocs‘,
‘mysite‘,
)
那麽就會有這樣的輸出,當然這只是針對單用戶(這裏拿我的博客開刀).在登陸後同時在首頁輸出cookies:
print request.COOKIES
{‘csrftoken‘: ‘9fBE9Kh0uuzXEMzWdc4z4aIOoZg1EaoI‘, ‘sessionid‘: ‘lf4dd7xjlyzrh4yvzbtltlbujy3ipp1f‘, ‘Hm_lvt_c65358e73ce306691a49ae5119f58783‘: ‘1405408338‘}
登陸成功後Django會自動在客戶端生成一個sessionid,且這個sessionid在未註銷前一直不變,當註銷後就改變了該sessionid了. Cookie中保存一個Session的索引編號(sessionid),其重要信息都保存在服務器端,Session控制.即可.
通過Cookie保存的sessionid與服務器端比較,當等於時則表示用戶已登陸,若不等於或兩者有一方不存在或都不存在則用戶處於註銷狀態.
二.Session 與Cookie
圖片來源: http://blog.csdn.net/tzjly/article/details/6986268
cookie機制采用的是在客戶端保持狀態的方案,而session機制采用的是在服務器端保持狀態的方案,由於采用服務器端保持狀態的方案在客戶端也需要保存一個標識,所以session機制可能需要借助於cookie機制來達到保存標識的目的.
關於session和cookie,這裏有篇文章說的很清楚,傳送門.
三.Django對於Cookie的處理方式
每個 HttpRequest
對象都對應一個 COOKIES
對象,該對象是字典形式.
request.COOKIES[‘sessionid‘] # 獲取
request.COOKIES.get(‘sessionid‘, None) # 獲取
對COOKIES的設置通過 HttpResponse
對象的 set_cookie
來完成, 文檔傳送門
HttpResponse.set_cookie(key, value=‘‘, max_age=None, expires=None, path=‘/‘, domain=None, secure=None, httponly=False)
參數如下:
(1).`max_age 默認:None ,cookie需要延續的時間(以秒為單位) 如果參數是\
None“ ,這個cookie會延續到瀏覽器關閉為止。
(2).`expires 默認None ,cookie失效的實際日期/時間。 它的格式必須是:\
“Wdy, DD-Mth-YY HH:MM:SS GMT” 。如果給出了這個參數,它會覆蓋\
max_age“ 參數。
(3). path
默認是 "/"
,cookie生效的路徑前綴。 瀏覽器只會把cookie回傳給帶有該路徑的頁 面,這樣你可以避免將cookie傳給站點中的其他的應用,當你不是控制你的站點的頂層時,這樣做是特別有用的。
(4). domain
默認None,這個cookie有效的站點。 你可以使用這個參數設置一個跨站點(cross-domain)的cookie。 比如,\ domain=".example.com"
可以設置一個在\www.example.com
、\ www2.example.com
以及\an.other.sub.domain.example.com
站點下都可讀到的cookie。
如果這個參數被設成\ None
,cookie將只能在設置它的站點下可以讀到。
(5). False
默認False ,如果設置為 True ,瀏覽器將通過HTTPS來回傳cookie。
四.Django對Session的處理
文檔傳送門 ,
如果想讓django項目支持session,則必須在settings.py中指定,見上. 默認情況django使用 django.contrib.sessions.models.Session將session存儲在你的數據庫中. ,這裏我們查看下,如一個多用戶的網站的django_session表:
mysql> select * from django_session;
+----------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+
| session_key | session_data | expire_date |
+----------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+
| 1x5fxr1upboyiw4ny640tgdl2mto6i6i | NjJkNzBiYjE2NjBlYTBkMGZmY2QzYmIxOGE5MDRmNTE1YTgzM2FmNzqAAn1xAS4= | 2014-07-16 02:21:35 |
..................
很長一大段,這裏省略了.如果在settings.py的 INSTALLED_APPS中添加了django.contrib.sessions
則運行 manage.py syncdb
將會生成數據庫級別的session,表名為django-session.
為了提升性能,我們可以使用緩存級別的session,這點會在後面提及.
1.在views中使用session
每個HttpRequest對象都有session屬性 ,那麽我們可以在views中使用:
fav_color = request.session[‘fav_color‘] #get
request.session[‘fav_color‘] = ‘blue‘ #set
del request.session[‘fav_color‘] #del
‘fav_color‘ in request.session #contains
fav_color = request.session.get(‘fav_color‘, ‘red‘)
fav_color = request.session.pop(‘fav_color‘) # pop
session對象字典形式,有keys(),items(),setdefault(),clear()等方法,以下方法是常用的:
(1). flush()
從session中刪除數據,然後再生數據,比如django的logout()函數就會調用它.
(2). set_test_cookie()
設置一個test cookie來判斷用戶瀏覽器是否接受cookie.
(3). test_cookie_worked()
當設置了test cookie後返回True或Flase判斷用戶瀏覽器是否接受cookie.所以要先執行 set_test_cookie()
來下個套.
(4). delete_test_cookie()
刪除test cookie,測試完成後要收回自己下的套.
(5). set_expiry(value)
設置過期時間,如果value是整數則表示N秒後過期,如果是時間或時間戳對象則表示指定到某一時間過期;如果value是0則在瀏覽器關閉後過期(會話session); 如果value 是None則使用全局session過期策略.
(6). get_expiry_age()
返回session過期時間
def login(request):
if request.method == ‘POST‘:
if request.session.test_cookie_worked(): # 測套
request.session.delete_test_cookie() # 收套
return HttpResponse("You‘re logged in.")
else:
return HttpResponse("Please enable cookies and try again.")
request.session.set_test_cookie() # 下套
return render_to_response(‘foo/login_form.html‘)
2.在模板中使用session
如果在views設置了session,如 request.session[‘ms‘]
, 那麽在模板中可以通過{{ request.session.key }}
的形式使用:
{{ request.session.ms }}
3.在views外使用session
從mysql檢索出來的django_session表來看,包含字段如下:
mysql> describe django_session;
+--------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+-------+
| session_key | varchar(40) | NO | PRI | NULL | |
| session_data | longtext | NO | | NULL | |
| expire_date | datetime | NO | MUL | NULL | |
+--------------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
那麽可以使用django 數據庫API來訪問session,註意使用 get_decoded() 來讀取實際的session數據,如下:
>>> from django.contrib.sessions.models import Session
>>> s=Session.objects.all()
>>> s
[<Session: Session object>, <Session: Session object>, <Session: Session object>, <Session: Session object>, <Session: Session object>,....]
>>> for i in s:
... print i.get_decoded()
...
{‘domain‘: ‘http://beginman.sinaapp.com‘}
{‘domain‘: ‘http://beginman.sinaapp.com‘, ‘_auth_user_backend‘: ‘django.contrib.auth.backends.ModelBackend‘, ‘_auth_user_id‘: 1L}
{‘domain‘: ‘http://beginman.sinaapp.com‘}
....
4.其他細節
ession 字典接受任何支持序列化的Python對象。 參考Python內建模塊pickle的文檔以獲取更多信息。
Session 數據存在數據庫表 django_session 中
Session 數據在需要的時候才會讀取。 如果你從不使用 request.session , Django不會動相關數據庫表的一根毛。11
Django 只在需要的時候才送出cookie。 如果你壓根兒就沒有設置任何會話數據,它不會 送出會話cookie(除非 SESSION_SAVE_EVERY_REQUEST 設置為 True )。
Django session 框架完全而且只能基於cookie。 它不會後退到把會話ID編碼在URL中(像某些工具(PHP,JSP)那樣)。
這是一個有意而為之的設計。 把session放在URL中不只是難看,更重要的是這讓你的站點 很容易受到攻擊——通過 Referer header進行session ID”竊聽”而實施的攻擊。
五.Session之存取Redis Django實現
session可以在數據庫級別,緩存級別,文件級別,cookie級別基礎上存取,對於緩存級別而言無疑是最提升性能的,我們可以放在django緩存系統中,也可以在memcache中,也可以在Redis中. 這裏比較推薦Redis.我們完全可以用Redis實現Session功能.
1.我們知道session其實是在cookie中保存了一個sessionid,用戶每次訪問都將sessionid發給服務器,服務器通過ID查找用戶對應的狀態數據。在這裏我的處理方式也是在cookie中定義一個sessionid,程序需要取得用戶狀態時將sessionid做為key在Redis中查找。
2.同時session支持用戶在一定時間不訪問將session回收。
思路參考: http://www.cnblogs.com/ddyq/p/3151284.html
第三方庫: django-redis-sessions
, github地址 ,提供了Redis database backend for your sessions. 另一個第三方庫 Redis Django Cache Backend ,A cache backend for Django using the Redis datastructure server.用Redis存儲緩存數據.
對此我們可以應用到自己的項目中,如上篇 Redis key的設計模式 & django登陸 提到的實例,那麽接下來我們在此基礎上 實現session,cookie,Redis定制的用戶系統 .
註意要實現配置好Redis服務.
1.安裝&配置
(1).首先安裝配置django-redis-sessions :
pip install django-redis-sessions
在settings.py中設置 SESSION_ENGINE
,它默認是: django.contrib.sessions.backends.db 這裏設置如下:
SESSION_ENGINE = ‘redis_sessions.session‘
然後在settings.py中設置Redis數據庫信息:
SESSION_REDIS_HOST = ‘localhost‘
SESSION_REDIS_PORT = 6379
SESSION_REDIS_DB = 0
SESSION_REDIS_PASSWORD = ‘password‘
SESSION_REDIS_PREFIX = ‘session‘
# If you prefer domain socket connection, you can just add this line instead of SESSION_REDIS_HOST and SESSION_REDIS_PORT.
SESSION_REDIS_UNIX_DOMAIN_SOCKET_PATH = ‘/var/run/redis/redis.sock‘
配置完成後可測試如下:
$ pip install django nose redis
# Make sure you have redis running on localhost:6379
(poem)[beginman@beginman poem]$ nosetests
----------------------------------------------------------------------
Ran 0 tests in 0.001s
OK
(2).安裝配置django-redis-cache
pip install django-redis-cache
然後在settings.py配置CACHES
#On Django < 1.3:
CACHE_BACKEND = ‘redis_cache.cache:// : ‘
#On Django >= 1.3:
# When using TCP connections
CACHES = {
‘default‘: {
‘BACKEND‘: ‘redis_cache.RedisCache‘,
‘LOCATION‘: ‘<host>:<port>‘,
‘OPTIONS‘: {
‘DB‘: 1,
‘PASSWORD‘: ‘yadayada‘,
‘PARSER_CLASS‘: ‘redis.connection.HiredisParser‘,
‘CONNECTION_POOL_CLASS‘: ‘redis.BlockingConnectionPool‘,
‘CONNECTION_POOL_CLASS_KWARGS‘: {
‘max_connections‘: 50,
‘timeout‘: 20,
}
},
},
}
# When using unix domain sockets
# Note: ``LOCATION`` needs to be the same as the ``unixsocket`` setting
# in your redis.conf
CACHES = {
‘default‘: {
‘BACKEND‘: ‘redis_cache.RedisCache‘,
‘LOCATION‘: ‘/path/to/socket/file‘,
‘OPTIONS‘: {
‘DB‘: 1,
‘PASSWORD‘: ‘yadayada‘,
‘PARSER_CLASS‘: ‘redis.connection.HiredisParser‘
},
},
}
MIDDLEWARE_CLASSES = (
‘django.middleware.cache.UpdateCacheMiddleware‘, # This must be first on the list
‘django.middleware.common.CommonMiddleware‘,
‘django.contrib.sessions.middleware.SessionMiddleware‘,
(...)
‘django.middleware.cache.FetchFromCacheMiddleware‘, # This must be last
註意關於TCP連接:
#這裏是TCP連接
CACHES = {
‘default‘: {
‘BACKEND‘: ‘redis_cache.RedisCache‘,
‘LOCATION‘: ‘127.0.0.1:6379‘,
‘OPTIONS‘: {
‘DB‘: 0,
‘PASSWORD‘: ‘‘, # 這裏沒有設置密碼
# ‘PARSER_CLASS‘: ‘redis.connection.HiredisParser‘, # 這段可先註釋掉否則出現 :Hiredis is not installed的錯誤
‘CONNECTION_POOL_CLASS‘: ‘redis.BlockingConnectionPool‘,
‘CONNECTION_POOL_CLASS_KWARGS‘: {
‘max_connections‘: 50,
‘timeout‘: 20,
}
},
},
}
至此我們的Redis For Django算是完成了,如果不太明白,還有一篇文章 Using Redis as Django’s session store and cache backend 可以借鑒.
接下來我們可以在django中使用cache,存儲在Redis中.如下:
# Start by importing your default cache:
from django.core.cache import cache
# Store data under a-unique-key:
cache.set(‘a-unique-key‘, ‘this is a string which will be cached‘)
# Later on you can retrieve it in another function:
cache.get(‘a-unique-key‘) # Will return None if key is not found in cache
# You can specify a default value:
cache.get(‘another-unique-key‘, ‘default value‘)
# You can store multiple values at once:
cache.set_many({‘a‘: 1, ‘b‘: 2, ‘c‘: 3})
# And fetch multiple values:
cache.get_many([‘a‘, ‘b‘, ‘c‘]) # returns {‘a‘: 1, ‘b‘: 2, ‘c‘: 3}
# You can store complex types in the cache:
cache.set(‘a-unique-key‘, {
‘string‘ : ‘this is a string‘,
‘int‘ : 42,
‘list‘ : [1, 2, 3, 4],
‘tuple‘ : (1, 2, 3, 4),
‘dict‘ : {‘A‘: 1, ‘B‘ : 2},
})
如果我們選擇socket連接方式而非TCP,那麽在運行中可能會出現如下錯誤:Error 2 connecting to unix socket: /var/run/redis/redis.sock. No such file or directory. 這是因為redis 默認沒有開啟unix socket,需要在/etc/redis/redis.conf中修改,如下:
unixsocket /var/run/redis/redis.sock
unixsocketperm 777
記住配置完成之後要重啟Redis服務:
$ sudo service redis-server restart
六.Django+Redis實現用戶系統
在掌握上述知識後,我們可以使用django+redis來模擬django用戶系統.接下來的例子那上篇博客開刀,項目放到github上了
這裏 . 上篇我們實現了登陸的功能,接下來要實現用戶系統功能.
1.思路
2.程序實現
1.用戶系統類
這裏模擬一個蹩腳的用戶系統類(userSystem),如下:
#coding=utf-8
#Redis實現用戶系統
__author__ = ‘beginman‘
import redis
import datetime
import hashlib
r = redis.StrictRedis(host=‘localhost‘, port=‘6379‘, db=0)
class usSystem(object):
def __init__(self, request,response=None, uid=0, **kwargs):
self.request = request
self.response = response
self.kwargs = kwargs
self.uid = uid # user id
self.sessionid = None
def testCookie(self):
"""事先在登陸方法中下了request.session.set_test_cookie()的套子"""
if self.request.session.test_cookie_worked():
self.request.session.delete_test_cookie()
return True
return False
def getUsObj(self):
"""返回用戶對象,有則說明用戶已登陸,無則註銷"""
self.sessionid = self.request.COOKIES.get(‘sessionid‘, None)
if r.exists(self.sessionid):
if r.exists(‘sessionid_%s‘ %self.sessionid):
return r.hget(‘sessionid_%s‘ %self.sessionid, ‘uid‘)
return None
def setCookieAndSession(self):
"""cookie在登陸成功後已經寫入"""
self.sessionid = self.request.COOKIES.get(‘sessionid‘, None)
if not self.sessionid:
# set cookie
h = hashlib.md5()
h.update(datetime.datetime.now())
self.response.set_cookie(‘sessionid‘, h.hexdigest())
self.sessionid = h.hexdigest()
if not r.exists(‘sessionid_%s‘ %self.sessionid):
#set session
r.hset(‘sessionid_%s‘ %self.sessionid,‘uid‘, self.uid)
return True
然後在登陸方法中這樣寫:
# coding=utf-8
__author__ = ‘beginman‘
from django.shortcuts import render
from django.http import HttpResponseRedirect
from form import LoginForm
from common.userSystem import usSystem
import redis
import datetime
r = redis.StrictRedis(host=‘localhost‘, port=‘6379‘, db=0)
def home(request):
return render(request, ‘index.html‘)
def usLogin(request):
context = {}
if request.method == ‘POST‘:
form = LoginForm(request.POST)
if form.is_valid():
us = form.cleaned_data[‘us‘]
pwd = form.cleaned_data[‘pwd‘]
if r.exists(‘us:%s:id‘ %us): # 檢查是否存在該用戶關系鍵值
uid = r.get(‘us:%s:id‘ %us) # 獲取該用戶在user表中對應的id
if r.exists(‘user:%s‘ %uid): # 檢查是否存在該用戶鍵值(如user:1)
us_, pwd_ = r.hmget(‘user:%s‘ %uid, ‘username‘, ‘pwd‘) # 獲取該用戶的用戶名密碼
if us_ == us and pwd_ == pwd: # 校驗成功
r.hincrby(‘user:%s‘ %uid, ‘login_count‘, 1) # 登陸次數累加
r.hset(‘user:%s‘ %uid, ‘last_login_date‘, datetime.datetime.now()) # 添加最近登陸
# set Cookies
res = HttpResponseRedirect(‘/‘)
ussys = usSystem(request, res, uid)
if ussys.testCookie() and ussys.setCookieAndSession():
return res
context[‘msg‘] = u‘賬號或密碼錯誤‘
context[‘form‘] = form
request.session.set_test_cookie()
form = LoginForm()
context[‘form‘] = form
return render(request, ‘login.html‘, context)
同時要註意中間件處理:
#coding=utf-8
#中間件擴展
__author__ = ‘beginman‘
from django.http import HttpResponseRedirect
from django.conf import settings
from common.userSystem import usSystem
class Mymiddleware(object):
def process_request(self, request):
"""Request預處理函數"""
path = str(request.path)
request.session[‘domain‘] = settings.DOMAIN
if path.startswith(‘/site_media/‘):
return None
#驗證登陸
ussys = usSystem(request)
if ussys.getUsObj():
pass
在一些需要登陸後才能訪問的可以寫在驗證登陸後面.這裏還需要慢慢改進.
參考
1.How to use sessions
2.第十四章: 會話、用戶和註冊
3.Redis-py 文檔
4.用unix socket加速php-fpm、mysql、redis的連接
Redis+Django(Session,Cookie、Cache)的用戶系統