昨日内容回顾

flask和django对比

flask和django本质是一样的,都是web框架。

但是django自带了一些组件,flask虽然自带的组件比较少,但是它有很多的第三方插件。

那么在什么情况下,使用flask呢?

比如让flask写一个大型项目,它需要很多第三方插件。

那么堆着堆着,就和django一样了!

总结:

如果一个项目需要的插件比较少,可以使用flask。

如果需要的插件比较多,使用django更加方便。

flask知识点

装饰器

在flask中,装饰器用的是比较多的。看下面一段代码

  1. from flask import Flask
  2. app = Flask(__name__)
  3. @app.route('/index')
  4. def index():
  5. return 'index'
  6. if __name__ == '__main__':
  7. app.run()

现在有一个装饰器函数xxx,如果需要在每次请求index页面时,做一些操作。

那么装饰器,应该加在哪里呢?

这样?

  1. @xxx
  2. @app.route('/index')

还是这样呢?

  1. @app.route('/index')
  2. @xxx

答案是,必须在@app.route(‘/index’)下面才行。为什么呢?

因为如果加在@app.route上面,那么执行@xxx之后,那么就直接走视图函数了。已经没有意义了!

而如果在@app.route下面,那么执行到路由后,就会先执行@xxx,再执行视图函数!

装饰器的顺序

看下面一段代码,index视图函数,加了一个装饰器xxxx

  1. from flask import Flask
  2. app = Flask(__name__)
  3. def xxxx(func):
  4. def inner(*args,**kwargs):
  5. print('before')
  6. return func(*args,**kwargs)
  7. return inner
  8. @app.route('/index')
  9. @xxxx
  10. def index():
  11. return 'index'
  12. if __name__ == '__main__':
  13. app.run()

启动程序,访问首页

  1. http://127.0.0.1:5000/index

查看Pycharm控制台输出: before

如果再加视图函数home,并应用xxxx装饰器

  1. from flask import Flask
  2. app = Flask(__name__)
  3. def xxxx(func):
  4. def inner(*args,**kwargs):
  5. print('before')
  6. return func(*args,**kwargs)
  7. return inner
  8. @app.route('/index')
  9. @xxxx
  10. def index():
  11. return 'index'
  12. @app.route('/home')
  13. @xxxx
  14. def home():
  15. return 'home'
  16. if __name__ == '__main__':
  17. app.run()

启动之后,会直接报错

  1. AssertionError: View function mapping is overwriting an existing endpoint function: inner

为什么呢?由于代码是从上至下执行的。视图函数执行xxxx装饰器之后,使用name方法获取函数名时,名字是inner

那么因此执行到home时,函数名也是inner。那么flask就会抛出异常,inner函数重复了!

如何解决呢?使用functools就可以了!它会保留原函数信息,包括函数名!

  1. from flask import Flask
  2. import functools
  3. app = Flask(__name__)
  4. def xxxx(func):
  5. @functools.wraps(func)
  6. def inner(*args,**kwargs):
  7. print('before')
  8. return func(*args,**kwargs)
  9. return inner
  10. @app.route('/index')
  11. @xxxx
  12. def index():
  13. return 'index'
  14. @app.route('/home')
  15. @xxxx
  16. def home():
  17. return 'home'
  18. if __name__ == '__main__':
  19. app.run()

再次执行,就不会报错了。

因此,以后为了装饰器不出问题,一定要加functools

before_request/after_request

