co6co.web-session 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- co6co_web_session-0.0.1/PKG-INFO +25 -0
- co6co_web_session-0.0.1/README.md +5 -0
- co6co_web_session-0.0.1/co6co.web_session.egg-info/PKG-INFO +25 -0
- co6co_web_session-0.0.1/co6co.web_session.egg-info/SOURCES.txt +18 -0
- co6co_web_session-0.0.1/co6co.web_session.egg-info/dependency_links.txt +1 -0
- co6co_web_session-0.0.1/co6co.web_session.egg-info/requires.txt +1 -0
- co6co_web_session-0.0.1/co6co.web_session.egg-info/top_level.txt +1 -0
- co6co_web_session-0.0.1/co6co.web_session.egg-info/zip-safe +1 -0
- co6co_web_session-0.0.1/co6co_web_session/__init__.py +17 -0
- co6co_web_session-0.0.1/co6co_web_session/aioredis.py +77 -0
- co6co_web_session-0.0.1/co6co_web_session/base.py +143 -0
- co6co_web_session-0.0.1/co6co_web_session/memcache.py +92 -0
- co6co_web_session-0.0.1/co6co_web_session/memory.py +34 -0
- co6co_web_session-0.0.1/co6co_web_session/mongodb.py +129 -0
- co6co_web_session-0.0.1/co6co_web_session/redis.py +86 -0
- co6co_web_session-0.0.1/co6co_web_session/session.py +41 -0
- co6co_web_session-0.0.1/co6co_web_session/utils.py +17 -0
- co6co_web_session-0.0.1/co6co_web_session/versions.py +2 -0
- co6co_web_session-0.0.1/setup.cfg +4 -0
- co6co_web_session-0.0.1/setup.py +51 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: co6co.web_session
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: web session 扩展
|
|
5
|
+
Home-page: http://github.com/co6co
|
|
6
|
+
Author: co6co
|
|
7
|
+
Author-email: co6co@qq.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: co6co
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: author-email
|
|
14
|
+
Dynamic: classifier
|
|
15
|
+
Dynamic: description
|
|
16
|
+
Dynamic: description-content-type
|
|
17
|
+
Dynamic: home-page
|
|
18
|
+
Dynamic: requires-dist
|
|
19
|
+
Dynamic: summary
|
|
20
|
+
|
|
21
|
+
# web session header
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
0.0.1 未经严谨测试
|
|
25
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: co6co.web_session
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: web session 扩展
|
|
5
|
+
Home-page: http://github.com/co6co
|
|
6
|
+
Author: co6co
|
|
7
|
+
Author-email: co6co@qq.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: co6co
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: author-email
|
|
14
|
+
Dynamic: classifier
|
|
15
|
+
Dynamic: description
|
|
16
|
+
Dynamic: description-content-type
|
|
17
|
+
Dynamic: home-page
|
|
18
|
+
Dynamic: requires-dist
|
|
19
|
+
Dynamic: summary
|
|
20
|
+
|
|
21
|
+
# web session header
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
0.0.1 未经严谨测试
|
|
25
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
co6co.web_session.egg-info/PKG-INFO
|
|
4
|
+
co6co.web_session.egg-info/SOURCES.txt
|
|
5
|
+
co6co.web_session.egg-info/dependency_links.txt
|
|
6
|
+
co6co.web_session.egg-info/requires.txt
|
|
7
|
+
co6co.web_session.egg-info/top_level.txt
|
|
8
|
+
co6co.web_session.egg-info/zip-safe
|
|
9
|
+
co6co_web_session/__init__.py
|
|
10
|
+
co6co_web_session/aioredis.py
|
|
11
|
+
co6co_web_session/base.py
|
|
12
|
+
co6co_web_session/memcache.py
|
|
13
|
+
co6co_web_session/memory.py
|
|
14
|
+
co6co_web_session/mongodb.py
|
|
15
|
+
co6co_web_session/redis.py
|
|
16
|
+
co6co_web_session/session.py
|
|
17
|
+
co6co_web_session/utils.py
|
|
18
|
+
co6co_web_session/versions.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
co6co
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
co6co_web_session
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .memcache import MemcacheSessionImp
|
|
2
|
+
from .redis import RedisSessionImp
|
|
3
|
+
from .memory import MemorySessionImp
|
|
4
|
+
from .mongodb import MongoDBSessionImp
|
|
5
|
+
from .aioredis import AIORedisSessionImp
|
|
6
|
+
from .session import Session
|
|
7
|
+
from .base import IBaseSession
|
|
8
|
+
|
|
9
|
+
__all__ = (
|
|
10
|
+
"IBaseSession",
|
|
11
|
+
"Session",
|
|
12
|
+
"MemcacheSessionImp",
|
|
13
|
+
"RedisSessionImp",
|
|
14
|
+
"MemorySessionImp",
|
|
15
|
+
"MongoDBSessionImp",
|
|
16
|
+
"AIORedisSessionImp",
|
|
17
|
+
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from .base import IBaseSession
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import aioredis
|
|
5
|
+
except ImportError:
|
|
6
|
+
aioredis = None
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AIORedisSessionImp(IBaseSession):
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
redis,
|
|
13
|
+
expiry: int = 2592000,
|
|
14
|
+
head_name: str = "session",
|
|
15
|
+
prefix: str = "session:",
|
|
16
|
+
samesite: str = None,
|
|
17
|
+
session_name: str = "Session",
|
|
18
|
+
secure: bool = False,
|
|
19
|
+
):
|
|
20
|
+
"""Initializes a session interface backed by Redis.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
redis (Callable):
|
|
24
|
+
aioredis connection or connection pool instance.
|
|
25
|
+
domain (str, optional):
|
|
26
|
+
Optional domain which will be attached to the cookie.
|
|
27
|
+
expiry (int, optional):
|
|
28
|
+
Seconds until the session should expire.
|
|
29
|
+
httponly (bool, optional):
|
|
30
|
+
Adds the `httponly` flag to the session cookie.
|
|
31
|
+
cookie_name (str, optional):
|
|
32
|
+
Name used for the client cookie.
|
|
33
|
+
prefix (str, optional):
|
|
34
|
+
Memcache keys will take the format of `prefix+session_id`;
|
|
35
|
+
specify the prefix here.
|
|
36
|
+
sessioncookie (bool, optional):
|
|
37
|
+
Specifies if the sent cookie should be a 'session cookie', i.e
|
|
38
|
+
no Expires or Max-age headers are included. Expiry is still
|
|
39
|
+
fully tracked on the server side. Default setting is False.
|
|
40
|
+
samesite (str, optional):
|
|
41
|
+
Will prevent the cookie from being sent by the browser to the target
|
|
42
|
+
site in all cross-site browsing context, even when following a regular link.
|
|
43
|
+
One of ('lax', 'strict')
|
|
44
|
+
Default: None
|
|
45
|
+
session_name (str, optional):
|
|
46
|
+
Name of the session that will be accessible through the request.
|
|
47
|
+
e.g. If ``session_name`` is ``alt_session``, it should be
|
|
48
|
+
accessed like that: ``request.ctx.alt_session``
|
|
49
|
+
e.g. And if ``session_name`` is left to default, it should be
|
|
50
|
+
accessed like that: ``request.ctx.session``
|
|
51
|
+
Default: 'session'
|
|
52
|
+
secure (bool, optional):
|
|
53
|
+
Adds the `Secure` flag to the session cookie.
|
|
54
|
+
"""
|
|
55
|
+
if aioredis is None:
|
|
56
|
+
raise RuntimeError("Please install aioredis: pip install sanic_session[aioredis]")
|
|
57
|
+
|
|
58
|
+
self.redis = redis
|
|
59
|
+
|
|
60
|
+
super().__init__(
|
|
61
|
+
expiry=expiry,
|
|
62
|
+
prefix=prefix,
|
|
63
|
+
head_name=head_name,
|
|
64
|
+
|
|
65
|
+
samesite=samesite,
|
|
66
|
+
session_name=session_name,
|
|
67
|
+
secure=secure,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
async def _get_value(self, prefix, sid):
|
|
71
|
+
return await self.redis.get(self.prefix + sid)
|
|
72
|
+
|
|
73
|
+
async def _delete_key(self, key):
|
|
74
|
+
await self.redis.delete(key)
|
|
75
|
+
|
|
76
|
+
async def _set_value(self, key, data):
|
|
77
|
+
await self.redis.setex(key, self.expiry, data)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import datetime
|
|
3
|
+
import abc
|
|
4
|
+
import uuid
|
|
5
|
+
from co6co.utils import log
|
|
6
|
+
from co6co.storage.Dict import CallbackDict
|
|
7
|
+
try:
|
|
8
|
+
import ujson
|
|
9
|
+
except ImportError:
|
|
10
|
+
import json as ujson
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_request_container(request):
|
|
14
|
+
"""
|
|
15
|
+
用于获取请求的容器对象
|
|
16
|
+
"""
|
|
17
|
+
return request.ctx.__dict__ if hasattr(request, "ctx") else request
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SessionDict(CallbackDict):
|
|
21
|
+
def __init__(self, initial=None, sid=None):
|
|
22
|
+
def on_update(self):
|
|
23
|
+
self.modified = True
|
|
24
|
+
|
|
25
|
+
super().__init__(initial, on_update)
|
|
26
|
+
|
|
27
|
+
self.sid = sid
|
|
28
|
+
self.modified = False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class IBaseSession(metaclass=abc.ABCMeta):
|
|
32
|
+
# this flag show does this Interface need request/response middleware hooks
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
# 会话的过期时间,单位为秒
|
|
36
|
+
self, expiry: int,
|
|
37
|
+
# 存储会话数据时使用的键前缀
|
|
38
|
+
prefix: str,
|
|
39
|
+
# 头部名称,可能用于自定义请求头或响应头
|
|
40
|
+
head_name: str,
|
|
41
|
+
# 用于设置 Cookie 的 SameSite 属性
|
|
42
|
+
samesite,
|
|
43
|
+
# 会话对象在请求容器中的名称
|
|
44
|
+
session_name,
|
|
45
|
+
# 是否使用安全的 Cookie(仅通过 HTTPS 传输)
|
|
46
|
+
secure,
|
|
47
|
+
):
|
|
48
|
+
|
|
49
|
+
self.expiry = expiry
|
|
50
|
+
self.prefix = prefix
|
|
51
|
+
self.head_name = head_name
|
|
52
|
+
self.samesite = samesite
|
|
53
|
+
self.session_name = session_name
|
|
54
|
+
self.secure = secure
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _calculate_expires(expiry):
|
|
58
|
+
expires = time.time() + expiry
|
|
59
|
+
return datetime.datetime.fromtimestamp(expires)
|
|
60
|
+
|
|
61
|
+
@abc.abstractmethod
|
|
62
|
+
async def _get_value(self, prefix: str, sid: str):
|
|
63
|
+
"""
|
|
64
|
+
Get value from datastore. Specific implementation for each datastore.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
prefix:
|
|
68
|
+
A prefix for the key, useful to namespace keys.
|
|
69
|
+
sid:
|
|
70
|
+
a uuid in hex string
|
|
71
|
+
"""
|
|
72
|
+
raise NotImplementedError
|
|
73
|
+
|
|
74
|
+
@abc.abstractmethod
|
|
75
|
+
async def _delete_key(self, key: str):
|
|
76
|
+
"""Delete key from datastore"""
|
|
77
|
+
raise NotImplementedError
|
|
78
|
+
|
|
79
|
+
@abc.abstractmethod
|
|
80
|
+
async def _set_value(self, key: str, data: SessionDict):
|
|
81
|
+
"""Set value for datastore"""
|
|
82
|
+
raise NotImplementedError
|
|
83
|
+
|
|
84
|
+
def getSid(self, request):
|
|
85
|
+
sid = request.headers.get(self.head_name)
|
|
86
|
+
return sid
|
|
87
|
+
|
|
88
|
+
async def open(self, request) -> SessionDict:
|
|
89
|
+
"""
|
|
90
|
+
Opens a session onto the request. Restores the client's session
|
|
91
|
+
from the datastore if one exists.The session data will be available on
|
|
92
|
+
`request.session`.
|
|
93
|
+
Args:
|
|
94
|
+
request (sanic.request.Request):
|
|
95
|
+
The request, which a sessionwill be opened onto.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
SessionDict:
|
|
99
|
+
the client's session data,
|
|
100
|
+
attached as well to `request.session`.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
sid = self. getSid(request)
|
|
104
|
+
if not sid:
|
|
105
|
+
sid = uuid.uuid4().hex
|
|
106
|
+
session_dict = SessionDict(sid=sid)
|
|
107
|
+
else:
|
|
108
|
+
val = await self._get_value(self.prefix, sid)
|
|
109
|
+
|
|
110
|
+
if val is not None:
|
|
111
|
+
data = ujson.loads(val)
|
|
112
|
+
session_dict = SessionDict(data, sid=sid)
|
|
113
|
+
else:
|
|
114
|
+
session_dict = SessionDict(sid=sid)
|
|
115
|
+
|
|
116
|
+
# attach the session data to the request, return it for convenience
|
|
117
|
+
req = get_request_container(request)
|
|
118
|
+
req[self.session_name] = session_dict
|
|
119
|
+
return session_dict
|
|
120
|
+
|
|
121
|
+
async def save(self, request, response) -> None:
|
|
122
|
+
"""Saves the session to the datastore.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
request (sanic.request.Request):
|
|
126
|
+
The sanic request which has an attached session.
|
|
127
|
+
response (sanic.response.Response):
|
|
128
|
+
The Sanic response. Cookies with the appropriate expiration
|
|
129
|
+
will be added onto this response.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
None
|
|
133
|
+
"""
|
|
134
|
+
req = get_request_container(request)
|
|
135
|
+
if self.session_name not in req:
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
key = self.prefix + req[self.session_name].sid
|
|
139
|
+
if not req[self.session_name]:
|
|
140
|
+
await self._delete_key(key)
|
|
141
|
+
|
|
142
|
+
val = ujson.dumps(dict(req[self.session_name]))
|
|
143
|
+
await self._set_value(key, val)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
from .base import IBaseSession
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
import aiomcache
|
|
7
|
+
except ImportError: # pragma: no cover
|
|
8
|
+
aiomcache = None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MemcacheSessionImp(IBaseSession):
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
memcache_connection,
|
|
15
|
+
domain: str = None,
|
|
16
|
+
expiry: int = 2592000,
|
|
17
|
+
httponly: bool = True,
|
|
18
|
+
cookie_name: str = "session",
|
|
19
|
+
prefix: str = "session:",
|
|
20
|
+
sessioncookie: bool = False,
|
|
21
|
+
samesite: str = None,
|
|
22
|
+
session_name: str = "Session",
|
|
23
|
+
secure: bool = False,
|
|
24
|
+
):
|
|
25
|
+
"""Initializes the interface for storing client sessions in memcache.
|
|
26
|
+
Requires a client object establised with `asyncio_memcache`.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
memcache_connection (aiomccache.Client):
|
|
30
|
+
The memcache client used for interfacing with memcache.
|
|
31
|
+
domain (str, optional):
|
|
32
|
+
Optional domain which will be attached to the cookie.
|
|
33
|
+
expiry (int, optional):
|
|
34
|
+
Seconds until the session should expire.
|
|
35
|
+
httponly (bool, optional):
|
|
36
|
+
Adds the `httponly` flag to the session cookie.
|
|
37
|
+
cookie_name (str, optional):
|
|
38
|
+
Name used for the client cookie.
|
|
39
|
+
prefix (str, optional):
|
|
40
|
+
Memcache keys will take the format of `prefix+session_id`;
|
|
41
|
+
specify the prefix here.
|
|
42
|
+
sessioncookie (bool, optional):
|
|
43
|
+
Specifies if the sent cookie should be a 'session cookie', i.e
|
|
44
|
+
no Expires or Max-age headers are included. Expiry is still
|
|
45
|
+
fully tracked on the server side. Default setting is False.
|
|
46
|
+
samesite (str, optional):
|
|
47
|
+
Will prevent the cookie from being sent by the browser to
|
|
48
|
+
the target site in all cross-site browsing context, even when
|
|
49
|
+
following a regular link. One of ('lax', 'strict')
|
|
50
|
+
Default: None
|
|
51
|
+
session_name (str, optional):
|
|
52
|
+
Name of the session that will be accessible through the
|
|
53
|
+
request.
|
|
54
|
+
e.g. If ``session_name`` is ``alt_session``, it should be
|
|
55
|
+
accessed like that: ``request.ctx.alt_session``
|
|
56
|
+
e.g. And if ``session_name`` is left to default, it should be
|
|
57
|
+
accessed like that: ``request.ctx.session``
|
|
58
|
+
Default: 'session'
|
|
59
|
+
secure (bool, optional):
|
|
60
|
+
Adds the `Secure` flag to the session cookie.
|
|
61
|
+
"""
|
|
62
|
+
if aiomcache is None:
|
|
63
|
+
raise RuntimeError("Please install aiomcache: pip install " "sanic_session[aiomcache]")
|
|
64
|
+
|
|
65
|
+
self.memcache_connection = memcache_connection
|
|
66
|
+
|
|
67
|
+
if expiry > 2592000:
|
|
68
|
+
warnings.warn("Memcache has a maximum 30-day cache limit")
|
|
69
|
+
expiry = 0
|
|
70
|
+
|
|
71
|
+
super().__init__(
|
|
72
|
+
expiry=expiry,
|
|
73
|
+
prefix=prefix,
|
|
74
|
+
cookie_name=cookie_name,
|
|
75
|
+
domain=domain,
|
|
76
|
+
httponly=httponly,
|
|
77
|
+
sessioncookie=sessioncookie,
|
|
78
|
+
samesite=samesite,
|
|
79
|
+
session_name=session_name,
|
|
80
|
+
secure=secure,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
async def _get_value(self, prefix, sid):
|
|
84
|
+
key = (self.prefix + sid).encode()
|
|
85
|
+
value = await self.memcache_connection.get(key)
|
|
86
|
+
return value.decode() if value else None
|
|
87
|
+
|
|
88
|
+
async def _delete_key(self, key):
|
|
89
|
+
return await self.memcache_connection.delete(key.encode())
|
|
90
|
+
|
|
91
|
+
async def _set_value(self, key, data):
|
|
92
|
+
return await self.memcache_connection.set(key.encode(), data.encode(), exptime=self.expiry)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from .base import IBaseSession
|
|
2
|
+
from co6co.storage.Dict import ExpiringDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class MemorySessionImp(IBaseSession):
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
expiry: int = 2592000,
|
|
9
|
+
head_name: str = "session",
|
|
10
|
+
prefix: str = "session:",
|
|
11
|
+
samesite: str = None,
|
|
12
|
+
session_name="Session",
|
|
13
|
+
secure: bool = False,
|
|
14
|
+
):
|
|
15
|
+
|
|
16
|
+
super().__init__(
|
|
17
|
+
expiry=expiry,
|
|
18
|
+
prefix=prefix,
|
|
19
|
+
head_name=head_name,
|
|
20
|
+
samesite=samesite,
|
|
21
|
+
session_name=session_name,
|
|
22
|
+
secure=secure,
|
|
23
|
+
)
|
|
24
|
+
self.session_store = ExpiringDict()
|
|
25
|
+
|
|
26
|
+
async def _get_value(self, prefix, sid):
|
|
27
|
+
return self.session_store.get(self.prefix + sid)
|
|
28
|
+
|
|
29
|
+
async def _delete_key(self, key):
|
|
30
|
+
if key in self.session_store:
|
|
31
|
+
self.session_store.delete(key)
|
|
32
|
+
|
|
33
|
+
async def _set_value(self, key, data):
|
|
34
|
+
self.session_store.set(key, data, self.expiry)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
import warnings
|
|
3
|
+
from .base import IBaseSession
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
from sanic_motor import BaseModel
|
|
7
|
+
|
|
8
|
+
class _SessionModel(BaseModel):
|
|
9
|
+
"""Collection for session storing.
|
|
10
|
+
|
|
11
|
+
Collection name (default session)
|
|
12
|
+
|
|
13
|
+
Fields:
|
|
14
|
+
sid
|
|
15
|
+
expiry
|
|
16
|
+
data:
|
|
17
|
+
User's session data
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
except ImportError: # pragma: no cover
|
|
24
|
+
_SessionModel = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MongoDBSessionImp(IBaseSession):
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
app,
|
|
31
|
+
coll: str = "session",
|
|
32
|
+
domain: str = None,
|
|
33
|
+
expiry: int = 30 * 24 * 60 * 60,
|
|
34
|
+
httponly: bool = True,
|
|
35
|
+
hand_name: str = "session",
|
|
36
|
+
sessioncookie: bool = False,
|
|
37
|
+
samesite: str = None,
|
|
38
|
+
session_name: str = "Session",
|
|
39
|
+
secure: bool = False,
|
|
40
|
+
):
|
|
41
|
+
"""Initializes the interface for storing client sessions in MongoDB.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
app (sanic.Sanic):
|
|
45
|
+
Sanic instance to register listener('after_server_start')
|
|
46
|
+
coll (str, optional):
|
|
47
|
+
MongoDB collection name for session
|
|
48
|
+
domain (str, optional):
|
|
49
|
+
Optional domain which will be attached to the cookie.
|
|
50
|
+
expiry (int, optional):
|
|
51
|
+
Seconds until the session should expire.
|
|
52
|
+
httponly (bool, optional):
|
|
53
|
+
Adds the `httponly` flag to the session cookie.
|
|
54
|
+
cookie_name (str, optional):
|
|
55
|
+
Name used for the client cookie.
|
|
56
|
+
sessioncookie (bool, optional):
|
|
57
|
+
Specifies if the sent cookie should be a 'session cookie', i.e
|
|
58
|
+
no Expires or Max-age headers are included. Expiry is still
|
|
59
|
+
fully tracked on the server side. Default setting is False.
|
|
60
|
+
samesite (str, optional):
|
|
61
|
+
Will prevent the cookie from being sent by the browser to
|
|
62
|
+
the target site in all cross-site browsing context, even when
|
|
63
|
+
following a regular link.
|
|
64
|
+
One of ('lax', 'strict')
|
|
65
|
+
Default: None
|
|
66
|
+
session_name (str, optional):
|
|
67
|
+
Name of the session that will be accessible through the
|
|
68
|
+
request.
|
|
69
|
+
e.g. If ``session_name`` is ``alt_session``, it should be
|
|
70
|
+
accessed like that: ``request.ctx.alt_session``
|
|
71
|
+
e.g. And if ``session_name`` is left to default, it should be
|
|
72
|
+
accessed like that: ``request.ctx.session``
|
|
73
|
+
Default: 'session'
|
|
74
|
+
secure (bool, optional):
|
|
75
|
+
Adds the `Secure` flag to the session cookie.
|
|
76
|
+
"""
|
|
77
|
+
if _SessionModel is None:
|
|
78
|
+
raise RuntimeError("Please install Mongo dependencies: " "pip install sanic_session[mongo]")
|
|
79
|
+
|
|
80
|
+
# prefix not needed for mongodb as mongodb uses uuid4 natively
|
|
81
|
+
prefix = ""
|
|
82
|
+
|
|
83
|
+
if httponly is not True:
|
|
84
|
+
warnings.warn(
|
|
85
|
+
"""
|
|
86
|
+
httponly default arg has changed.
|
|
87
|
+
To spare you some debugging time, httponly is currently
|
|
88
|
+
hardcoded as True. This message will be removed with the
|
|
89
|
+
next release. And ``httponly`` will no longer be hardcoded
|
|
90
|
+
""",
|
|
91
|
+
DeprecationWarning,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
super().__init__(
|
|
95
|
+
expiry=expiry,
|
|
96
|
+
prefix=prefix,
|
|
97
|
+
hand_name=hand_name,
|
|
98
|
+
samesite=samesite,
|
|
99
|
+
session_name=session_name,
|
|
100
|
+
secure=secure,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# set collection name
|
|
104
|
+
_SessionModel.__coll__ = coll
|
|
105
|
+
|
|
106
|
+
@app.listener("after_server_start")
|
|
107
|
+
async def apply_session_indexes(app, loop):
|
|
108
|
+
"""Create indexes in session collection
|
|
109
|
+
if doesn't exist.
|
|
110
|
+
|
|
111
|
+
Indexes:
|
|
112
|
+
sid:
|
|
113
|
+
For faster lookup.
|
|
114
|
+
expiry:
|
|
115
|
+
For document expiration.
|
|
116
|
+
"""
|
|
117
|
+
await _SessionModel.create_index("sid")
|
|
118
|
+
await _SessionModel.create_index("expiry", expireAfterSeconds=0)
|
|
119
|
+
|
|
120
|
+
async def _get_value(self, prefix, key):
|
|
121
|
+
value = await _SessionModel.find_one({"sid": key}, as_raw=True)
|
|
122
|
+
return value["data"] if value else None
|
|
123
|
+
|
|
124
|
+
async def _delete_key(self, key):
|
|
125
|
+
await _SessionModel.delete_one({"sid": key})
|
|
126
|
+
|
|
127
|
+
async def _set_value(self, key, data):
|
|
128
|
+
expiry = datetime.utcnow() + timedelta(seconds=self.expiry)
|
|
129
|
+
await _SessionModel.replace_one({"sid": key}, {"sid": key, "expiry": expiry, "data": data}, upsert=True)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
from co6co_web_session.base import IBaseSession
|
|
3
|
+
|
|
4
|
+
try:
|
|
5
|
+
import asyncio_redis
|
|
6
|
+
except ImportError:
|
|
7
|
+
asyncio_redis = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RedisSessionImp(IBaseSession):
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
redis_getter: Callable,
|
|
14
|
+
domain: str = None,
|
|
15
|
+
expiry: int = 2592000,
|
|
16
|
+
httponly: bool = True,
|
|
17
|
+
head_anme: str = "session",
|
|
18
|
+
prefix: str = "session:",
|
|
19
|
+
sessioncookie: bool = False,
|
|
20
|
+
samesite: str = None,
|
|
21
|
+
session_name: str = "Session",
|
|
22
|
+
secure: bool = False,
|
|
23
|
+
):
|
|
24
|
+
"""Initializes a session interface backed by Redis.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
redis_getter (Callable):
|
|
28
|
+
Coroutine which should return an asyncio_redis connection pool
|
|
29
|
+
(suggested) or an asyncio_redis Redis connection.
|
|
30
|
+
domain (str, optional):
|
|
31
|
+
Optional domain which will be attached to the cookie.
|
|
32
|
+
expiry (int, optional):
|
|
33
|
+
Seconds until the session should expire.
|
|
34
|
+
httponly (bool, optional):
|
|
35
|
+
Adds the `httponly` flag to the session cookie.
|
|
36
|
+
cookie_name (str, optional):
|
|
37
|
+
Name used for the client cookie.
|
|
38
|
+
prefix (str, optional):
|
|
39
|
+
Memcache keys will take the format of `prefix+session_id`;
|
|
40
|
+
specify the prefix here.
|
|
41
|
+
sessioncookie (bool, optional):
|
|
42
|
+
Specifies if the sent cookie should be a 'session cookie', i.e
|
|
43
|
+
no Expires or Max-age headers are included. Expiry is still
|
|
44
|
+
fully tracked on the server side. Default setting is False.
|
|
45
|
+
samesite (str, optional):
|
|
46
|
+
Will prevent the cookie from being sent by the browser to the
|
|
47
|
+
target site in all cross-site browsing context, even when
|
|
48
|
+
following a regular link.
|
|
49
|
+
One of ('lax', 'strict')
|
|
50
|
+
Default: None
|
|
51
|
+
session_name (str, optional):
|
|
52
|
+
Name of the session that will be accessible through the
|
|
53
|
+
request.
|
|
54
|
+
e.g. If ``session_name`` is ``alt_session``, it should be
|
|
55
|
+
accessed like that: ``request.ctx.alt_session``
|
|
56
|
+
e.g. And if ``session_name`` is left to default, it should be
|
|
57
|
+
accessed like that: ``request.ctx.session``
|
|
58
|
+
Default: 'session'
|
|
59
|
+
secure (bool, optional):
|
|
60
|
+
Adds the `Secure` flag to the session cookie.
|
|
61
|
+
"""
|
|
62
|
+
if asyncio_redis is None:
|
|
63
|
+
raise RuntimeError("Please install asyncio_redis: pip install sanic_session[redis]")
|
|
64
|
+
|
|
65
|
+
self.redis_getter = redis_getter
|
|
66
|
+
|
|
67
|
+
super().__init__(
|
|
68
|
+
expiry=expiry,
|
|
69
|
+
prefix=prefix,
|
|
70
|
+
head_anme=head_anme,
|
|
71
|
+
samesite=samesite,
|
|
72
|
+
session_name=session_name,
|
|
73
|
+
secure=secure,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
async def _get_value(self, prefix, key):
|
|
77
|
+
redis_connection = await self.redis_getter()
|
|
78
|
+
return await redis_connection.get(prefix + key)
|
|
79
|
+
|
|
80
|
+
async def _delete_key(self, key):
|
|
81
|
+
redis_connection = await self.redis_getter()
|
|
82
|
+
await redis_connection.delete([key])
|
|
83
|
+
|
|
84
|
+
async def _set_value(self, key, data):
|
|
85
|
+
redis_connection = await self.redis_getter()
|
|
86
|
+
await redis_connection.setex(key, self.expiry, data)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
|
|
2
|
+
from .memory import MemorySessionImp
|
|
3
|
+
from .base import IBaseSession
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Session:
|
|
7
|
+
|
|
8
|
+
def __init__(self, app=None, interface: IBaseSession = None):
|
|
9
|
+
self.interface = None
|
|
10
|
+
if app:
|
|
11
|
+
self.init_app(app, interface)
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def expiry(self):
|
|
15
|
+
"""
|
|
16
|
+
session 过期时间
|
|
17
|
+
"""
|
|
18
|
+
return self.interface.expiry if self.interface else None
|
|
19
|
+
|
|
20
|
+
def init_app(self, app, interface: IBaseSession):
|
|
21
|
+
self.interface = interface or MemorySessionImp()
|
|
22
|
+
if not hasattr(app.ctx, "extensions"):
|
|
23
|
+
app.ctx.extensions = {}
|
|
24
|
+
|
|
25
|
+
app.ctx.extensions[self.interface.session_name] = self # session_name defaults to 'session'
|
|
26
|
+
|
|
27
|
+
# @app.middleware('request')
|
|
28
|
+
async def add_session_to_request(request):
|
|
29
|
+
"""Before each request initialize a session
|
|
30
|
+
using the client's request."""
|
|
31
|
+
await self.interface.open(request)
|
|
32
|
+
|
|
33
|
+
# @app.middleware('response')
|
|
34
|
+
async def save_session(request, response):
|
|
35
|
+
"""After each request save the session, pass
|
|
36
|
+
the response to set client cookies.
|
|
37
|
+
"""
|
|
38
|
+
await self.interface.save(request, response)
|
|
39
|
+
|
|
40
|
+
app.request_middleware.appendleft(add_session_to_request)
|
|
41
|
+
app.response_middleware.append(save_session)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from .base import get_request_container
|
|
3
|
+
|
|
4
|
+
# 只做一些备份 没作用
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _delete_cookie(self, request, response):
|
|
8
|
+
req = get_request_container(request)
|
|
9
|
+
response.cookies[self.cookie_name] = req[self.session_name].sid
|
|
10
|
+
|
|
11
|
+
# We set expires/max-age even for session cookies to force expiration
|
|
12
|
+
response.cookies[self.cookie_name]["expires"] = datetime.datetime.utcnow()
|
|
13
|
+
response.cookies[self.cookie_name]["max-age"] = 0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def getSid(self, request):
|
|
17
|
+
return request.cookies.get(self.cookie_name)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from os import path
|
|
2
|
+
from setuptools import setup, find_packages
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
packages = find_packages()
|
|
6
|
+
packageName = packages[0]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_version():
|
|
10
|
+
package_dir = path.abspath(path.dirname(__file__))
|
|
11
|
+
version_file = path.join(package_dir, packageName, 'versions.py')
|
|
12
|
+
with open(version_file, "rb") as f:
|
|
13
|
+
source_code = f.read()
|
|
14
|
+
exec_code = compile(source_code, version_file, "exec")
|
|
15
|
+
scope = {}
|
|
16
|
+
exec(exec_code, scope)
|
|
17
|
+
version = scope.get("__version__", None)
|
|
18
|
+
if version:
|
|
19
|
+
return version
|
|
20
|
+
raise RuntimeError("Unable to find version string.")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# read readmeFile contents
|
|
24
|
+
currentDir = path.abspath(path.dirname(__file__))
|
|
25
|
+
with open(path.join(currentDir, 'README.md'), encoding='utf-8') as f:
|
|
26
|
+
long_description = f.read()
|
|
27
|
+
|
|
28
|
+
setup(
|
|
29
|
+
name=packageName.replace('_', '.', 1),
|
|
30
|
+
version=get_version(),
|
|
31
|
+
description="web session 扩展",
|
|
32
|
+
packages=packages,
|
|
33
|
+
long_description=long_description,
|
|
34
|
+
long_description_content_type='text/markdown',
|
|
35
|
+
classifiers=["Programming Language :: Python :: 3", "Programming Language :: Python :: 3.6"],
|
|
36
|
+
include_package_data=True, zip_safe=True,
|
|
37
|
+
# 依赖哪些模块
|
|
38
|
+
install_requires=["co6co"],
|
|
39
|
+
# package_dir= {'utils':'src/log','main_package':'main'},#告诉Distutils哪些目录下的文件被映射到哪个源码
|
|
40
|
+
author='co6co',
|
|
41
|
+
author_email='co6co@qq.com',
|
|
42
|
+
url="http://github.com/co6co",
|
|
43
|
+
data_file={
|
|
44
|
+
('', "*.txt"),
|
|
45
|
+
('', "*.md"),
|
|
46
|
+
},
|
|
47
|
+
package_data={
|
|
48
|
+
'': ['*.txt', '*.md'],
|
|
49
|
+
'bandwidth_reporter': ['*.txt']
|
|
50
|
+
}
|
|
51
|
+
)
|