HTTP(Hypertext Transfer Protocol, 超文本传输协议)
客户端(Client Side) 用来提供给用户与服务器通信的各种软件 例:Web浏览器
服务器端(Server Side) 为用户提供服务的服务器,程序运行的地方
每一个Web应用都包含“请求-响应循环(Request-Response Cycle)”处理模式:客户端发出请求,服务器端处理请求并返回响应
标准URL:http://helloflask.com/hello?name=Grey
?name=Grey 部分是查询字符串(query string) 以键值对形式写出
多个键值对间用&分隔
报文(message):浏览器与服务器之间交互的数据
-请求时浏览器发送的数据 request message
-响应时服务器发送的数据 response message
-method GET POST PUT DELETE HEAD OPTIONS
-URL
-protocol
-header
-body
使用request的属性获取请求URL:
request对象常用的属性和方法:
获取请求URL中的查询字符串:
@app.route('/hello')
def hello():
name = request.args.get('name', 'Flask') # 获取查询参数name的值,默认为Flask
return 'Hello, %s!
' % name
# 该代码包含安全漏洞, 现实中要避免直接将用户传入的数据直接作为响应返回
# request.args['name']若无对应键, 返回404错误
# 故用get()方法获取数据,若无则返回None
app.url_map
命令行执行 flask routes
显示 Endpoint Methods Rule
@app.route('/hello2', methods=['GET', 'POST'])
def hello2():
return 'Hello, Flask!
'
405错误响应(Method Not Allowed 表示请求方法不允许)
# 为year变量添加一个int转换器,Flask解析该URL变量是将其转换为整型
# string(不含/) int float path(含/) any uuid
# <转换器:变量名>
@app.route('/go_back/' )
def go_back(year):
return 'Welcome to %d!
' % (2021 - year)
#
# 将部分换为any设置可选值以外的字符,均404
@app.route('/colors/' )
def three_colors(color):
return 'Love is patient and kind.
'
colors2 = ['blue', 'white', 'red']
@app.route('/colors2/' % str(colors2)[1:-1])
def three_colors2(color):
return 'Love is patient and kind.
'
注册在请求处理不同阶段执行的处理函数(或称回调函数 Callback)
预处理(preprocessing) 后处理(postprocessing)
每个Hook可以注册任意多个Callback
@app.before_request
def do_something():
pass # 该代码会在每个请求处理前执行
包含协议版本,状态码(status code) 原因短语(reason phrase)
响应首部,响应主体
make_response()
# 返回status code
@app.route('/hello')
def hello():
...
return 'hello flask', 201
# 生成状态码为3XX的重定向响应,需将首部中的Location字段设置为重定向目标的URL
@app.route('/ha')
def ha():
# return '', 302, {'Request URL:', 'http://www.example.com'}
return redirect('https://www.baidu.com')
# 重定向(Redirect)
@app.route('/hi')
def hi():
return redirect(url_for('hello'))
# 手动返回错误响应
@app.route('/418')
def teapot():
abort(418) # 一旦abort被调用,其之后的代码不会被执行
默认HTML格式(Flask默认设置)
不同响应格式徐设置不同MIME类型(在首部的Content-Type字段中定义)
MIME类型(又称media type/content type) 一种用来标识文件类型的机制
# make_response()方法生产响应对象,传入响应的主体作为参数
# 使用响应对象的mimetype属性设置MIME类型
# 也可直接设置首部字段(使用mimetype更方便,且无需设置charset选项)
# response.headers['Content-Type']='text/xml;charset=utf-8'
@app.route('/foo')
def foo():
response = make_response('Hello, World!')
response.mimetype = 'text/plain'
return response
XML Extensible Markup Language(可扩展标记语言)
JSON JavaScript Object Notation(JavaScript对象表示法)
# return response with different formats
@app.route('/note', defaults={
'content_type': 'text'})
@app.route('/note/' )
def note(content_type):
content_type = content_type.lower()
if content_type == 'text':
body = '''Note
to: Peter
from: Jane
heading: Reminder
body: Don't forget the party!
'''
response = make_response(body)
response.mimetype = 'text/plain'
elif content_type == 'html':
body = '''
Note
to: Peter
from: Jane
heading: Reminder
body: Don't forget the party!
'''
response = make_response(body)
response.mimetype = 'text/html'
elif content_type == 'xml':
body = '''
Peter
Jane
Reminder
Don't forget the party!
'''
response = make_response(body)
response.mimetype = 'application/xml'
elif content_type == 'json':
body = {
"note": {
"to": "Peter",
"from": "Jane",
"heading": "Remider",
"body": "Don't forget the party!"
}
}
response = jsonify(body)
# equal to:
# response = make_response(json.dumps(body))
# response.mimetype = "application/json"
else:
abort(400)
return response
@app.route('/boo')
def boo():
data = {
'name': 'Joseph Tong',
'gender': 'male'
}
# dumps()方法将字典,列表,元组serialize为JSON字符串
response = make_response(json.dumps(data))
response.mimetype = 'application/json'
return response
一般不直接使用json模块的dumps(),load()等方法
而使用jsonify(),默认生成200响应
@app.route('/boo')
def boo():
return jsonify(name='Joseph Tong', gender='male')
@app.route('/boo')
def boo():
return jsonify({
name:'Joseph Tong', gender:'male'})
两种形式返回值相同,都生成以下JSON字符串:
‘{“gender”: “male”, “name”: “Joseph Tong”}’
自定义响应类型:
@app.route('/boo')
def boo():
return jsonify(message='Error!'), 500
HTTP是无状态(stateless)协议
Cookie:Web服务器为存储某些数据而保存在浏览器上的小型文本数据
Cookie技术通过在请求和响应报文中添加Cookie数据来保存客户端的状态信息。
Flask中,想在响应中添加一个cookie,最方便的方法是用
Response类提供的set_cookie()方法,要使用该方法,需先使用
make_response()方法手动生成一个响应对象,传入响应主体作为参数
set_cookie()方法支持多个参数来设置Cookie的选项
@app.route('/set/' )
def set_cookie(name):
response = make_response(redirect(url_for('say_hello')))
response.set_cookie('name', name)
return response
# 查看浏览器中的Cookie,多了一块名为name的cookie,其值为
@app.route('/')
@app.route('/say_hello')
def say_hello():
name = request.args.get('name')
if name is None:
name = request.cookies.get('name', 'Human') # 从Cookie中获取name值
return 'Hello, %s
' % name
# 由/set/改变name值
session:安全的Cookie
若直接把用户认证信息以明文方式存储在Cookie中
恶意用户可通过伪造cookie内容来获取网站权限
为避免该问题,需对敏感的Cookie内容进行加密
Flask提供了session对象用来将Cookie数据加密储存
session通过密钥对数据进行签名以加密数据(必须设置才可使用session)
程序密钥可通过Flask.secret_key属性或配置变量SECRET_KEY设置
app.secret_key = 'secret string'
SECRET_KEY=secret string 保存在.env文件中(在命令行使用export或set命令)
程序脚本中使用os模块提供的getenv()方法获取
app.secret_key = os.getenv('SECRET_KEY', 'secret string')
# 第二个参数为没有获取到对应环境变量时使用的默认值
# 模拟用户认证
@app.route('/login')
def login():
session['logged_in'] = True # 写入session
return redirect(url_for('hello'))
@app.route('/')
@app.route('/hello')
def hello():
name = request.args.get('name')
if name is None:
name = request.cookies.get('name', 'Human')
response = 'Hello, %s!
' % name
# 根据用户认证状态返回不同的内容
if 'logged_in' in session:
response += '[Authenticated]'
else:
response += '[Not Authenticated]'
return respons
# 模拟管理后台
@app.route('/admin')
def admin():
if 'logged_in' not in session:
abort(403)
return 'Welcome to admin page'
# 登出用户
@app.route('/logout')
def logout():
if 'logged_in' in session:
session.pop('logged_in')
return redirect(url_for('hello'))
默认情况,session cookie会在用户关闭浏览器时删除
将session.permanent属性设为True可将session有效期延长为
Flask.permanent_session_lifetime属性值对应的datetime.timedelta对象
也可通过配置变量PERMANENT_SESSION_LIFETIME设置,默认为31天
绝不能在session中存储敏感信息,如用户密码
可以把编程中的上下文理解为当前环境(environment)的快照(snapshot)
Flask中有两种上下文:
程序上下文(application context):存储程序运行所必须的信息
请求上下文(request context):包含请求的各种信息,例如请求的URL,请求的HTTP方法等
Flask会在每个请求产生后自动激活当前请求的上下文,激活请求上下文后,request被临时设为全局可访问(非实际意义上的全局,动态的全局变量)。当每个请求结束后,Flask就销毁对应的请求上下文。
这四个变量都是代理对象(proxy),即指向真实对象的代理
可对proxy调用_get_current_object()方法获取被代理的真实对象
form flask import g
@app.before_request
def get_name():
g.name = request.args.get('name')
设置该函数后,在其他视图中可直接用g.name获取对应的值
g也支持类似字典的get(),pop(),setdefault()方法进行操作
以下情况,Flask会自动激活程序上下文:
当请求进入时,Flask会自动激活请求上下文,当请求上下文被激活时,程序上下文也被自动激活,请求处理完毕后,请求上下文和程序上下文会自动销毁(两者生命周期相同)
url_for()依赖请求上下文才可正常运行,jsonify()函数内部调用中使用了current_app变量,只能在视图函数中使用它们
未激活上下文情况下使用:
>>> from app import app >>> from flask import current_app >>> with app.app_context(): ... current_app.name 'app'
显示使用push()方法激活,执行完pop()方法销毁
>>> from app import app >>> from flask import current_app >>> app_ctx = app.app_context() >>> app_ctx.push() >>> current_app.name 'app' >>> app_ctx.pop()
请求上下文可通过test_request_context()方法临时创建
>>> from app import app >>> from flask import request >>> with app.test_request_context('/hello'): ... request.method 'GET'
同样可以push()和pop()方法显示地推送和销毁请求上下文
Flask为上下文提供了一个teardown_appcontext钩子,用它注册的回调函数(需接收异常对象作为参数)会在程序上下文被销毁时调用,且通常会在请求上下文被销毁时调用
# 在每个请求处理结束后销毁数据库连接
@app.teardown_appcontext
def teardown_db(exception): # 请求被正常处理时该参数值将是None
...
db.close()
# redirect to last page
@app.route('/foo')
def foo():
return 'Foo page
Redirect' \
% url_for('do_something', next=request.full_path)
@app.route('/bar')
def bar():
return 'Bar page
Redirect' \
% url_for('do_something', next=request.full_path
@app.route('/do-something')
def do_something():
# do something here
pass
(1)HTTP referer,一个用来记录请求发源地址的HTTP首部字段(HTTP_REFERER)
当用户在某站点单击链接,浏览器向新链接所在服务器发起请求,请求数据中包含的HTTP_REFERER字段记录了用户所在的原站点URL
Flask中,referer的值可通过请求对象的referrer属性获取,即request.referrer
do_something视图返回值可这样写
return redirect(request.referrer)
# 在很多种情况下,referrer字段会是空值,需添加一个备选项
return redirect(request.referrer or url_for('hello'))
(2)查询参数
在URL中手动加入包含当前页面URL的查询参数,一般命名为next
如上述foo/bar视图的返回值中的URL后添加next参数
do_something视图返回值可这样写
return redirect(request.args.get('next'))
# 为避免next为空,添加备选项
return redirect(request.args.get('next', url_for('hello')))
自定义通用函数:
def redirect_back(default='hello', **kwargs):
for target in request.args.get('next'), request.referrer:
if target:
return redirect(target)
return redirect(url_for(default, **kwargs))
do_something视图:
@app.route('/do_something')
def do_something():
return redirect_back()
由于referer和next容易被篡改的特性,若不对这些值进行验证,则会形成开放重定向(Open Redirect)漏洞。
例如,若你访问下面的URL
http://localhost:5000/do-something?next=https://www.baidu.com
程序会被重定向到https://www.baidu.com
确保URL安全的关键是判断URL是否属于程序内部
# 验证URL安全性
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_url.scheme in ('http', 'https') and \
ref_url.netloc == test_url.netloc
该函数接收目标URL作为参数,通过request.host_url获取程序内的主机URL,使用urljooin()函数将目标URL转换为绝对URL。接着,分别使用urlparse()函数解析两个URL,最后对目标URL的URL模式和主机地址进行验证,确保只有属于程序内部的URL才会被返回。
重写redirect_back():
def redirect_back(default='hello', **kwargs):
for target in request.args.get('next'), request.referrer:
if not target:
continue
if is_safe_url(target):
return redirect(target)
return redirect(url_for(default, **kwargs))
AJAX指异步Javascript和XML(Asynchronous JavaScript AndXML),它不是编程语言或通信协议,而是一系列技术的组合体。简单来说,AJAX基于XMLHttpRequest让我们可以在不重载页面的情况下和服务器进行数据交换。加上JavaScript和DOM(Document Object Model,文档对象模型),我们就可以在接收到响应数据后局部更新页面。
使用JQuery函数ajax()发送AJAX请求
ajax()函数支持的参数:
对于处理AJAX请求的视图函数,一般返回局部数据
常见的三种类型:1.纯文本或局部HTML模板;2.JSON数据;3.空值
异步加载长文章示例:
from jiinja2.utils import generate_lorem_ipsum
@app.route('/post')
def show_post():
post_body = generate_lorem_ipsum(n=2) # 生成两段随机文本
return '''
A very long post
%s
# 从CDN中加载JQuery资源
''' % post_body
@app.route('/more')
def load_post():
return generate_lorem_ipsum(n=1)
$(function(){…})函数是常见的 $(document).ready(function(){…})函数的简写形式。
美元符号是JQuery的简写,通常用它来调用JQuery提供的多个方法
$.ajax()等同于JQuery.ajax()
$(’#load’)被称为选择器,我们在括号中传入目标元素的id、class或是其他属性来定位到对应的元素,将其创建为jQuery对象。
不论是传统的HTTP请求-响应式的通信模式,还是异步的AJAX式请求,服务器端始终处于被动的应答状态,只有在客户端发出请求的情况下,服务器端才会返回响应。这种通信模式被称为客户端拉取(client pull)。在某些场景下,我们需要的通信模式是服务器端的主动推送(server push)
实现服务器端推送的一系列技术被合称为HTTP Server Push(HTTP服务器端推送)
OWASP是一个开源的、非盈利的国际性 安全组织。在OWASP网站的Top10页面中的Translation Efforts标签下可以找到中文版本的Top 10报告。
。。。。。。。