看下面的代码,b1和b2谁会先执行?

  1. # import pymysql
  2. # from DBUtils.PooledDB import PooledDB, SharedDBConnection
  3. # POOL = PooledDB(
  4. # creator=pymysql, # 使用链接数据库的模块
  5. # maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数
  6. # mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
  7. # maxcached=5, # 链接池中最多闲置的链接,0和None不限制
  8. # maxshared=3, # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
  9. # blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
  10. # maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制
  11. # setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
  12. # ping=0,
  13. # # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
  14. # host='127.0.0.1',
  15. # port=3306,
  16. # user='root',
  17. # password='123',
  18. # database='pooldb',
  19. # charset='utf8'
  20. # )
  21. '''
  22. 1.Flask路由
  23. 1.endpoint="user" # 反向url地址
  24. 2.url_address = url_for("user")
  25. 3.methods = ["GET","POST"] # 允许请求进入视图函数的方式
  26. 4.redirect_to # 在进入视图函数之前重定向
  27. 5./index/<nid> # 动态参数路由 <int:nid> def index(nid)
  28. 6.strict_slashes # 是否严格要求路由地址 /
  29. 7.defaults={"nid":1} # def index(nid)
  30. 2.Flask初始化配置(实例化):
  31. 1.template_folder # 指定模板路径
  32. 2.static_url_path # 指定静态文件目录的URL地址
  33. 3.static_folder # 指定静态文件目录路径
  34. 3.Flask对象配置
  35. 1.DEBUG #开发模式的调试功能 True False
  36. 2.app.config.from_object(class) # 通过对象的方式导入配置
  37. 3.secret_key # 开启session功能的时候需要添加的配置
  38. 4.Blueprint
  39. 1.将功能和主程序分离,注册
  40. 2.bl = Blueprint("dongdong",__name__)
  41. 3.注册 register_blueprint(bl)
  42. 5.send_file jsonify
  43. 1.send_file # 打开并返回文件 content-type:文件类型
  44. 2.jsonify # 将一个字符串 转为JSON格式 加入 content-type:application/json 头
  45. 6.特殊的装饰器:
  46. 1.before_request # 在请求进入视图函数之前执行的函数(登录认证)
  47. 2.after_request # 在请求响应回浏览器之前执行的函数
  48. 3.before_first_request # 在第一次请求进入视图函数之前执行的函数
  49. 4.errorheader(404) # 当遇到此类错误响应的时候(自定义错误页面)
  50. 7.flash
  51. 1.flash("msg","tag") # 闪现存储
  52. 2.get_flashed_messages(category_filter=["tag"]) # 闪现取值
  53. 只要用到了get_flashed_messages就一定清空flash
  54. 1.DButils 数据库连接池
  55. 创建连接池同时创建连接
  56. 用到连接时从连接池中抽取一个连接
  57. 释放连接时将连接放回连接池中
  58. 节省与mysql的通讯次数和时长
  59. 2.Websocket 通讯协议
  60. Web + socket
  61. QQ 即时通讯软件 97
  62. 初期轮询:
  63. QQ 联众 软件不断的循环访问服务器问它有没有给我发送的消息
  64. 优点:响应及时
  65. 缺点:浪费CPU资源,浪费带宽
  66. 长轮询:
  67. 当客户端发起询问,服务器说你等着1分钟之后,你再来问我
  68. 断开再次发起连接,服务器帮你轮询
  69. 优点:响应及时
  70. 缺点:用户一旦形成规模,服务器消耗是致命的
  71. 新的协议 websocket
  72. 规定了一个数据格式
  73. 收发数据
  74. 该收就收
  75. 该发就发
  76. 3.群聊
  77. 4.私聊
  78. '''
  79. # from flask import Flask,request,redirect,session
  80. #
  81. # app = Flask(__name__)
  82. # app.secret_key = "DragonFire"
  83. #
  84. #
  85. # @app.before_request
  86. # def is_login(): # 判断是否登录
  87. # # 白名单设置,判断为登录页面时
  88. # if request.path == "/login":
  89. # # 跳过处理
  90. # return None
  91. # # 判断session是不存在时
  92. # if not session.get("user"):
  93. # # 重定向到登录页面
  94. # return redirect("/login")
  95. #
  96. # @app.after_request
  97. # def foot_log(environ): # 记录访问日志
  98. # print(environ) # 响应信息
  99. # # 判断请求路径不是登录页面
  100. # if request.path != "/login":
  101. # # 打印访问路径
  102. # print("有客人访问了",request.path)
  103. #
  104. # return environ
  105. #
  106. # @app.route("/login",methods=["POST","GET"])
  107. # def login():
  108. # if request.method == "GET":
  109. # return "Login"
  110. #
  111. # user = request.form["username"] # form表单获取
  112. # pwd = request.form["password"] # form表单获取
  113. # # 判断form表示数据和 后台数据库匹配
  114. # # models.UserInfo.objects.filter(username=user,password=pwd).first()
  115. # if user == 'xiao' and pwd == '123':
  116. # # 设置session
  117. # session["user"] = user
  118. # # 跳转首页
  119. # return redirect("/index")
  120. #
  121. #
  122. # @app.route("/index")
  123. # def index():
  124. # return "Index"
  125. #
  126. # @app.route("/home")
  127. # def home():
  128. # return "Home"
  129. #
  130. # if __name__ == '__main__':
  131. # app.run("0.0.0.0", 5000)
  132. '''
  133. 1.玩具开机提示语
  134. 刚刚开机的时候:
  135. 1.授权问题(MD5授权码)提示语 : 请联系玩具厂商
  136. 2.绑定问题 提示语 : 快给我找一个小主人
  137. 3.成功 提示语:欢迎使用
  138. 2.为多个玩具发送点播:
  139. mpop 弹出菜单
  140. 3.聊天界面:
  141. <div class="leftd">
  142. <img src="avatar/girl.jpg" class="leftd_h" />
  143. <div class="speech left">点击播放</div>
  144. </div>
  145. <div class="rightd">
  146. <img src="avatar/girl.jpg" class="rightd_h" />
  147. <div class="speech right">点击播放</div>
  148. </div>
  149. 按住录音:
  150. hold: 按住事件 开始录音(回调函数)
  151. release: 松开事件 结束录音 执行录音中的回调函数
  152. 4.app录音:
  153. var rec = plus.audio.getRcorder()
  154. rec.record(
  155. {filename:"_doc/audio/",format:"amr"},
  156. function(success){ success //录音文件保存路径 },
  157. function(error){}
  158. )
  159. rec.stop()
  160. 5.app与服务器端文件传输(ws传输):
  161. 1.app使用dataURL方式打开录音文件 : base64 文件
  162. 2.通过某个函数 将 Base64 格式的文件 转为 Blob 用于 websocket传输
  163. 3.将Blob对象使用Ws发送至服务端
  164. 4.服务端保存文件(amr)
  165. 5.将amr 转换为 mp3 使用 ffmpeg -i xxx.amr xxx.mp3
  166. 6.简单的对话(app向玩具(web)发起):
  167. app: 1.发起两次 ws.send({to_user:}) 告诉服务端我要发给谁消息
  168. 2. ws.send(blob) app与服务器端文件传输
  169. websocket服务:
  170. 0.创建两个变量,用于接收to_user 和 blob对象
  171. 1.收到用户的JSON字符串,to_user
  172. 获取对方的Websocket,用户send
  173. 2.收到用户的Blob对象,语音文件
  174. 保存成amr文件,转换成mp3
  175. 注意保存文件的路径
  176. 3.将转换完成的文件发送给 to_user
  177. 4.两个变量置空
  178. '''
  179. from flask import Flask
  180. import functools
  181. app = Flask(__name__)
  182. @app.before_request
  183. def b1():
  184. print('b1')
  185. @app.before_request
  186. def b2():
  187. print('b2')
  188. def xxxx(func):
  189. @functools.wraps(func)
  190. def inner(*args,**kwargs):
  191. print('before')
  192. return func(*args,**kwargs)
  193. return inner
  194. @app.route('/index')
  195. @xxxx
  196. def index():
  197. return 'index'
  198. @app.route('/home')
  199. @xxxx
  200. def home():
  201. return 'home'
  202. if __name__ == '__main__':
  203. app.run(debug=True)

