├── example ├── __init__.py ├── urls.py ├── app.py └── settings.py ├── redis_sessions ├── __init__.py ├── settings.py └── session.py ├── dev_requirements.txt ├── MANIFEST.in ├── tests ├── __init__.py └── tests.py ├── Makefile ├── .gitignore ├── AUTHORS ├── CHANGELOG.md ├── .travis.yml ├── setup.py ├── LICENSE.txt └── README.rst /example/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /redis_sessions/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.6.2' 2 | -------------------------------------------------------------------------------- /dev_requirements.txt: -------------------------------------------------------------------------------- 1 | nose 2 | django 3 | redis==4.5.4 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENSE.txt 3 | include CHANGELOG.md 4 | graft tests -------------------------------------------------------------------------------- /example/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import url 2 | 3 | 4 | urlpatterns = [ 5 | url(r'^$', HomeView.as_view()), 6 | ] 7 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | 4 | settings.configure( 5 | SESSION_ENGINE='redis_sessions.session' 6 | ) 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | upload_to_pypi: 2 | pip install twine setuptools 3 | rm -rf dist/* 4 | rm -rf build/* 5 | python setup.py sdist build 6 | twine upload dist/* 7 | 8 | test: 9 | nosetests -v -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | _build/ 4 | .coverage 5 | build 6 | env* 7 | dist 8 | *.egg-info 9 | *.egg 10 | .idea 11 | *.sublime* 12 | *.swp 13 | virtualenv 14 | venv 15 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Ordered by date of first contribution: 2 | Martin Rusev 3 | Chris Jones 4 | Mikhail Andreev 5 | Jeff Baier / http://jeffbaier.com/ 6 | hellysmile 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.6.1 (16 September 2017) 2 | 3 | BUG FIXES: 4 | 5 | * Dict Config fixes: ([#48](https://github.com/martinrusev/django-redis-sessions/issues/48)) 6 | 7 | 8 | ## 0.6 (6 September 2017) 9 | 10 | 11 | IMPROVEMENTS / BREAKING CHANGES: 12 | 13 | * Dict config: ([#18](https://github.com/martinrusev/django-redis-sessions/issues/18)) 14 | 15 | 16 | ## 0.5.6 (9 June 2016) 17 | 18 | 19 | IMPROVEMENTS: 20 | 21 | * Redis Sentinel support: ([#29](https://github.com/martinrusev/django-redis-sessions/pull/29)) 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | services: 3 | - redis-server 4 | matrix: 5 | include: 6 | - python: "3.6" 7 | env: DJANGO="django>1.5" 8 | - python: "3.5" 9 | env: DJANGO="django>1.5" 10 | - python: "3.4" 11 | env: DJANGO="django>1.5" 12 | - python: "2.7" 13 | env: DJANGO="django>=1.4,<2.0" 14 | - python: "pypy" 15 | env: DJANGO="django>=1.4,<2.0" 16 | install: 17 | - pip install -r <(cat dev_requirements.txt | sed "s/django/$DJANGO/") 18 | script: python setup.py nosetests 19 | -------------------------------------------------------------------------------- /example/app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | import sys 4 | import os 5 | 6 | 7 | from django.conf import settings 8 | from django.conf.urls import url 9 | from django.http import HttpResponse 10 | from django.views import View 11 | 12 | 13 | import settings as app_settings 14 | 15 | class RedisSessionsView(View): 16 | def get(self, request): 17 | return HttpResponse('Home') 18 | 19 | 20 | urlpatterns = [ 21 | url(r'^$', RedisSessionsView.as_view()), 22 | ] 23 | 24 | 25 | from django.core.wsgi import get_wsgi_application 26 | def return_application(): 27 | return get_wsgi_application() 28 | 29 | if __name__ == "__main__": 30 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "settings") 31 | from django.core.management import execute_from_command_line 32 | execute_from_command_line(sys.argv) 33 | else: 34 | return_application() -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import os 3 | from redis_sessions import __version__ 4 | 5 | def read(filename): 6 | return open(os.path.join(os.path.dirname(__file__), filename)).read() 7 | 8 | packages = ['redis_sessions'] 9 | 10 | 11 | setup( 12 | name='django-redis-sessions', 13 | version=__version__, 14 | description= "Redis Session Backend For Django", 15 | long_description=read("README.rst"), 16 | keywords='django, sessions,', 17 | author='Martin Rusev', 18 | author_email='martin@amon.cx', 19 | url='http://github.com/martinrusev/django-redis-sessions', 20 | license='BSD', 21 | packages=packages, 22 | zip_safe=False, 23 | install_requires=['redis>=2.4.10'], 24 | include_package_data=True, 25 | classifiers=[ 26 | "Programming Language :: Python :: 2", 27 | "Programming Language :: Python :: 3", 28 | "Topic :: Software Development :: Libraries :: Python Modules", 29 | "Framework :: Django", 30 | "Environment :: Web Environment", 31 | ], 32 | test_suite='tests' 33 | ) 34 | -------------------------------------------------------------------------------- /example/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | current_dir = lambda x: os.path.join(os.path.abspath(os.path.dirname(__file__)), x) 4 | sys.path.append('/home/martin/personal/django-redis-sessions/redis_sessions') 5 | 6 | 7 | DEBUG = True 8 | ROOT_URLCONF="app" 9 | DATABASES = {'default': {}} 10 | 11 | TEMPLATES = [ 12 | { 13 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 14 | 'DIRS': [current_dir('.')], 15 | 'APP_DIRS': True, 16 | 'OPTIONS': { 17 | 'context_processors': ( 18 | 'django.template.context_processors.debug', 19 | 'django.template.context_processors.static', 20 | 'django.template.context_processors.request', 21 | ), 22 | }, 23 | }, 24 | ] 25 | SECRET_KEY = 'secret' 26 | MIDDLEWARE = [ 27 | 'django.middleware.common.CommonMiddleware', 28 | 'django.contrib.sessions.middleware.SessionMiddleware' 29 | ] 30 | INSTALLED_APPS = ( 31 | 'django.contrib.sessions', 32 | ) 33 | SESSION_ENGINE = 'redis_sessions.session' 34 | 35 | STATIC_URL = '/static/' 36 | STATICFILES_DIRS = ( 37 | current_dir('static'), 38 | ) 39 | 40 | 41 | SESSION_REDIS = { 42 | 'host': 'localhost', 43 | 'port': 6378, 44 | 'db': 1, 45 | 'password': 'password', 46 | 'prefix': 'session', 47 | 'socket_timeout': 1 48 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Martin Rusev 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | * Neither the name of the author nor the names of other 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /redis_sessions/settings.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | # SESSION_REDIS - Default 4 | SESSION_REDIS = getattr(settings, 'SESSION_REDIS', {}) 5 | SESSION_REDIS_CONNECTION_OBJECT = getattr(settings, 'SESSION_REDIS_CONNECTION_OBJECT', None) 6 | SESSION_REDIS_HOST = SESSION_REDIS.get('host', 'localhost') 7 | SESSION_REDIS_PORT = SESSION_REDIS.get('port', 6379) 8 | SESSION_REDIS_SOCKET_TIMEOUT = SESSION_REDIS.get('socket_timeout', 0.1) 9 | SESSION_REDIS_RETRY_ON_TIMEOUT = SESSION_REDIS.get('retry_on_timeout', False) 10 | SESSION_REDIS_DB = SESSION_REDIS.get('db', 0) 11 | SESSION_REDIS_PREFIX = SESSION_REDIS.get('prefix', '') 12 | SESSION_REDIS_PASSWORD = SESSION_REDIS.get('password', None) 13 | SESSION_REDIS_UNIX_DOMAIN_SOCKET_PATH = SESSION_REDIS.get('unix_domain_socket_path', None) 14 | SESSION_REDIS_URL = SESSION_REDIS.get('url', None) 15 | SESSION_REDIS_SENTINEL_PASSWORD = getattr(settings, 'SESSION_REDIS_SENTINEL_PASSWORD', "") 16 | SESSION_REDIS_SENTINEL_KWARGS = SESSION_REDIS.get('sentinel_kwargs', None) 17 | 18 | 19 | """ 20 | Should be on the format: 21 | [ 22 | { 23 | 'host': 'localhost2', 24 | 'port': 6379, 25 | 'db': 0, 26 | 'password': None, 27 | 'unix_domain_socket_path': None, 28 | 'url': None, 29 | 'weight': 1, 30 | }, 31 | { 32 | 'host': 'localhost1', 33 | 'port': 6379, 34 | 'db': 0, 35 | 'password': None, 36 | 'unix_domain_socket_path': None, 37 | 'url': None, 38 | 'weight': 1, 39 | }, 40 | ] 41 | """ 42 | SESSION_REDIS_POOL = SESSION_REDIS.get('POOL', None) 43 | 44 | # should be on the format [(host, port), (host, port), (host, port)] 45 | SESSION_REDIS_SENTINEL_LIST = getattr(settings, 'SESSION_REDIS_SENTINEL_LIST', None) 46 | SESSION_REDIS_SENTINEL_MASTER_ALIAS = getattr(settings, 'SESSION_REDIS_SENTINEL_MASTER_ALIAS', None) 47 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | django-redis-sessions 2 | ===================== 3 | 4 | Redis database backend for your sessions 5 | 6 | |Build Status| 7 | 8 | - `Installation`_ 9 | - `Available Settings`_ 10 | - `Changelog`_ 11 | 12 | Installation 13 | ============ 14 | 15 | - Run ``pip install django-redis-sessions`` or alternatively download 16 | the tarball and run ``python setup.py install``, 17 | 18 | For Django < 1.4 run ``pip install django-redis-sessions==0.3`` 19 | 20 | - Set ``redis_sessions.session`` as your session engine, like so: 21 | 22 | .. code:: python 23 | 24 | SESSION_ENGINE = 'redis_sessions.session' 25 | 26 | Available Settings 27 | ================== 28 | 29 | .. code:: python 30 | 31 | SESSION_REDIS = { 32 | 'host': 'localhost', 33 | 'port': 6379, 34 | 'db': 0, 35 | 'password': 'password', 36 | 'prefix': 'session', 37 | 'socket_timeout': 1, 38 | 'retry_on_timeout': False 39 | } 40 | 41 | If you prefer domain socket connection, you can just add this line 42 | instead of HOST and PORT. 43 | 44 | .. code:: python 45 | 46 | SESSION_REDIS = { 47 | 'unix_domain_socket_path': '/var/run/redis/redis.sock', 48 | 'db': 0, 49 | 'password': 'password', 50 | 'prefix': 'session', 51 | 'socket_timeout': 1, 52 | 'retry_on_timeout': False 53 | } 54 | 55 | Redis Sentinel 56 | ~~~~~~~~~~~~~~ 57 | 58 | .. code:: python 59 | 60 | SESSION_REDIS_SENTINEL_LIST = [(host, port), (host, port), (host, port)] 61 | SESSION_REDIS_SENTINEL_MASTER_ALIAS = 'sentinel-master' 62 | 63 | Redis Pool (Horizontal partitioning) 64 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 65 | 66 | Splits sessions between Redis instances based on the session key. You 67 | can configure the connection type for each Redis instance in the pool 68 | (host/port, unix socket, redis url). 69 | 70 | 71 | .. code:: python 72 | 73 | SESSION_REDIS = { 74 | 'prefix': 'session', 75 | 'socket_timeout': 1 76 | 'retry_on_timeout': False, 77 | 'pool': [{ 78 | 'host': 'localhost3', 79 | 'port': 6379, 80 | 'db': 0, 81 | 'password': None, 82 | 'unix_domain_socket_path': None, 83 | 'url': None, 84 | 'weight': 1 85 | }, 86 | { 87 | 'host': 'localhost2', 88 | 'port': 6379, 89 | 'db': 0, 90 | 'password': None, 91 | 'unix_domain_socket_path': None, 92 | 'url': None, 93 | 'weight': 1 94 | }, 95 | { 96 | 'host': 'localhost1', 97 | 'port': 6379, 98 | 'db': 0, 99 | 'password': None, 100 | 'unix_domain_socket_path': None, 101 | 'url': None, 102 | 'weight': 1 103 | }] 104 | } 105 | 106 | 107 | Tests 108 | ===== 109 | 110 | .. code:: bash 111 | 112 | $ pip install -r dev_requirements.txt 113 | # Make sure you have redis running on localhost:6379 114 | $ nosetests -v 115 | 116 | `Changelog `__ 117 | ============================================================================================= 118 | 119 | .. _Installation: #installation 120 | .. _Available Settings: #available-settings 121 | .. _Changelog: #changelog 122 | 123 | .. |Build Status| image:: https://travis-ci.org/martinrusev/django-redis-sessions.svg?branch=master 124 | :target: https://travis-ci.org/martinrusev/django-redis-sessions 125 | -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | import time 2 | from random import randint 3 | 4 | 5 | from nose.tools import eq_, assert_false 6 | 7 | from redis_sessions.session import SessionStore 8 | from redis_sessions.session import RedisServer 9 | from redis_sessions import settings 10 | 11 | 12 | ## Dev 13 | import redis 14 | import timeit 15 | 16 | redis_session = SessionStore() 17 | 18 | 19 | def test_modify_and_keys(): 20 | eq_(redis_session.modified, False) 21 | redis_session['test'] = 'test_me' 22 | eq_(redis_session.modified, True) 23 | eq_(redis_session['test'], 'test_me') 24 | 25 | 26 | def test_session_load_does_not_create_record(): 27 | session = SessionStore('someunknownkey') 28 | session.load() 29 | 30 | eq_(redis_session.exists(redis_session.session_key), False) 31 | 32 | 33 | def test_save_and_delete(): 34 | redis_session['key'] = 'value' 35 | redis_session.save() 36 | eq_(redis_session.exists(redis_session.session_key), True) 37 | redis_session.delete(redis_session.session_key) 38 | eq_(redis_session.exists(redis_session.session_key), False) 39 | 40 | 41 | def test_flush(): 42 | redis_session['key'] = 'another_value' 43 | redis_session.save() 44 | key = redis_session.session_key 45 | redis_session.flush() 46 | eq_(redis_session.exists(key), False) 47 | 48 | 49 | def test_items(): 50 | redis_session['item1'], redis_session['item2'] = 1, 2 51 | redis_session.save() 52 | # Python 3.* 53 | eq_(set(list(redis_session.items())), set([('item2', 2), ('item1', 1)])) 54 | 55 | 56 | def test_expiry(): 57 | redis_session.set_expiry(1) 58 | # Test if the expiry age is set correctly 59 | eq_(redis_session.get_expiry_age(), 1) 60 | redis_session['key'] = 'expiring_value' 61 | redis_session.save() 62 | key = redis_session.session_key 63 | eq_(redis_session.exists(key), True) 64 | time.sleep(2) 65 | eq_(redis_session.exists(key), False) 66 | 67 | 68 | def test_save_and_load(): 69 | redis_session.set_expiry(60) 70 | redis_session.setdefault('item_test', 8) 71 | redis_session.save() 72 | session_data = redis_session.load() 73 | eq_(session_data.get('item_test'), 8) 74 | 75 | 76 | def test_with_redis_url_config(): 77 | settings.SESSION_REDIS_URL = 'redis://localhost' 78 | 79 | from redis_sessions.session import SessionStore 80 | 81 | redis_session = SessionStore() 82 | server = redis_session.server 83 | 84 | host = server.connection_pool.connection_kwargs.get('host') 85 | port = server.connection_pool.connection_kwargs.get('port') 86 | db = server.connection_pool.connection_kwargs.get('db') 87 | 88 | eq_(host, 'localhost') 89 | eq_(port, 6379) 90 | eq_(db, 0) 91 | 92 | 93 | def test_one_connection_is_used(): 94 | session = SessionStore('session_key_1') 95 | session['key1'] = 'value1' 96 | session.save() 97 | 98 | redis_server = session.server 99 | set_client_name_1 = 'client_name_' + str(randint(1, 1000)) 100 | redis_server.client_setname(set_client_name_1) 101 | client_name_1 = redis_server.client_getname() 102 | eq_(set_client_name_1, client_name_1) 103 | del session 104 | 105 | session = SessionStore('session_key_2') 106 | session['key2'] = 'value2' 107 | session.save() 108 | 109 | redis_server = session.server 110 | client_name_2 = redis_server.client_getname() 111 | eq_(client_name_1, client_name_2) 112 | 113 | 114 | def test_redis_pool_server_select(): 115 | servers = [ 116 | { 117 | 'HOST': 'localhost2', 118 | 'PORT': 6379, 119 | 'DB': 0, 120 | 'PASSWORD': None, 121 | 'UNIX_DOMAIN_SOCKET_PATH': None, 122 | 'WEIGHT': 1, 123 | }, 124 | { 125 | 'HOST': 'localhost1', 126 | 'PORT': 6379, 127 | 'DB': 0, 128 | 'PASSWORD': None, 129 | 'UNIX_DOMAIN_SOCKET_PATH': None, 130 | 'WEIGHT': 1, 131 | }, 132 | ] 133 | 134 | keys1 = [ 135 | 'm8f0os91g40fsq8eul6tejqpp6k22', 136 | 'kcffsbb5o272et1d5e6ib7gh75pd9', 137 | 'gqldpha87m8183vl9s8uqobcr2ws3', 138 | 'ukb9bg2jifrr60fstla67knjv3e32', 139 | 'k3dranjfna7fv7ijpofs6l6bj2pw1', 140 | 'an4no833idr9jddr960r8ikai5nh3', 141 | '16b9gardpcscrj5q4a4kf3c4u7tq8', 142 | 'etdefnorfbvfc165c5airu77p2pl9', 143 | 'mr778ou0sqqme21gjdiu4drtc0bv4', 144 | 'ctkgd8knu5hukdrdue6im28p90kt7' 145 | ] 146 | 147 | keys2 = [ 148 | 'jgpsbmjj6030fdr3aefg37nq47nb8', 149 | 'prsv0trk66jc100pipm6bb78c3pl2', 150 | '84ksqj2vqral7c6ped9hcnq940qq1', 151 | 'bv2uc3q48rm8ubipjmolgnhul0ou3', 152 | '6c8oph72pfsg3db37qsefn3746fg4', 153 | 'tbc0sjtl2bkp5i9n2j2jiqf4r0bg9', 154 | 'v0on9rorn71913o3rpqhvkknc1wm5', 155 | 'lmsv98ns819uo2klk3s1nusqm0mr0', 156 | '0foo2bkgvrlk3jt2tjbssrsc47tr3', 157 | '05ure0f6r5jjlsgaimsuk4n1k2sx6', 158 | ] 159 | rs = RedisServer('') 160 | 161 | for key in keys1: 162 | server_key, server = rs.get_server(key, servers) 163 | eq_(server_key, 1) 164 | 165 | for key in keys2: 166 | server_key, server = rs.get_server(key, servers) 167 | eq_(server_key, 0) 168 | 169 | 170 | def test_redis_connection_object(): 171 | settings.SESSION_REDIS_CONNECTION_OBJECT = redis.StrictRedis( 172 | host='redis', 173 | port=1234, 174 | db=12, 175 | ) 176 | 177 | from redis_sessions.session import SessionStore 178 | 179 | redis_session = SessionStore() 180 | server = redis_session.server 181 | 182 | host = server.connection_pool.connection_kwargs.get('host') 183 | port = server.connection_pool.connection_kwargs.get('port') 184 | db = server.connection_pool.connection_kwargs.get('db') 185 | 186 | eq_(host, 'redis') 187 | eq_(port, 1234) 188 | eq_(db, 12) 189 | 190 | 191 | def test_with_unix_url_config(): 192 | pass 193 | 194 | # Uncomment this in `redis.conf`: 195 | # 196 | # unixsocket /tmp/redis.sock 197 | # unixsocketperm 755 198 | 199 | #settings.SESSION_REDIS_URL = 'unix:///tmp/redis.sock' 200 | 201 | #from redis_sessions.session import SessionStore 202 | 203 | # redis_session = SessionStore() 204 | # server = redis_session.server 205 | # 206 | # host = server.connection_pool.connection_kwargs.get('host') 207 | # port = server.connection_pool.connection_kwargs.get('port') 208 | # db = server.connection_pool.connection_kwargs.get('db') 209 | # 210 | # eq_(host, 'localhost') 211 | # eq_(port, 6379) 212 | # eq_(db, 0) 213 | 214 | # def test_load(): 215 | # redis_session.set_expiry(60) 216 | # redis_session['item1'], redis_session['item2'] = 1,2 217 | # redis_session.save() 218 | # session_data = redis_session.server.get(redis_session.session_key) 219 | # expiry, data = int(session_data[:15]), session_data[15:] 220 | -------------------------------------------------------------------------------- /redis_sessions/session.py: -------------------------------------------------------------------------------- 1 | import redis 2 | 3 | try: 4 | from django.utils.encoding import force_unicode 5 | except ImportError: # Python 3.* 6 | from django.utils.encoding import force_str as force_unicode 7 | from django.contrib.sessions.backends.base import SessionBase, CreateError 8 | from redis_sessions import settings 9 | from redis.exceptions import ResponseError 10 | 11 | 12 | class RedisServer(): 13 | __redis = {} 14 | 15 | def __init__(self, session_key): 16 | self.session_key = session_key 17 | self.connection_key = '' 18 | 19 | if settings.SESSION_REDIS_SENTINEL_LIST is not None: 20 | self.connection_type = 'sentinel' 21 | elif settings.SESSION_REDIS_CONNECTION_OBJECT is not None: 22 | self.connection_type = 'connection_object' 23 | else: 24 | if settings.SESSION_REDIS_POOL is not None: 25 | server_key, server = self.get_server(session_key, settings.SESSION_REDIS_POOL) 26 | self.connection_key = str(server_key) 27 | settings.SESSION_REDIS_HOST = getattr(server, 'host', 'localhost') 28 | settings.SESSION_REDIS_PORT = getattr(server, 'port', 6379) 29 | settings.SESSION_REDIS_DB = getattr(server, 'db', 0) 30 | settings.SESSION_REDIS_PASSWORD = getattr(server, 'password', None) 31 | settings.SESSION_REDIS_URL = getattr(server, 'url', None) 32 | settings.SESSION_REDIS_UNIX_DOMAIN_SOCKET_PATH = getattr(server,'unix_domain_socket_path', None) 33 | 34 | if settings.SESSION_REDIS_URL is not None: 35 | self.connection_type = 'redis_url' 36 | elif settings.SESSION_REDIS_UNIX_DOMAIN_SOCKET_PATH is not None: 37 | self.connection_type = 'redis_unix_url' 38 | elif settings.SESSION_REDIS_HOST is not None: 39 | self.connection_type = 'redis_host' 40 | 41 | self.connection_key += self.connection_type 42 | 43 | def get_server(self, key, servers_pool): 44 | total_weight = sum([row.get('weight', 1) for row in servers_pool]) 45 | pos = 0 46 | for i in range(3, -1, -1): 47 | pos = pos * 2 ** 8 + ord(key[i]) 48 | pos = pos % total_weight 49 | 50 | pool = iter(servers_pool) 51 | server = next(pool) 52 | server_key = 0 53 | i = 0 54 | while i < total_weight: 55 | if i <= pos < (i + server.get('weight', 1)): 56 | return server_key, server 57 | i += server.get('weight', 1) 58 | server = next(pool) 59 | server_key += 1 60 | 61 | return 62 | 63 | def get(self): 64 | if self.connection_key in self.__redis: 65 | return self.__redis[self.connection_key] 66 | 67 | if self.connection_type == 'connection_object': 68 | self.__redis[self.connection_key] = settings.SESSION_REDIS_CONNECTION_OBJECT 69 | elif self.connection_type == 'sentinel': 70 | from redis.sentinel import Sentinel 71 | sentinel = Sentinel( 72 | settings.SESSION_REDIS_SENTINEL_LIST, 73 | socket_timeout=settings.SESSION_REDIS_SOCKET_TIMEOUT, 74 | retry_on_timeout=settings.SESSION_REDIS_RETRY_ON_TIMEOUT, 75 | db=getattr(settings, 'SESSION_REDIS_DB', 0), 76 | password=getattr(settings, 'SESSION_REDIS_PASSWORD', None), 77 | sentinel_kwargs={'password': getattr(settings, 'SESSION_REDIS_SENTINEL_PASSWORD', None)} 78 | ) 79 | self.__redis[self.connection_key] = sentinel.master_for(settings.SESSION_REDIS_SENTINEL_MASTER_ALIAS) 80 | # check if redis server is configured with sentinel password. 81 | # If not, then connect without password 82 | try: 83 | sentinel.sentinel_masters() 84 | except ResponseError: 85 | self.__redis[self.connection_key] = Sentinel( 86 | settings.SESSION_REDIS_SENTINEL_LIST, 87 | socket_timeout=settings.SESSION_REDIS_SOCKET_TIMEOUT, 88 | retry_on_timeout=settings.SESSION_REDIS_RETRY_ON_TIMEOUT, 89 | db=getattr(settings, 'SESSION_REDIS_DB', 0), 90 | password=getattr(settings, 'SESSION_REDIS_PASSWORD', None) 91 | ).master_for(settings.SESSION_REDIS_SENTINEL_MASTER_ALIAS) 92 | elif self.connection_type == 'redis_url': 93 | self.__redis[self.connection_key] = redis.StrictRedis.from_url( 94 | settings.SESSION_REDIS_URL, 95 | socket_timeout=settings.SESSION_REDIS_SOCKET_TIMEOUT 96 | ) 97 | elif self.connection_type == 'redis_host': 98 | self.__redis[self.connection_key] = redis.StrictRedis( 99 | host=settings.SESSION_REDIS_HOST, 100 | port=settings.SESSION_REDIS_PORT, 101 | socket_timeout=settings.SESSION_REDIS_SOCKET_TIMEOUT, 102 | retry_on_timeout=settings.SESSION_REDIS_RETRY_ON_TIMEOUT, 103 | db=settings.SESSION_REDIS_DB, 104 | password=settings.SESSION_REDIS_PASSWORD 105 | ) 106 | elif self.connection_type == 'redis_unix_url': 107 | self.__redis[self.connection_key] = redis.StrictRedis( 108 | unix_socket_path=settings.SESSION_REDIS_UNIX_DOMAIN_SOCKET_PATH, 109 | socket_timeout=settings.SESSION_REDIS_SOCKET_TIMEOUT, 110 | retry_on_timeout=settings.SESSION_REDIS_RETRY_ON_TIMEOUT, 111 | db=settings.SESSION_REDIS_DB, 112 | password=settings.SESSION_REDIS_PASSWORD, 113 | ) 114 | 115 | return self.__redis[self.connection_key] 116 | 117 | 118 | class SessionStore(SessionBase): 119 | """ 120 | Implements Redis database session store. 121 | """ 122 | def __init__(self, session_key=None): 123 | super(SessionStore, self).__init__(session_key) 124 | self.server = RedisServer(session_key).get() 125 | 126 | def load(self): 127 | try: 128 | session_key = self._get_or_create_session_key() 129 | real_stored_session_key = self.get_real_stored_key(session_key) 130 | session_data = self.server.get(real_stored_session_key) 131 | except Exception: 132 | session_data = None 133 | 134 | if session_data is not None: 135 | return self.decode(force_unicode(session_data)) 136 | 137 | self._session_key = None 138 | return {} 139 | 140 | def exists(self, session_key): 141 | return self.server.exists(self.get_real_stored_key(session_key)) 142 | 143 | def create(self): 144 | while True: 145 | self._session_key = self._get_new_session_key() 146 | 147 | try: 148 | self.save(must_create=True) 149 | except CreateError: 150 | # Key wasn't unique. Try again. 151 | continue 152 | self.modified = True 153 | return 154 | 155 | def save(self, must_create=False): 156 | if self.session_key is None: 157 | return self.create() 158 | if must_create and self.exists(self._get_or_create_session_key()): 159 | raise CreateError 160 | data = self.encode(self._get_session(no_load=must_create)) 161 | if redis.VERSION[0] >= 2: 162 | self.server.setex( 163 | self.get_real_stored_key(self._get_or_create_session_key()), 164 | self.get_expiry_age(), 165 | data 166 | ) 167 | else: 168 | self.server.set( 169 | self.get_real_stored_key(self._get_or_create_session_key()), 170 | data 171 | ) 172 | self.server.expire( 173 | self.get_real_stored_key(self._get_or_create_session_key()), 174 | self.get_expiry_age() 175 | ) 176 | 177 | def delete(self, session_key=None): 178 | if session_key is None: 179 | if self.session_key is None: 180 | return 181 | session_key = self.session_key 182 | try: 183 | self.server.delete(self.get_real_stored_key(session_key)) 184 | except: 185 | pass 186 | 187 | @classmethod 188 | def clear_expired(cls): 189 | pass 190 | 191 | def get_real_stored_key(self, session_key): 192 | """Return the real key name in redis storage 193 | @return string 194 | """ 195 | prefix = settings.SESSION_REDIS_PREFIX 196 | if not prefix: 197 | return session_key 198 | return ':'.join([prefix, session_key]) 199 | --------------------------------------------------------------------------------