aiohttp session

cookie和session

服务端和客户端之间的联系一般是根据http协议来进行的 但是http协议是无状态的 无状态会有状态的概念大概如下

有状态

  1. 甲:你吃午饭了吗
  2. 乙:吃了
  3. 甲:吃了什么菜
  4. 乙:吃了鸡蛋

无状态

  1. 甲:你吃午饭了吗
  2. 乙:吃了
  3. 甲:吃了什么菜
  4. 乙:你说什么时候吃什么菜 早饭?午饭?还是晚饭

因此服务端和客户端之间需要一种机制来记录用户的状态 这种机制在客户端那边是cookies 在服务端那边是session
cookies可以存储在磁盘 内存里面 session可以存储在内存 缓存 redis甚至磁盘中


aiohttp session

flask框架具有内置的session机制 我以前用起来觉得非常方便以至于我觉得这个不重要 aiohttp不具有内置的session机制 但是具有第三方模块支持 aiohttp_session

安装这个第三方模块真的挺费事 因为依赖模块实在太多了 而且aiohttp_session是可以依赖redis作为缓存的 还需要安装redis和一些相关模块 下面的开始的实例我没有使用redis作为存储位置 而是使用了EncryptedCookieStorage 后面附加了使用redis存储session的例子

dependencies

  • python3.5.3+
  • cryptography
  • aioredis

安装模块

  • pip3 install cryptography(必要时进行upgrade 可能版本太低)
  • pip3 install aiohttp_session[secure]
  • pip3 install aiohttp_session[aioredis] (需要安装redis libhiredis-dev hiredis libssl-dev)
  • pip3 install aiohttp_session[aiomcache]
  • pip3 install aiohttp_sessionsss

    上面的安装问题真的太多 谷歌好几次才解决


存储位置选择

所有仓库是使用名为AIOHTTP_COOKIE_SESSION的对象来进行存储数据的

可以使用的仓库有

  • SimpleCookieStorage 把session数据以明文json字符串的格式存在cookies body中 只用来测试 非常不安全
  • EncryptedCookieStorage 存储数据在cookie中的格式跟SimpleCookieStorage一样 但是使用了cryptography的Fernet cipher加密算法进行加密
  • RedisStorage 存json化的数据到redis中 格式是name+reids key
  • MemcachedStorage 跟RedisStorage类似 但是使用memcache数据库进行存储

使用

使用上跟flask的session大同小异 但是需要在init app阶段将session安装到app上 设定一个32位bytes作为加密密钥 跟flask session的secret key类似

session是一个dict like的对象

设定session

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from aiohttp import web
import datetime
from aiohttp_session import get_session

async def login(request):
engine = await aio_engine.init_engine()
data = await request.json()
print("data", data)
if "name" not in data or "psw" not in data:
return web.json_response({
"status": False
})
name = data["name"]
psw = data["psw"]
verify = await keeper.verify(engine, name = name, psw = psw)
if verify:
session = await get_session(request)
session["ooad"] = name
session["login"] = psw
session["time"] = str(datetime.datetime.now())
return web.json_response({
"status": True
})
return web.json_response({
"status": False
})

检查是否含有具体session字段值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from aiohttp import web
import datetime
from aiohttp_session import get_session

async def need_cookies_page(request):
engine = await aio_engine.init_engine()
session = await get_session(request)
if "ooad" not in session or "login" not in session or "time" not in session:
return web.json_response({
"status": False
})
name = session["ooad"]
psw = session["login"]
r = await keeper.verify(engine, name = name, psw = psw)
if r:
return web.json_response({
"status": True
})
return web.json_response({
"status": False
})
`

setup app

1
setup(app, EncryptedCookieStorage(b'Thirty  two  length  bytes  key.'))


效果

login result
image_1cfrgldqfjrht9n1gva149ocj423.png-72.7kB
login cookies
image_1cfrgm0d8nov172b2mj10ta4g62g.png-46.1kB
cookies check
image_1cfrgn8mh1vf717qu1elcqdv8ur2t.png-43.3kB


使用redis存储session

这里有个问题 就是aiohttp本事内置一个asyncio event loop执行所有跟app相关的协程了 但是当我们使用redis作为仓库进行存储session的时候需要使用到aioredis进行支持 但是这个也是异步io连接模块 因此我们初始化redis pool的时候也需要一个event loop进行运行这个初始化的协程 一旦这个event loop跟web.run_app混在一起就会报错 event loop is already run

正确的做法是分开 并且在执行redis pool连接的event loop run_until_complete处获取返回值 得到需要的storage

1
2
3
4
5
6
7
8
9
10
11
12
from aiohttp_session import setup, redis_storage
import aioredis
import asyncio

async def get_storage():
redis = await aioredis.create_pool(("localhost", 6379))
return redis_storage.RedisStorage(redis)

def setup_session_support(app):
storage = asyncio.get_event_loop().run_until_complete(get_storage())
setup(app, storage)
return app
1
app = session_redis.setup_session_support(app)

效果
可以看到生成的cookies值确实存在了redis中
postman中获取的cookies
image_1cfs5ia7pm0mke1hcn13edld9m.png-18.6kB
redis中也存有一样的cookies
image_1cfs5h8s6st01coa4491gh0bic9.png-20kB