启动程序,访问index页面

  1. http://127.0.0.1:5000/index

查看Pycharm控制台输出:

  1. b1
  2. b2
  3. before

可以发现,b1先执行。为什么呢?因为代码是从上至下执行的,所以谁先加载,谁就先执行!

关于before_request源码分析,请参考链接:

https://blog.csdn.net/slamx/article/details/50491192

举例:

  1. from flask import Flask
  2. app = Flask(__name__)
  3. @app.before_request
  4. def b1():
  5. print('b1')
  6. @app.after_request
  7. def a1(environ):
  8. print('a1')
  9. return environ
  10. @app.route('/index')
  11. def hello_world():
  12. return 'Hello World!'
  13. if __name__ == '__main__':
  14. app.run()

访问首页:http://127.0.0.1:5000/index,效果如下:

Day139 websocket原理,flask之请求上下文 - 图1

Pycharm输出:

  1. b1
  2. a1

总结:before_request实际上是将视图函数,append到一个列表中。after_request也是将视图函数append到一个列表中,但是它对列表做了reverse操作!具体,可以看源码。

endpoint

endpoint主要是做反向解析的,使用url_for模块,就可以反向生成url

  1. from flask import Flask,url_for
  2. app = Flask(__name__)
  3. @app.route('/index',endpoint='n1')
  4. def index():
  5. print(url_for('n1'))
  6. return 'index'
  7. if __name__ == '__main__':
  8. app.run(debug=True)

访问url:

  1. http://127.0.0.1:5000/index

执行输出:

  1. /index

flask内置session

flask的session默认存储在哪里呢?在django中,session默认是保存在表里面的。

那么flask的session其实是保存在 用户浏览器的cookie中

它是如何存储的呢?看下面一段代码

  1. from flask import Flask,request,session
  2. app = Flask(__name__)
  3. app.secret_key = 'fdsa' # 必须要指定这个参数
  4. @app.route('/login')
  5. def login():
  6. #认证过程省略...
  7. # 设置session
  8. session['user_info'] = 'xiao'
  9. return '123'
  10. if __name__ == '__main__':
  11. app.run(debug=True)

访问登录页面,效果如下:

Day139 websocket原理,flask之请求上下文 - 图2

查看请求, 发现一个Set-Cookie。这个cookie的key就是session,值为一堆字符串。它是已经加密过的!

