Web开发之Flask框架学习笔记(二)—— Flask与HTTP

Flask与HTTP

    • 基础知识
      • 专业名词
      • 请求响应循环
      • Flask Web工作流程
    • HTTP请求
      • URL
      • 请求报文
      • Request对象
      • 处理请求
        • 路由匹配
        • 设置监听方法
        • URL处理
        • 请求钩子(Hook)
    • HTTP响应
      • 响应报文
      • 生成响应
      • 响应格式
      • Cookie
    • Flask上下文
      • Flask中的上下文变量
      • 激活上下文
      • 上下文钩子
    • HTTP进阶实践
      • 重定向回上个页面
        • 1.获取上个页面的URL (两种方法)
        • 2.对URL进行安全验证
      • AJAX技术发送异步请求
      • HTTP服务器端推送
      • Web安全防范
        • 注入攻击
        • XSS攻击
        • CSPF攻击

基础知识

专业名词

HTTP(Hypertext Transfer Protocol, 超文本传输协议)

客户端(Client Side) 用来提供给用户与服务器通信的各种软件 例:Web浏览器

服务器端(Server Side) 为用户提供服务的服务器,程序运行的地方

请求响应循环

每一个Web应用都包含“请求-响应循环(Request-Response Cycle)”处理模式:客户端发出请求,服务器端处理请求并返回响应

Web开发之Flask框架学习笔记(二)—— Flask与HTTP_第1张图片

Flask Web工作流程

Web开发之Flask框架学习笔记(二)—— Flask与HTTP_第2张图片

HTTP请求

URL

标准URL:http://helloflask.com/hello?name=Grey
Web开发之Flask框架学习笔记(二)—— Flask与HTTP_第3张图片
?name=Grey 部分是查询字符串(query string) 以键值对形式写出
多个键值对间用&分隔

请求报文

报文(message):浏览器与服务器之间交互的数据
-请求时浏览器发送的数据 request message
-响应时服务器发送的数据 response message
-method GET POST PUT DELETE HEAD OPTIONS
-URL
-protocol
-header
-body

Request对象

使用request的属性获取请求URL:
Web开发之Flask框架学习笔记(二)—— Flask与HTTP_第4张图片
request对象常用的属性和方法:
Web开发之Flask框架学习笔记(二)—— Flask与HTTP_第5张图片
获取请求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 表示请求方法不允许)

URL处理

# 为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.

'

请求钩子(Hook)

注册在请求处理不同阶段执行的处理函数(或称回调函数 Callback)
预处理(preprocessing) 后处理(postprocessing)
每个Hook可以注册任意多个Callback
Web开发之Flask框架学习笔记(二)—— Flask与HTTP_第6张图片

@app.before_request
def do_something():
    pass  # 该代码会在每个请求处理前执行

HTTP响应

响应报文

包含协议版本,状态码(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

Cookie

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中存储敏感信息,如用户密码

Flask上下文

可以把编程中的上下文理解为当前环境(environment)的快照(snapshot)

Flask中有两种上下文:
程序上下文(application context):存储程序运行所必须的信息
请求上下文(request context):包含请求的各种信息,例如请求的URL,请求的HTTP方法等

Flask会在每个请求产生后自动激活当前请求的上下文,激活请求上下文后,request被临时设为全局可访问(非实际意义上的全局,动态的全局变量)。当每个请求结束后,Flask就销毁对应的请求上下文。

Flask中的上下文变量

Web开发之Flask框架学习笔记(二)—— Flask与HTTP_第7张图片
这四个变量都是代理对象(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 run命令启动程序时
  • 使用app.run()方法启动程序时
  • 执行使用@app.cli.command()装饰器注册的flask命令时
  • 使用flask shell命令启动Python Shell时

当请求进入时,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()

HTTP进阶实践

重定向回上个页面

# 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.获取上个页面的URL (两种方法)

(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()

2.对URL进行安全验证

由于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技术发送异步请求

AJAX指异步Javascript和XML(Asynchronous JavaScript AndXML),它不是编程语言或通信协议,而是一系列技术的组合体。简单来说,AJAX基于XMLHttpRequest让我们可以在不重载页面的情况下和服务器进行数据交换。加上JavaScript和DOM(Document Object Model,文档对象模型),我们就可以在接收到响应数据后局部更新页面。

使用JQuery函数ajax()发送AJAX请求
ajax()函数支持的参数:
Web开发之Flask框架学习笔记(二)—— Flask与HTTP_第8张图片
对于处理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服务器端推送

不论是传统的HTTP请求-响应式的通信模式,还是异步的AJAX式请求,服务器端始终处于被动的应答状态,只有在客户端发出请求的情况下,服务器端才会返回响应。这种通信模式被称为客户端拉取(client pull)。在某些场景下,我们需要的通信模式是服务器端的主动推送(server push)
实现服务器端推送的一系列技术被合称为HTTP Server Push(HTTP服务器端推送)

常用推送技术:
Web开发之Flask框架学习笔记(二)—— Flask与HTTP_第9张图片

Web安全防范

OWASP是一个开源的、非盈利的国际性 安全组织。在OWASP网站的Top10页面中的Translation Efforts标签下可以找到中文版本的Top 10报告。

注入攻击

XSS攻击

CSPF攻击

。。。。。。。

你可能感兴趣的:(Web开发之Flask框架)