文章目录
Flask 上下文
- 程序上下文(application context):存储了程序运行所必须的信息
- 请求上下文(Request context):当客户端发来请求时,请求上下文抱哈了请求的各种信息,比如请求的URL,请求的HTTP方法。
上下文全局变量
每一视图函数需要上下文信息,如果将请求报文封装在Request中,得将它作为参数传入视图函数,这将导致大量的重复,增加视图函数的复杂度。
如果不传入这个参数,那么直接将Flask中导入一个全局的request对象,然后在视图函数中调用Request的属性获取数据。Flask会在每个请求后自动激活当前的上下文,在请求结束后,销毁这个临时的请求上下文。
flask提供了4个上下文全局变量:
变量名 | 上下文类别 | 说明 |
---|---|---|
current_app | 程序上下文 | 指向处理请求的当前程序实例 |
g | 程序上下文 | 替代Python的全局变量用法,确保仅在当前请求中可用。用于存储全局数据,每次请求都会重设 |
request | 请求上下文 | 封装客户端发出的请求报文数据 |
session | 请求上下文 | 用于记住请求之间的数据,通过签名的Cookie实现 |
g 存储在程序上下文,随着每一个请求的进入而激活,随着每一个请求的处理完毕而销毁,所以每次请求都会重设这个值。通常结合请求钩子来保存每个请求处理所需要的全局变量。 |
from flask import g
@app.before_request
def get_name():
g,name = request.args.get('name')
激活上下文
Flask自动激活上下文的情况:
- 使用flask run命令启动程序时
- 使用旧的app.run()启动程序时
- 执行使用@app.cli.command()装饰器注册的flask命令时
- 使用flask shell命令启动Python Shell时
激活后,我们可以使用 request和 session变量。当请求上下文被激活后,程序上下文也自动被激活,当请求处理完毕,这两个上下文自动被 销毁
此外,url_for()和jsonify()也依赖上下文。
除了自动激活上下文,也可以手动激活:
from app import app
from flask import current_app
with app.app_context():
current_app.name
from app import app
from flask import current_app
app_ctx = app.app_context()
app_ctx.push()
current_app.name
‘app’
from app import app
from flask import current_app
with app.test_request_context('/hello'):
request.method
‘GET’
同样地,pop()和push()
上下文钩子
flask为上下文提供了一个teardown_appcontext钩子,使用它注册的回调函数会在程序上下文被销毁时调用,而且通常也会在请求上下文被销毁时调用。例如,请求结束后销毁数据库连接:
@app.teardown_appcontext
def teardown_db:
...
db.close()
HTTP进阶实践
重定向回上一个页面
在前面,我们使用**redirect()**函数生成重定向响应。
这个是直接跳转。另一种复杂情况是用户单击某个需要登录才能访问的连接,这时程序会重定向到登录页面,当用户登录后,合理的行为是重定向到用户登录前浏览的页面,以便用户执行未完成的操作,而不是直接重定向到主页。
1. 获取上一个页面的URL
有两种方法
-
HTTP referer
在flask中,用request.referrer获取
避免request.referrer为空,需要添加备选项
return redict(request.referrer of url_for('hello'))
-
查询参数
在URL中手动加入包含当前页面URL的查询参数,一般名称为next
next=request.full_path
使用request.full_path获取当前页面的完整路径
return redict(request.args.get('next',url_for('hello')))
可以将这两种方法搭配起来使用,首先尝试获取next,如果为空继续获取referrer:
def redict_back(default='hello',**kwargs):
for target in request.args.get('next'),request.referrer:
if target:
return redict(target)
return redict(url_for(default,**kwargs))
2. 对URL进行安全验证
referer和url容易被篡改,如果不验证,会形成开放重定向(Open Redirect)漏洞。
验证安全性:
from urllib.parse import urlparse,urljoin
from flask import request
def is_safe_url(target):
ref_url = urlparse(request.host_url)
test_url = urlparse(urljoin(request.host_url,target))
return test_uil.scheme in ('http','https') and ref_url.netloc ==test_url.netloc
request.host_url获取程序内的注记URL
urljoin()将目标URL转换为绝对URL
**urlparse()**解析两个URL,最后对目标URL的URL模式和主机地址进行验证
使用AJAX技术发送异步请求
传统Web应用:
首先,频繁更新页面会牺牲性能,浪费服务器资源,同时降低用户体验。
此外,页面重新加载后才能显示结果很不合理。
1. 认识AJAX
指异步JavaScript和XML,是一系列技术的组合体。
AJAX基于XMLHttpRequest让我们可以在不重载页面的情况下和服务器进行数据交换。加上JavaScript和DOM,就可以在接收到响应数据后局部更新页面。XML指的是数据的交互格式,也可以是纯文本或HTML、JSON。XMLHttpRequest不仅支持HTTP协议,还支持FILE和FTP协议。
2. 使用jQuery发送AJAX请求
jQuery是流行的JavaScript库。对于AJAX,它提供了多个相关方法,另外它处理了不同浏览器的AJAX兼容问题。
参数 | 参数值型及默认值 | 说明 |
---|---|---|
url | 字符串;默认为当前地址 | 请求的地址 |
type | 字符串;默认‘Get’ | 请求的方式,比如GET、POST、DELETE |
data | 字符串;无默认 | 发送到服务器的数据。会被jQuery自动转为查询字符串 |
dataType | 字符串;默认由jQuery自动判断 | 可用:xml,html,script,json,jsonp,text |
contentType | 字符串;默认‘application/x-www-form-rulencoded;charset=UTF-8’ | 发送请求时使用的内容类型,即请求首部的Content-Type字段内容 |
complete | 函数;无默认 | 请求完成的回调函数 |
sucess | 函数;无默认 | 请求成功的回调函数 |
error | 函数;无默认 | 请求失败后的回调函数 |
返回‘局部数据’
对于处理AJAX的视图函数,一般会返回局部数据,常见的三种类型如下:
-
纯文本或局部HTML模板
纯文本可以在JavaScript中替换页面的文本值,而局部HTML这可以直接插入页面,比如返回评论列表:
@app.route('/comments/<int:post_id>') def get_comments(post_id): ... return render_template('comments.html')
-
JSON数据
JSON数据可以在JavaScript中直接操作
@app.route('/profile/<int:user_id>') def get_profile(userr_id): ... return jsonify(username=username, bio=bio)
在jQuery中的ajax()方法的success回调中,响应主体的JSON字符串会被解析为JSON对象,我们可以直接获取并进行操作。
-
空值
不需要返回数据给客户端,比如删除文章的视图
return '', 204
-
异步加载长文章示例
@app.route('/post') def show_post(): post_body = generate_lorem_ipsum(n=2) return ''' <h1>A very long post</h1> <div class="body">%s</div> <button id="load">Load More</button> <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script type="text/javascript"> $(function() { $('#load').click(function() { $.ajax({ url: '/more', type: 'get', success: function(data){ $('.body').append(data); } }) }) }) </script>''' % post_body @app.route('/more') def load_post(): return generate_lorem_ipsum(n=1)
HTTP服务器端推送
不管什么请求方式,服务器始终处于被动的应答状态,只有客户端发出请求,服务器端才会返回响应。
某些情况,需要服务器端主动推送(server push)。例如聊天室某个用户发送消息,服务器将消息推送给所有用户。比如社交网站在导航栏实时显示新提醒和私信数量,用户的在线状态更新,多人游戏,文档协作等。
实现吴福气段推送的一系列技术被合称为 HTTP Server Push。有传统轮询、长轮询、Server-Sent-Events(SSE)
除了这些推送技术还有WebSocket协议。
Web安全防范
1. 注入攻击
有系统命令注入、SQL注入、NoSQL注入、ORM注入。重点介绍SQL注入:
-
攻击原理
编写SQL语句时,直接将用户传入的数据作为参数使用字符串拼接的方式插入到SQL查询。
-
攻击示例
@app.route('/students') def boddy_tabele(): password = request.args.get('password') cur = db.execute("SELECT * FROM students WHERE password='%S';" &password) resuts = cur.fetchall() return results
如果参数值为‘or 1=1 --’会把表中的所有结果返回。
如果参数值为‘; drop table users; --’会把删除表中所有记录。