Day139 websocket原理,flask之请求上下文 - 图3

那么它是如何实现的呢?看这一行代码

  1. session['user_info'] = 'xiao'

它在内存中,维护了一个空间,这个空间是一个字典。由于服务端是单进程,单线程。

所有请求过来时,会排队。这个字典,会放一个key,这个key就是程序的线程id,value存放用户信息。

而value是一个字典,比如:{‘user_info’:’xiao’}

假设有100个用户,那么有100个值。大概是这样的样子:

  1. {
  2. "线程id": {
  3. "user_info": "xiao"
  4. },
  5. "线程id": {
  6. "user_info": "zhang"
  7. },
  8. ...
  9. }

返回给浏览器时,将内存中的字典做序列化,并做了加密

加完密之后,在cookie中写了一点数据

key是随机的,但是vlaue才是真正的数据

这个时候,flask字典,就清空了。

用户浏览器cookie中就有数据了,但是flask中的数据已经没有了!

这个时候,如果再来一用户,也是执行上面的流程。

总之,作为服务器,我不存储数据。

那么问题来了,flask如何做session验证?

如果之前的用户来了,它会携带cookie。

flask会读取cookie值,如果发现有,进行解密。如果解密成功,那么就是已经登录过了,否则没有登录过。

解密之后,它会将数据放到字典中!

那么读取时,它会直接从内存中读取。

关于flask的源码分析,请参考链接:

https://blog.csdn.net/m0_37519490/article/details/80774069

一、websocket原理

由于时间关系,步骤略…

关于websocket原理,请参考链接:

https://www.cnblogs.com/wupeiqi/p/6558766.html

二、flask之请求上下文

flask上下文管理,主要分为2类:

请求上下文管理

应用上下文管理

由于时间关系,步骤略…

草稿图

Day139 websocket原理,flask之请求上下文 - 图4

关于flask上下文管理,请参考链接:

https://www.cnblogs.com/zhaopanpan/p/9457343.html

https://blog.csdn.net/bestallen/article/details/54429629

关于flask面试题,请参考链接:

https://www.cnblogs.com/caochao-/articles/8963610.html

今日内容总结:

  1. 内容详细:
  2. 1. websocket原理
  3. a. websocket是一个协议。
  4. websocket解决了一个问题:服务端可以向客户端推送消息。
  5. http协议规定:
  6. - 请求体请求体
  7. - 一次请求一次响应(无状态短链接)
  8. websocket协议规定:
  9. - 握手
  10. - base64(sha1(key + magic string ))
  11. - 收发数据(加密)
  12. - =127
  13. - =126
  14. - <=125
  15. - 连接创建不断开(持久连接)
  16. b. 使用
  17. - flask werkzurg / geventwebsocket
  18. - django: wsgiref / channel
  19. - tornado: 自己写全支持:httpws
  20. 2. flask上下文管理
  21. 前戏:
  22. a. threading.local
  23. # 创建threading.local对象
  24. val = threading.local()
  25. def task(arg):
  26. # threading.local对象.xxx = 123
  27. # 内部,获取当前线程ID
  28. # {
  29. # 7800:{'x1':1}
  30. # 7180:{'x1':2}
  31. # }
  32. val.x1 = arg
  33. for i in range(10):
  34. t = threading.Thread(target=task,args=(i,))
  35. t.start()
  36. # ####### flask中搞了一个升级版的threading.local() #######
  37. # 创建threading.local对象
  38. val = threading.local()
  39. def task(arg):
  40. # threading.local对象.xxx = 123
  41. # 内部,获取当前协程ID
  42. # {
  43. # 7800:{'x1':1}
  44. # 7180:{'x1':2}
  45. # }
  46. val.x1 = arg
  47. for i in range(10):
  48. t = threading.Thread(target=task,args=(i,))
  49. t.start()
  50. b.
  51. 后进先出的数据结构
  52. c. 偏函数
  53. 保留已知参数
  54. d. 全局变量,flask程序启动只有一份数据
  55. _request_ctx_stack = LocalStack()
  56. _app_ctx_stack = LocalStack()
  57. current_app = LocalProxy(_find_app)
  58. request = LocalProxy(partial(_lookup_req_object, 'request'))
  59. session = LocalProxy(partial(_lookup_req_object, 'session'))
  60. g = LocalProxy(partial(_lookup_app_object, 'g'))
  61. 正文:图
  62. 重点总结:
  63. 1. flask路由:装饰器 *****
  64. 2. flasksession,默认写在浏览器cookie中。 ***
  65. 3. websocket协议 *****
  66. 4. flask请求上下文管理 *****
  67. 作业:
  68. 请求上下文类关系图
未完待续…