发了一个论文在互联网文章上,发明html, http。
http特点:
http是无状态的,短连接的(长连接也是有时效的).
无状态,服务端无法知道这一次连接是谁? 解决: cookie+session; token
有状态,表示服务端和客户端有数据交互。服务端会为客户端存放数据。这一次请求和上一次请求有内在关系
。
没有价值的数据,可以不需要识别用户。有价值的数据,就需要识别用户。sessionid
http前一次请求和后一次请求,有没有关系, 服务器不知道。
cookie
, 浏览器可以存储一个数据,这个数据跟你访问的域名有关,是kv对 叫“cookie值”
,你对这个域名发起了http请求,浏览器就会自动把这个数据kv对
,自动放在请求头,发给服务器端。
域名相关数据
中,下一回访问服务器时, 你会带上这些cookie值
纯cookie
并不能解决服务端状态问题,光有cookie服务端并不知道你是谁,还需要session。在Hash表中基于sessionid
查出用户。这样就解决了状态问题
- 往往用户名,密码登录成功(authentication ok)后才会给你发token,hash值,sessionid。
- 登陆后,将sessionid
关联的用户的数据
记录到表。这些就是用户的行为- session是在服务端保存的值,实现状态管理,同时返回一个token值(
Sessionid
或sid
,hash值)cookie。- hash表保存
- redis 内存中:查询快 高效,内存占用大。 数据是暂时的。
- mysql 磁盘中:查询慢。
cookie避免重复利用:客户端: cookie过期快 1s后过期,sessionstorage存储。服务端:过期前可以对同一个sessionid,set-cookie重置过期时间。
sessionid多久过期?过期之后浏览器会删除sessionid(cookie值);
电商网站,必须定期过期。用户想方便,可以不定期过期。只要用户一直在电脑前可以一直续期。
非电商网站,可以免登陆。
- 用户点了登出按钮, 请求到服务器端。服务端将sessionid关联的
session()
删除,响应给用户时,响应首部添加set-cookie: sessionid=''
- 真实场景是我们关浏览器就清理了cookie。并没有发logout请求。服务端hash表的key也有过期值, 过期也会清理session。 用户重新登陆后,服务器给用户一个全新的sessionid,而不是上次未过期的sessionid。
cookie是浏览器中,放在内存中的数据,这个数据与域相关,每次请求这个域就会自动带上这些数据,发到服务器。
键 | 描述 |
---|---|
sessionid | 首次请求为用户生成Session()对象中的一个属性 |
expiredate | 内存中,重启浏览器,cookie就没有了;不重启时,过期时间到达浏览器会自动清理cookie |
domain | 默认当前域 blog.mykernel.cn;如果指定mykernel.cn表示blog.mykernel.cn 或 www.mykernel.cn ; 只要对满足的域请求,会自动带上cookie |
path | / 表示匹配的域,所有路径请求时,会自动带cookie;/app表示/app/index.html会带cookie;而/app1路径的请求就不会自动携带cookie; |
httponly | cookie只能自动发到server或server发来数据存放到浏览器,javascript不能读cookie; |
secure | 发送cookie必须https |
不要将密码放在cookie中
;负载均衡基于用户粘在某个后端主机上。不合适服务器弹性扩展
tomcat session复制。
deltasession, 增量session类,session增删时,会通知其他服务器进行增删。
优点:每台session是冗余的
缺点:网络开销,每台服务器全量session,占用大量内存
如果来100万个用户在使用,内存耗不够。小场景可以使用。
为什么要100台tomcat同步,为什么要这么大的网络?可以把服务子模块化,不同业务不同tomcat服务器。
session server。memcached
redis
旧部署memcached,完全够了。
新部署redis,数据类型丰富,但是存储session,redis用不了这么多数据类型。
带来的
sessionid
先在内存访问,没有的话,再从memcached/redis访问。以主机内存为主,以外部存储为辅助。万一memcached挂了,所以2个memcached/redis.
使用以上架构:日活多少?峰值多少? 不需要考虑这些架构。
django支持sesssion
检测sessionid,带了sessionid检测是否有效? 用户是否有效,由 django.contrib.auth.middleware.AuthenticationMiddleware 决定
MIDDLEWARE = [ 'django.contrib.sessions.middleware.SessionMiddleware',
2次shift字符串
python def process_request(self, request): # session是否有效
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME) # 获取sessionid
request.session = self.SessionStore(session_key) # 与django_session表交互, 并在request上添加属性session
python def process_response(self, request, response): # session过期,续期,删除session.
try:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
return response
# First check if we need to delete this cookie.
# The session should be deleted only if the session is entirely empty.
if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
response.delete_cookie(
settings.SESSION_COOKIE_NAME,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
samesite=settings.SESSION_COOKIE_SAMESITE,
)
patch_vary_headers(response, ('Cookie',))
app,用来颁发sessionid,管理session是基于backends 数据库存储session?还是内存?
INSTALLED_APPS = [ 'django.contrib.sessions',
session不使用可以关中间件和app
数据库表使用django_session表,记录session。可以使用mysql或redis存储session。
mysql表django_session 记录了session,优点是服务崩溃session在mysql不丢失。
sqlmysql> SHOW TABLES;
+----------------------------+
| Tables_in_t39 |
+----------------------------+
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
10 rows in set (0.00 sec)
mysql> SELECT * FROM django_session;
Empty set (0.00 sec)
mysql> DESC django_session;
+--------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+-------+
| session_key | varchar(40) | NO | PRI | NULL | | # sessionid
| session_data | longtext | NO | | NULL | | # sessionid关联的字典,内存中的数据序列化之后存储的。
| expire_date | datetime(6) | NO | MUL | NULL | | # sessionid过期时间
+--------------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
url
urlpatterns = [ # 暴露handler函数,直接是函数 # path('', test), # 列表页, /emps/ test = require_GET(test) => inner # path('<int:id>', test_detail), # 详情页, restful # 暴露handler函数,装饰出来的handler函数 path('', require_GET(test)), # /emps/ 到 test 函数 require_GET(test) = inner == inner(request, *args, **kwargs) path('ts3/',TestView.as_view()), ]
view
def test(request: HttpRequest, **kwargs): print('~'*30) print(request) # <WSGIRequest: GET '/'> print(kwargs) print(request.session, '-'*30)# session print('~'*30) return JsonResponse([ (1, 'python', 3), (2, 'c++', 3) ], safe=False) # json的数据类型, 字符串; json.dumps() -> str python 列表 => js 数组; python 字典 => js 对象
响应
python[19/May/2022 11:10:07] "GET /emps/ HTTP/1.1" 200 33
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<WSGIRequest: GET '/emps/'>
{}
<django.contrib.sessions.backends.db.SessionStore object at 0x000001695D2F3910> ------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
view
pythondef test(request: HttpRequest, **kwargs):
print('~' * 30)
print(request) # <WSGIRequest: GET '/'>
print(kwargs)
print(request.session.get('userdata'), '-' * 30) # session
request.session['userdata'] = random.randint(100, 200) # 对当前这个请求, 分配session, 响应sessionid;将session数据保存到mysql。
print(request.session) # 保存了一个sessionid
print('~' * 30)
return JsonResponse([
(1, 'python', 3),
(2, 'c++', 3)
], safe=False) # json的数据类型, 字符串; json.dumps() -> str python 列表 => js 数组; python 字典 => js 对象
现在请求 http://127.0.0.1:8000/emps/
注意sessionid
sessionid=4u6mxvt03z12vwx4aqmp2u7i142xgbqf; expires=Thu, 02 Jun 2022 05:01:14 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax
同时header中,会自动携带这个cookie
这一次python请求
pythonQuit the server with CTRL-BREAK.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<WSGIRequest: GET '/emps/'>
{}
None ------------------------------
<django.contrib.sessions.backends.db.SessionStore object at 0x000001FCE6CA76D0>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
(0.000)
SELECT VERSION(),
@@sql_mode,
@@default_storage_engine,
@@sql_auto_is_null,
@@lower_case_table_names,
CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
; args=None
(0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
(0.000) SELECT (1) AS `a` FROM `django_session` WHERE `django_session`.`session_key` = '4u6mxvt03z12vwx4aqmp2u7i142xgbqf' LIMIT 1; args=('4u6mxvt03z12vwx4aqmp2u7i142xgbqf',)
(0.000) INSERT INTO `django_session` (`session_key`, `session_data`, `expire_date`) VALUES ('4u6mxvt03z12vwx4aqmp2u7i142xgbqf', 'eyJ1c2VyZGF0YSI6MTAyfQ:1nrYHi:uzUbfMebFTi1j6PrjoXdcIqEE5ZwPpOeiJ_wN9KkGg8', '2022-06-02 05:01:14.428641'); args=('4u6mxvt03z12vwx4aqmp2u7i142xgbqf', 'eyJ1c2VyZGF0YSI6MTAyfQ:1nrYHi:uzUbfMebFTi1j6PrjoXdcIqEE5ZwPpOeiJ_wN9KkGg8', '2022-06-02 05:01:14.428641')
[19/May/2022 13:01:14] "GET /emps/ HTTP/1.1" 200 33
查看mysql
sqlmysql> SELECT * FROM django_session\G;
*************************** 1. row ***************************
session_key: 4u6mxvt03z12vwx4aqmp2u7i142xgbqf
session_data: eyJ1c2VyZGF0YSI6MTAyfQ:1nrYHi:uzUbfMebFTi1j6PrjoXdcIqEE5ZwPpOeiJ_wN9KkGg8
expire_date: 2022-06-02 05:01:14.428641
1 row in set (0.00 sec)
ERROR:
No query specified
注意:session_data就是我们上面保存的数据,进行序列化的结果。他有过期时间。
view 现在不用更新此值了,查看现在的值。
diffdef test(request: HttpRequest, **kwargs):
print('~' * 30)
print(request) # <WSGIRequest: GET '/'>
print(kwargs)
print(request.session.get('userdata'), '-' * 30) # session
+ # request.session['userdata'] = random.randint(100, 200) # 对当前这个请求, 分配session, 响应sessionid;将session数据保存到mysql。
print(request.session) # 保存了一个sessionid
+ # request.session对象 是 django.contrib.sessions.backends.db.SessionStore 类的实例,此类的父类有 __getitem__, __setitem__, __delitem__ 方法,说明是字典。
+ print(*request.session.items(),sep='\n') # ('userdata', 102)
+ print(request.COOKIES) # {'sessionid': '4u6mxvt03z12vwx4aqmp2u7i142xgbqf'}
print('~' * 30)
return JsonResponse([
(1, 'python', 3),
(2, 'c++', 3)
], safe=False) # json的数据类型, 字符串; json.dumps() -> str python 列表 => js 数组; python 字典 => js 对象
相同cookie请求时,会基于sessionid查询mysql,且未过期。有记录拿到sessionid相关值。
pythonQuit the server with CTRL-BREAK.
(0.000)
SELECT VERSION(),
@@sql_mode,
@@default_storage_engine,
@@sql_auto_is_null,
@@lower_case_table_names,
CONVERT_TZ('2001-01-01 01:00:00', 'UTC', 'UTC') IS NOT NULL
; args=None
(0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
(0.000) SELECT `django_session`.`session_key`, `django_session`.`session_data`, `django_session`.`expire_date` FROM `django_session` WHERE (`django_session`.`expire_date` > '2022-05-19 05:07:41.062909' AND `django_session`.`session_key` = '4u6mxvt03z12vwx4aqmp2u7i142xgbqf') LIMIT 21; args=('2022-05-19 05:07:41.062909', '4u6mxvt03z12vwx4aqmp2u7i142xgbqf')
[19/May/2022 13:07:41] "GET /emps/ HTTP/1.1" 200 33
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<WSGIRequest: GET '/emps/'>
{}
102 ------------------------------
<django.contrib.sessions.backends.db.SessionStore object at 0x000001F893871490>
('userdata', 102)
{'sessionid': '4u6mxvt03z12vwx4aqmp2u7i142xgbqf'}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pythonimport os, django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup(set_prefix=False)
######################### 测试 #########################
from django.contrib.sessions.models import Session # 对应django_session表
smgr = Session.objects.all()
s: Session = smgr.get(pk='4u6mxvt03z12vwx4aqmp2u7i142xgbqf')
print(s)
print(s.session_data) # 中间字段的含义
print(s.get_decoded()) # {'userdata': 102} 这个数据可以非常大;如果在内存中,数据多,对内存占用就大;
diffdef test(request: HttpRequest, **kwargs):
print('~' * 30)
print(request) # <WSGIRequest: GET '/'>
print(kwargs)
print(request.session.get('userdata'), '-' * 30) # session
# request.session['userdata'] = random.randint(100, 200) # 对当前这个请求, 分配session, 响应sessionid;将session数据保存到mysql。
print(request.session) # 保存了一个sessionid
# request.session对象 是 django.contrib.sessions.backends.db.SessionStore 类的实例,此类的父类有 __getitem__, __setitem__, __delitem__ 方法,说明是字典。
print(*request.session.items(),sep='\n') # ('userdata', 102)
+ print(request.COOKIES) # {'sessionid': '4u6mxvt03z12vwx4aqmp2u7i142xgbqf'}
print('~' * 30)
res = JsonResponse([
(1, 'python', 3),
(2, 'c++', request.method)
], safe=False) # json的数据类型, 字符串; json.dumps() -> str python 列表 => js 数组; python 字典 => js 对象
+ res.cookies['mycookie'] = 'testcookie'
return res
浏览器会自动将加的cookie追加到已有的cookie;
响应体为
[ [ 1, "python", 3 ], [ 2, "c++", "GET" ] ]
pycharm打印
python~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<WSGIRequest: GET '/emps/'> # request
{} # kwargs
102 ------------------------------ # 获取之前用户的数据
<django.contrib.sessions.backends.db.SessionStore object at 0x0000027D016EC610> # session对象
('userdata', 102) # session 字典中的数据
{'sessionid': '4u6mxvt03z12vwx4aqmp2u7i142xgbqf'} # 请求的cookie
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
当postman第二次请求时
python[19/May/2022 13:24:16] "GET /emps/ HTTP/1.1" 200 37
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<WSGIRequest: GET '/emps/'>
{}
(0.000) SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; args=None
(0.000) SELECT `django_session`.`session_key`, `django_session`.`session_data`, `django_session`.`expire_date` FROM `django_session` WHERE (`django_session`.`expire_date` > '2022-05-19 05:26:25.140215' AND `django_session`.`session_key` = '4u6mxvt03z12vwx4aqmp2u7i142xgbqf') LIMIT 21; args=('2022-05-19 05:26:25.140215', '4u6mxvt03z12vwx4aqmp2u7i142xgbqf')
[19/May/2022 13:26:25] "GET /emps/ HTTP/1.1" 200 37
102 ------------------------------
<django.contrib.sessions.backends.db.SessionStore object at 0x0000027D015C6EE0>
('userdata', 102) # 自己添加的session在mysql/redis/内存中。
{'mycookie': 'testcookie', 'sessionid': '4u6mxvt03z12vwx4aqmp2u7i142xgbqf'} # 由于我们添加了cookie, 所以请求的cookie就一个数据。cookie在浏览器中保存,服务端不会保存。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
'django.contrib.sessions.middleware.SessionMiddleware',
session中间件只解决验证session: sessionid获取,发sessionid,关联sessionid与session,SessionMiddleware并不管认证,认证由 'django.contrib.auth.middleware.AuthenticationMiddleware',
此中间件完成。
SessionMiddleware
对sessionid关联Session,对应一个字典,内存/mysql/redis。AuthenticationMiddleware
将session_data中的userid关联到user实例。用户存在且激活状态 ,就有user is_authenticated=True,否则就是匿名用户实例 is_authenticated=False。view
diffdef test(request: HttpRequest, **kwargs):
print('~' * 30)
print(request) # <WSGIRequest: GET '/'>
print(kwargs)
print(request.session.get('userdata'), '-' * 30) # session
# request.session['userdata'] = random.randint(100, 200) # 对当前这个请求, 分配session, 响应sessionid;将session数据保存到mysql。
print(request.session) # 保存了一个sessionid
# request.session对象 是 django.contrib.sessions.backends.db.SessionStore 类的实例,此类的父类有 __getitem__, __setitem__, __delitem__ 方法,说明是字典。
print(*request.session.items(),sep='\n') # ('userdata', 102)
print(request.COOKIES) # {'sessionid': '4u6mxvt03z12vwx4aqmp2u7i142xgbqf'}
+ print(request.user) # AnonymousUser
print('~' * 30)
res = JsonResponse([
(1, 'python', 3),
(2, 'c++', request.method)
], safe=False) # json的数据类型, 字符串; json.dumps() -> str python 列表 => js 数组; python 字典 => js 对象
res.cookies['mycookie'] = 'testcookie'
return res
Django内建函数
authenticated(用户,密码)
认证用户和密码是否正常,成功返回用户实例,失败返回None
pythondef login_view(request): # request.user = 匿名用户
username = request.POST['username']
password = request.POST['password']
user = authenticated(username,password)
if user is not None:
login(request,user) # request.user = 覆盖; 生成sessionid和小字典;将小字典保存到数据库/redis/内存;
# 返回response报文时,headers中有set-cookie: sessionid=xxxxxxx; 浏览器下次请求,经过session request.session; 经过 auth request.user;
login(request,user)
request.user = user 并且响应cookie, cookie中保存sessionid, sessionid关联的字典为 sessionid: {userid: user.id}
logout
将sessionid关联的字典清空 request.session.flush()
,响应set-cookie: sessionid: ''
pythondef logout_view(request):
logout(request) # request.user = None; sessionid 关联的小字典清空,即数据库表中记录删除。
如果用户不登出,大量session在mysql中,不会定期删除。django提供了命令, cleanersessions,我们只需要在晚上cron
定时删除即可。
登出时,需要登陆的用户才可以登出
path('/logout', logout_view)
现在已经到view函数,现在已经通过session, auth中间件,就算没有sessionid或user为匿名用户。这2个中间件并不会拒绝访问。
python#@检查 request.user 。登陆过 user不是匿名用户。request.session 有sessionid 并不代表user激活。
from django.contrib.auth.decorators import login_required
@login_required
def logout_view(request):
logout(request)
未登陆,默认跳转 settings.LOGIN_URL
url中引用 handler函数,需要认证
diff
urlpatterns = [
# 暴露handler函数,直接是函数
# path('', test), # 列表页, /emps/ test = require_GET(test) => inner
# path('<int:id>', test_detail), # 详情页, restful
# 暴露handler函数,装饰出来的handler函数
path('', require_GET(test)), # /emps/ 到 test 函数 require_GET(test) = inner == inner(request, *args, **kwargs)
+ path('ts3/',login_required(TestView.as_view())),
+ path('ts4/',login_required(login_url='/login_in')(TestView.as_view())),
]
- ts3 默认跳转配置的
settings.LOGIN_URL
- ts4跳转到
/login_in
类上需要认证,同 4.2.3.1 4.2.3.2 4.2.3.3 4.2.3.4 节相同方法使用。这里传递的装饰器返回必须符合 view(request,*args,**kwargs)
handler函数格式
diff
from django.utils.decorators import method_decorator
from django.views.decorators.http import require_GET, require_http_methods
+from django.contrib.auth.decorators import login_required
+@method_decorator(login_required,
name='dispatch') # require_http_methods(['GET','PUT'] 或 require_GET
class TestView(View):
def get(self, request: HttpRequest, **kwargs): # TestView 实例相关; 视图函数必须有request
print(request.content_type)
print(kwargs)
return HttpResponse('这是视图类TestView的get')
def post(self, request: HttpRequest, **kwargs): # TestView 实例相关; 视图函数必须有request
print(request.content_type)
print(kwargs)
return HttpResponse('这是视图类TestView的post')
使用mixin类完成需要先认证
pythonfrom django.contrib.auth.mixins import LoginRequiredMixin
class MyView(LoginRequiredMixin, View):
login_url = '/login/'
redirect_field_name = 'redirect_to'
本文作者:mykernel
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!