Flask-HTTP请求、响应、上下文、进阶实验


本节主要目录如下:

一、请求响应循环

二、HTTP请求

2.1、请求报文

2.2、Request对象

2.3、在Flask中处理请求

2.4、请求钩子

三、HTTP响应

3.1、响应报文

3.2、在Flask中生成响应

3.3、响应格式

3.4、Cookie

3.5、session:安全的Cookie

四、Flask上下文

4.1、上下文全局变量

4.2、激活上下文*

4.3、上下文钩子

五、HTTP进阶实践

5.1、重定向回上一个页面

5.2、使用AJAX技术发送异步请求

5.3、HTTP服务器端推送

5.4、Web安全防范


一、请求响应循环

请求-响应循环”:客户端发出请求,服务器处理请求并返回响应。

Flask Web程序的工作流程

当用户访问一个URL,浏览器便生成对应的HTTP请求,经由互联网发送到对应的Web服务。Web服务器接收请求通过WSGI将HTTP格式的请求数据转换为成我们的Flask程序能够使用的Python数据

在程序中,Flask根据请求的URL执行对应的视图函数,获取返回值生成响应。响应依此经过WSGI转换生成HTTP响应,再经由Web服务器传递,最终被发出请求的客户端接收。浏览器渲染响应中包含的HTML和CSS代码,并执行JavaScript代码,最终把解析后的页面呈现在用户浏览器的窗口中。

二、HTTP请求

一个标准的URL由很多部分组成,以下面这个URL为例:

http://helloflask.com/hello?name=Grey

URL组成部分:

信息说明
http://协议字符串,指定要使用的协议
helloflask.com服务器的地址(域名
/hello?name=Grey要获取的资源路径(path),类似UNIX的文件目录结构

2.1、请求报文

请求的实质是发送到服务器上的一些数据,这种浏览器与服务器之间交互的数据被称为报文,请求时浏览器发送的数据被称为请求报文,而服务器返回的数据被称为响应报文

请求的报文由请求的方法、URL、协议版本、首部字段以及内容实体组成。

请求报文示意表:

组成说明请求报文内容
报文首部请求行(方法、URL、协议)GET/hello HTTP/1.1
报文首部:各种首部字段Host:helloflask.com Connection:keep-alive Cache-Control:max-age=0 User-Agent:...
空行
报文主体name=Grey

常见的HTTP方法:

方法说明
GET获取资源
POST传输数据
PUT传输文件
DELETE删除资源
HEAD获得报文首部
OPTIONS询问支持的方法

2.2、Request对象

Flask的请求对象requests封装了从客户端发来的请求报文,当收到请求后,请求对象会提供多个属性来获取URL(http://helloflask.com/hello?name=Grey)的各个部分:

属性属性
pathu'/hello'base_urlu'http://helloflask.com/hello'
full_pathu'/hello?name=Grey'urlu'http://helloflask.com/hello?name=Grey'
hostu'/helloflask.com'url_rootu'HelloFlask'
host_urlu'/HelloFlask'

除了URL,请求报文中的其他信息都可以通过request对象提供的属性和方法获取:

属性/方法说明
args存储解析后的查询字符串,可通过字典方式获取键值。
valueWerkze CombinedMultiDict 对象,结合了 args 和form 属性的值
headers一个 Werkzeug EnvironHeaders 象,包含首部字段, 可以以字典的形式操作
user_agent用户代理( User Agent,)UA,包含了用户的客户端类型,操作系统类型等信息
 from flask import Flaskapp = Flask(__name__)@app.route('/hello')def hello():name = request.args.get('name','Flask') # 获取查询参数name的值return '<h1>hello,%s</h1>' % name

2.3、在Flask中处理请求

URL是指向网络上资源的地址。在Flask中,我们需要让请求的URL匹配对应的视图函数,视图函数返回值就是URL对应的资源。

2.3.1、路由匹配

为了便于将请求分发到对应的视图函数,程序实例中存储了一个路由表(app.url_map),其中定义了URL规则和视图函数的映射关系。

当请求的URL与某个视图函数的URL规则匹配成功时,对应的视图函数就会被调用。使用flask routes命令可以查看程序中定义的所有路由,这个列表由app.url_map解析得到:

 $ flask routesEndpoint     Methods  Rule                   -----------  -------  -----------------------greet        GET      /greet/<name>greet        GET      /greethello_world  GET      /static       GET      /static/<path:filename>

在输出的文本中,我们可以看到每个路由对应的端点、HTTP方法和URL规则,其中static端点是Flask添加的特殊路由,用来访问静态文件

2.3.2、设置监听的HTTP方法

在app.route()装饰器中使用methods参数传入一个包含监听的HTTP方法的可迭代对象。比如,下面的视图函数同时监听GET和POST请求:

 @app.route('/hello',methods=['GET','POST'])def hello():return '<h1>Hello, Flask!</h1>'

当某个请求的方法不合符要求时,请求将无法被正常处理。返回405错误响应(表示请求方法不允许)。

2.3.3、URL处理

Flask内置的URL变量转换器:

转换器说明
string不包含斜线的字符串(默认值)
int整型
float浮点数
path包含斜线的字符串。static路由的URL规则中的filename变量就使用了这个转换器
any匹配一系列给定值中的一个元素
uuidUUID字符串

转换器通过特定的规则指定,即“<转换器:变量名>”。int:year把year的值转换为整数,因此我们可以在视图函数中直接对year变量进行数学计算:

 @app.route('goback/<int:year>')def go_back(year):return '<p>Welcome to %d</p>' % (2018 - year)

在用法上唯一特别的是any转换器,需要在转换器后添加括号来给出可选值:“<any(value1,value2,...):变量名>”比如:

@app.route('/colors/<any(blue,white,red):color>')def three_colors(color):return '<p>Love is patient and kind,Love is not jealous or boastful or proud or rude.</p>'

还可以在any转换器中传入一个预先定义的列表,可通过格式化字符串的方式(使用%或是format()函数)来构建URL规则字符串:

 colors = ['blue','white','red']@app.route('/colors/any(%S):color>' %s str(colors)[1:-1])...

2.4、请求钩子

有时候需要对请求进行预处理后处理,这时可以使用Flask提供的一些请求钩子,它们可以用来注册在请求处理的不同阶段执行的处理函数(或称为回调函数,即Callback)。

这些请求钩子使用装饰器实现,通过程序实例app调用:以before_request钩子(请求之前)为例,当对一个函数附加了app.before_request装饰器后,就会将这个函数注册为before_request处理函数,每次执行请求前都会触发所有before_request处理函数。

Flask默认实现的五种请求钩子:

钩子说明
before_first_request注册一个函数,在处理第一个请求前运行
before_request注册一个函数,在处理每个请求前运行
after_request注册一个函数,如果没有未处理的异常抛出,会在每个请求结束后运行
teardown_request注册一个函数,即使有未处理的异常抛出,会在每个请求结束后运行。如果发生异常,会传入异常对象作为参数到注册的函数中
after_this_request在视图内注册一个函数,会在这个请求结束后运行

使用和app.route()装饰器基本相同,每个钩子可以注册任意多个处理函数,函数名并不是必须和钩子函数名称相同,示例:

 @app.before_requestdef do_something()pass        # 这里的代码会在每个请求处理前执行

使用情况示例

  • before_first_request:在完整程序中,运行程序前我们需要进行一些程序的初始化操作,比如创建数据库表,添加管理员用户。
  • before_request:网站上要记录用户最后在线时间,可以通过用户最后发送请求时间来实现。
  • after_request:在视图函数中进行数据库操作,比如更新、插入等,之后需要将更改提交到数据库中。

另一种常见的应用是建立数据库连接,通常会有多个视图函数需要建立和关闭数据库连接,这些操作基本相同。一个理想的方法是在强求之前(before_request)建立连接,在请求之后(teardown_request)关闭连接。

三、HTTP响应

在Flask程序中,客户端发出的请求触发相应的视图函数,获取返回值会作为响应的主体,最后生成完整的响应,即响应报文。

3.1、响应报文

响应报文主要由协议版本、状态码、原因短语、响应首部和响应主体组成。以向localhost:5000/hello的请求为例,服务器生成的响应报文示意:

组成说明响应报文内容
报文首部:状态行(协议、状态码、原因短语)HTTP/1.1 200 OK
报文首部:各种首部字段Content-Type:text/html;charset=utf-8 ...
空行
报文主体<h1>Hello Human!</h1>

常见状态码和相应的原因短语:

类型状态码原因短语说明
成功200OK请求被正常处理
201Created请求被处理,并创建了一个新资源
204No Content请求处理成功,但无内容返回
重定向301Moved Permanently永久重定向
302Found临时性重定向
304Not Modified请求的资源未被修改,重定向到缓存的资源
客户端错误400Bad Request表示请求无效,即请求报文中存在错误
401Unauthorized类似403,表示请求的资源需要获取授权信息,在浏览器会弹出认证弹窗
403Forbidden表示请求的资源被服务器拒绝访问
404Not Found表示服务器上无法找到请求的资源或URL无效
服务器端错误500Internal Server Error服务器内部发生错误

3.2、在Flask中生成响应

响应在Flask中用Response对象表示,大部分情况,我们只负责返回主体内容。

视图函数可以返回最多由三个元素组成的元组:响应主体、状态码、首部字段(可以为字典或是两元素元组组成的列表)。

 # 普通的响应可以只包含主体@app.route('/hello')def hello():...return '<h1>Hello,Flask!</h1>'​# 默认状态码为200,下面指定不同的状态码@app.route('/hello')def hello():...return '<h1>Hello,Flask!</h1>',201​# 要生成状态码为3XX的重定向响应:@app.route('/hello')def hello():...return '',302,{'Location','http://www.example.com'}

3.2.1、重定向

当某个用户在没有经过认证的情况下访问需要登录后才能访问的资源,程序通常会重定向到登录页面。

除了上一节手动生成302响应,我们可以使用Flask提供的redirect()函数来生成重定向响应,重定向的目标URL作为第一个参数:

 from flask import Flask,redirect# ...@app.route('/hello')def hello():return redirect('http://www.example.com')​# 使用redirect()函数时,默认的状态码为302,即临时重定向。若要修改则在函数中第二个参数

若要在程序内重定向到其他视图,只需要在redirect()函数中使用url_for()函数生成目标URL即可:

 # http/app.py重定向到其他视图from flask import Flask,url_for...@app.route('/hi')def hi():...return redirect(url_for('/hello'))  # 重定向到/hello​@app.route('/hello')def hello():...

3.2.2、错误响应

使用Flask提供的abort()函数手动返回错误响应,在abort()函数中传入状态码即可返回对应的错误响应

 from flask import Flask,abort..@app.route('/404')def not_found():abort(404)

3.3、响应格式

Flask默认使用HTML格式返回响应,在Content-Type字段中定义设置不同的MIME类型以返回不同的响应数据格式。以默认的HTML为例:

 Content-Type:text/html;charset=utf-8

若要使用其他MIME类型,通过Flask提供的make_response()方法生成响应对象,传入响应的主体作为参考,然后使用响应对象的mimetype属性设置MIMW类型:

 from flask import make_response@app.route('/foo')def foo():response = make_response('Hello,World!')response.mimetype = 'text/plain'return response

常见的数据格式有纯文本、HTML、XML和JSON

3.3.1、纯文本

MIME类型:text/plain

 # 示例Noteto:Peterfrom:Janeheading:Reminderbody:Don't forget the party!

3.3.2、HTML

MIME类型:text/html

 # 示例<!DOCTYPE html><html><head></head><body><h1>Note</h1><p>to:Peter</p><p>from:Jane</p><p>heading:Reminder</p><p>body:<strong>Don't forget the party!</strong></p></body></html>

3.3.3、XML

MIME类型:application/xml

# 示例<?xml version='1.0' encoding="UTF-8"?><note> <to>Peter</to> <from>Jane</from> <heading>Reminder</heading> <body> Don’t forget the party!</body> </ note>

XML一般作为AJAX请求的响应格式,或是Web API的响应格式

3.3.4、JSON

MIME类型:application/json

 # 示例{"note":{"to":"Peter","from":"Jane","heading":"Reminder","body":"Don't forget the party!"}}

可以直接从Flask中导入json对象,然后调用dumps()方法将字典、列表或元组序列化为JSON字符串,再使用前面介绍的方法修改MIME类型,即可返回JSON响应,例如:

 from flask import Flask,make_response,json...@app.route('/foo')def foo():data = {'name':'Grey Li','gender':'male'}response = make_response(json.dumps(data))response.mimetype = 'application/json'return response

除此Flask提供更方便的jsonify()函数,仅需要传入数据或参数,它会对我们传入的参数进行序列化,转化成JSON字符串作为响应的主体,然后生成一个响应对象,并且设置正确的MIME类型。

 # 上述简化版(jsonify()函数)from flask import jsonify@app.route('/foo')def foo():return jsonify({name:'Grey Li',gender:'male'})​# jsonify()函数默认生成200响应

3.4、Cookie

HTTP是无状态协议。就是说在一次请求响应结束后,服务器不会留下任何关于对方状态的信息。

Cookie技术通过在请求和响应报文中添加Cookie数据来保存客户端的状态信息

在Flask中使用Response类提供的set_cookie()方法在响应中添加一个cookie。使用方法:先使用make_response()方法手动生成一个响应对象,传入响应主体作为参数。这个响应对象默认实例化内置的Response类。

Response类的常用属性和方法

方法/属性说明
headers一个Werkzeug的Headers对象,表示响应首部,可以像字典一样操作
status状态码,文本类型
status_code状态码,整型
mimetypeMIME类型
set_cookie()用来设置一个cookie

set_cookie() 方法支持多个参数来设置Cookie的选项:

属性说明
keycookie的
valuecookie的
max_agecookie被保存的时间数,单位为秒;默认在用户会话结束时过期
expires具体的过期时间,一个datetime对象或UNIX时间戳
path限制cookie只在给定的路径可用,默认为整个域名
domain设置cookie可用的域名
secure如果为True,只有通过HTTPS才可以使用
httponly如果为True,进制客户端JavaScript获取cookie

set_cookei视图用来设置cookie,它会将URL中的name变量的值设置到名为name的cookie里:

 from flask import Flask,make_response...@app.route('/set/<name>')def set_cookie(name):response = make_response(redirect(url_for('hello')))response.set_cookie('name',name)return response​# 查看浏览器的Cookie会看到多了一块名为name的cookie# 在Flask中,Cookie可以通过请求对象的cookies属性读取。在修改后的hello视图中,如果没有从查询参数中获取到name的值,就从cookie中寻找:from flask import Flask,request@app.route('/')@app.route('/hello')def hello():name = request.args.get('name')if name is None:name = request.cookies.get('name','human')  # 从Cookie中获取name值return '<h1>Hello,%s</h1>'% name

3.5、session:安全的Cookie

Flask提供session对象将Cookie数据加密存储

附注:在编程中,session指用户会话,又称对话,即服务器和客户端/浏览器之间或桌面程序和用户之间的交互活动、在Flask中,session对象用来加密Cookie。默认情况下,它会把数据存储在浏览器上一个名为session的cookie里。

3.5.1、设置程序密钥

session通过密钥对数据进行签名以加密数据,通过Flask.secret_key属性配置变量SECRET_KEY设置,比如:

 app.secret_key = 'secret string'​# 更安全的做法是把密钥写进环境变量中或保存在.env文件值:SECRET_KEY=secret string# 然后在程序脚本中使用os模块提供的getenv()方法获取:import os# ...app.secre_key = os.getenv('SECRET_KEY','secret string')# getenv()方法中的第二个参数作为没有获取到对应环境变量时使用的默认值。

3.5.2、模拟用户认证

 # 使用session模拟用户的认证功能from flask import redirect,session,url_for@app.route('/login')def login():session['logged_in'] = True     # 写入sessionreturn redirect(url_for('hello'))

当支持用户登录后,我们就可以根据用户的认证状态分别显示不同的内容。在login视图的最后,我们将程序重定向到hello视图:

 from flask import request,session@app.route('/')@app.route('/hello')def hello():name = request.args.get('name','Human')response = '<h1>Hello,%s</h1>'% name# 根据用户认证状态返回不同的内容if 'logged_in' in session:response += '[Authenticated]'else:response += '[Not Authenticated]'return response

程序中的某些资源仅提供给登入的用户,比如管理后台,这时我们就可以通过判断session是否存在logged_in键来判断用户是否认证

 # 模拟管理后台from flask import session,abort@app.route('/admin')def admin():if 'logged_in' not in session:abort(403)return 'Welcome to admin page.'​# 通过判断logged_in是否存在session中,可以实现:如果用户已经认证,会返回一个提示文字,否则返回403错误响应。

登出用户的logout视图实际操作就是把代表用户认证的logged_in cookie删除,这通过session对象的pop方法实现:

 from flask import session@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的内容不被篡改,借助工具仍可以读取,因此不能在session中存储敏感信息,比如用户密码。

四、Flask上下文

我们可以把编程中的上下文理解为当前环境的快照。Flask中有两种上下文,程序上下文请求上下文

4.1、上下文全局变量

每一个视图都需要上下文信息。前面实例中直接从Flask导入一个全局的request对象,然后在视图函数里直接调用request的属性获取数据。为了方便获取这两种上下文环境中存储的信息,Flask提供了四个上下文全局变量

变量名上下文类别说明
current_app程序上下文指向处理请求的当前程序实例
g程序上下文替代Python的全局变量用法,确保仅在当前请求中可用。用于存储全局数据,每次请求都会重设
request请求上下文封装客户端发出的请求报文数据
session请求上下文用于记住请求间的数据,通过前面的Cookie实现

在不同的视图函数中,request对象都表示和视图函数对应的请求,也就是当前请求。而程序也会有多个程序实例的情况,为了能获取对应的程序实例,而不是固定的某一个程序实例,我们就需要current_app变量

g存储在程序上下文中,而程序上下文会随着每一个请求的进入而激活,随着每一个请求的处理完毕而销毁,所以每次请求都会重设这个值。通常结合钩子来保存每个请求处理前所需要的全局变量,比如当前登入的用户对象,数据库连接等。

 from flask import g@app.before_requestdef get_name():g.name = request.args.get('name')

设置这个函数后,在其他视图中可以直接使用g.name获取对应的值。另外,g 也支持使用类似字典的get()、pop()以及setdefault()方法进行操作。

4.2、激活上下文*

Flask自动激活程序上下文的情况:

  • 使用flask run命令启动程序时
  • 旧方法app.run()方法启动程序时
  • 执行使用@app.cli.command()装饰器注册的flask命令时
  • 使用flask shell命令启动Python Shell时

当请求进入时,Flask会自动激活请求上下文(程序上下文也自动激活),这时我们可以使用request和session变量。请求处理完毕后两个上下文都自动销毁(拥有相同的生命周期)。

如果我们在没有激活上下文时使用这些变量,Flask就会抛出RuntimeRrror异常

 "RuntimeError:Working outside of application context."或是"RuntimeError:Working outside of request context."

手动激活上下文

 # Python Shell# 程序上下文对象使用app.app_context()获取>>> 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()方法显式地推送和销毁请求上下文

4.3、上下文钩子

Flask为上下文提供了一个teardown_appcontext钩子,使用它注册的回调函数会在程序上下文被销毁时调用,而且通常也会在请求上下文被销毁时调用。

 # 比如在每个请求处理结束后销毁数据库连接@app.teardown_appcontextdef teardown_db(exception):...db.close()

五、HTTP进阶实践

5.1、重定向回上一个页面

 # 创建两个视图函数foo和bar,分别显示一个Foo页面和一个Bar页面@app.route('/foo')def foo():return '<h1>Foo page</h1><a href="%s">Do something</a>' % url_for('do_something')​@app.route('/bar')def bar():return '<h1>Bar page</h1><a href="%s">Do something</a>' % url_for('do_something')​# 这两个页面都添加了一个指向do_something视图的链接:@app.route('/do_something')def do_something():return redirect(url_for('hello'))   

要完成的操作:在Foo页面上单击链接,我们希望被重定向回Foo页面;Bar页面同理:

5.1.1、获取上一个页面的URL

要重定向回上一个页面,最关键的是获取上一个页面的URL。上一个页面的URL一般可以通过两种方式获取:

(1)HTTP referer

HTTP referer是一个用来记录请求发起地址的HTTP首部字段,即访问来源。当用户在某个站点单击链接,浏览器向新链接所在的服务器发起请求,请求的数据中包含HTTP_REFERER字段记录了用户所在原站点URL。

这个值通常用来追踪用户,在Flask中,referer的值可以通过请求对象的referrer属性获取,即request.referrer。现在可改写do_something视图的返回值:

 return redirect(request.referrer)

但在多种情况下,referrer字段会是空值,比如在浏览器的地址栏输入URL,或是用户出于保护隐私的考虑使用了防火墙软件等修改了referrer字段。我们需要加一个备选项

 return redirect(request.referrer or url_for('hello'))

(2)查询参数

在URL中手动加入包含当前页面URL的查询参数,这个参数一般命名为next

# 在foo和bar视图的返回值中的URL后添加next参数:@app.route('/foo')def foo():return '<h1>Foo page</h1><a href="%s">Do something</a>' % url_for('do_something',next=request.full_path)​@app.route('/bar')def bar():return '<h1>Bar page</h1><a href="%s">Do something</a>' % url_for('do_something',next=request.full_path)

在程序内部只需要使用相对URL,所以这里使用request.full_path获取当前页面的完整路径。在do_something视图中,我们获取这个next值,然后重定向到对应的路径:

 return redirect(request.args.get('next'))

为了避免next参数为空的情况,添加备选项,如果为空就重定向到hello视图:

 return redirect(request.args.get('next',url_for('hello')))

(3)整合

为了覆盖更全面,我们将这两种方式搭配起来一起使用:首先获取next参数,如果为空就尝试获取referer,如果仍为空,就重定向到hello视图。因为在不同视图执行这部分操作的代码完全先那个塔,可以创建一个通用的redirect_back()函数:

 # 重定向回上一个页面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_and_redirect')def do_something():return redirect_back()

5.1.2、对URL进行安全验证

鉴于referer和next容易被篡改的特性,如果我们不对这些值进行验证,则会形成开发重定向(Open Redirect)漏洞。如果我们不验证next变量指向的URL地址是否属于我们的应用内,那么程序很容易就会被重定向到外部地址。

 # 创建一个URL验证函数is_safe_url(),用来验证next变量值是否属于程序内部URLfrom urllib.parse import urlparse, urljoin​def is_safe_url(target):ref_url = urlparse(request.host_url)    # 获取程序内的主机URLtest_url = urlparse(urljoin(request.host_url, target))  # 将目标URL转换为绝对URL,使用urlparse()函数解析两个URLreturn test_url.scheme in ('http', 'https') and ref_url.netloc == test_url.netloc       # 验证,只有属于程序内部的URL才会被返回​# 在执行重定向回上一个页面的redirect_back()函数中,我们使用is_safe_url()验证next和referer的值:def redirect_back(default='hello', **kwargs):for target in request.args.get('next'), request.referrer:if not target:continueif is_safe_url(target):return redirect(target)return redirect(url_for(default, **kwargs))

5.2、使用AJAX技术发送异步请求

5.2.1、AJAX

AJAX指异步Javascript和XML,它不是编程语言或通信协议,而是一些列技术的组合体。ajax让我们在不重载页面的情况下和服务器进行数据交换。加上JavaScript和DOM(文档对象模型),我们就可以在接收到数据后局部更新页面。XML指数据的交互模式,也可以是纯文本、HTML或JSON。

使用AJAX加载数据的情况:用户鼠标向下滚动到底部时在后台发送请求获取数据,然后插入文章;

以删除某个资源为例,AJAX实现步骤:

  • 当单击“删除”按钮时,客户端在后台发送一个异步请求,页面不变,在接收响应前可以进行其他操作。
  • 服务器端接收请求后执行删除操作,返回提示消息或是无内容的 204 响应
  • 客户端接收到响应 ,使用 JavaScript 更新页面,移除资源对应的页面元素

5.2.2、使用jQuery发送AJAX请求

jQuery 是流行的 JavaScript 库,它包装了 JavaScript 。对于AJAX,它提供了多个相关的方法,使用它可以很方便地实现AJAX操作。

使用jQuery的ajax()函数发送AJAX请求。其所支持的参数:

参数参数值类型及默认值说明
url字符串;默认为当前页地址请求的地址
type字符串;默认为“GET“请求的方式,即HTTP方法,比如GET、POST、DELETE等
data字符串;无默认值发送到服务器的数据。会被 jQuery 自动转换为查询字符串
dataType字符串;默认由jQuery自动判断期待服务器返回的数据类型,可用的值如下:“xml".html" "script"”json" "jsonp””text”
contentTypr字符串;默认为‘application/x-www-form-urlencoded;charset=UTF-8'发送请求时使用的内容类型,即请求首部放Content-Type字段内容
complete函数;无默认值请求完成后调用的回调函数
suceess函数;无默认值请求成功后的调用的回调函数
error函数;无默认值请求失败后调用的回调函数

5.2.3、返回“局部数据”

对于处理AJAX请求的视图函数来说,不会返回完整的HTM响应,而是局部数据,常见三种类型:

1、纯文本或局部HTML模板

纯文本可以在JavaScript用来直接替换页面中的文本值,而局部HTML则可以直接插入到页面中:

 # 返回评论列表@app.route('/comments/<int:post_id>')def get_comments(post_id):...return render_template('comments.html')

2、JSON数据

JSON数据可以直接在JavaScript中直接操作

 @app.route('/profile/<int:user_id>')def get_profile(user_id):...return jsonify(username=username,bio=bio)

3、空值

有时程序中的某些接收AJAX请求的视图并不需要返回数据给客户端,比如用来删除文章的视图。返回空值,并将状态码指定为204(表示无内容):

 @app.route('/post/delete/<int:post_id>',method=['DELETE'])def delete_post(post_id):...return '', 204

4、异步加载长文章

当加载文章按钮被点击时,会发送一个AJAX请求获取文章的更多内容并直接动态插入到文章下方。

5.3、HTTP服务器端推送

社交网站在导航栏实时显示新提醒和私信的数量,用户的在线状态更新,股价行情监控,显示商品库存信息、多人游戏、文档协作等。

实现服务器端推送的一系列技术被合称为HTTP Server Push,目前常用的推送技术:

名称说明
传统轮询在特定的时间间隔内,客户端使用 AJAX 技术不断向服务器发起 HTTP 请求,然后获取新的数据并更新页面
长轮询和传统轮询类似,但是如果服务器端没有返回数据,那就保持连接一直开启, 直到有数据才返回。取回数据后再次发送另一个请求
Server-Sent Events(SSE)SSE通过HTML中的EventSource API实现。SSE 会在客户端和服务器端建立 一个单向的通道,客户端监听来自服务器端的数据,而服务器端可以在任意时间发送数据,两者建立类似订阅/发布的通信模式

在HTML5的API中还包含一个WebSocket协议,它是一种基于TCP协议的全双工通信协议。实时性更强,而且还可以实现双向通信

5.4、Web安全防范

下面介绍常见几种攻击和其他常见漏洞

5.4.1、注入攻击

注入攻击包括系统命令注入、SQL注入、NoSQL注入、ORM注入等。重点介绍SQL注入:

(1)攻击原理

在编写SQL语句时,如果直接将用户传入的数据作为参数使用字符串拼接的方式插入到SQL查询中,那么攻击者可以通过注入其他语句来执行攻击操作(获取敏感数据、修改数据、删除数据库表...)

(2)攻击示例

假设我们程序是一个学生信息查询程序,其中某个视图函数接收用户输入的密码,返回查询结果对应的数据。

 @app.route('/students')def body_table():password = request.args.get('password')cur = db.excute("SELECT * FROM students WHERE password='%s';" % password)results = cur.fetchall()return results

如果攻击者输入的password参数值为"'or 1=1 --",那么最终视图函数中被执行的SQL语句将变为:

 SELECT * FROM students WHERE password='' or 1=1 --;'

这会吧students表中的所有记录全部查询并返回。若设为"'; drop table students; ---",那么查询语句变为:

 SELECT * FROM students WHERE password=''; drop table students; --;

这个语句会把students表中的所有记录全部删掉。

(3)主要防范方法

  • 使用ORM可以一定程度上避免SQL注入问题
  • 验证输入类型。
  • 参数化查询
  • 转义特殊字符

5.4.2、XSS攻击

XSS(Cross-Site Scripting,跨站脚本)攻击历史悠久

(1)攻击原理

XSS是注入攻击的一种,攻击者通过将代码注入被攻击者网站中,用户一旦访问网页便会执行注入的恶意脚本。XSS攻击主要分为反射型XSS攻击存储型XSS攻击

(2)攻击示例

反射型XSS又称为非持久型XSS。当某个站点存在CSS漏洞时,这种攻击会通过URL注入攻击脚本,只有当用户访问这个URL时才会执行攻击脚本。

 # 包含反射型XSS漏洞@app.route('/hello')def hello():name = request.args.get('name')response = '<h1>Hello,%s!</h1>' % name

这里未对字符串做任何处理就插入到返回的响应主体中,返回给客户端。若干某个用户输入了一段JavaScript代码作为查询参数的值:

 http://example.com/hello?name=<script>alert('Bingo!');</script>

访问便会弹出相应内容。

存储型XSS也被称为持久性XSS,这种类型的XSS攻击更常见,危害也更大。它和上述类似,不过会把攻击代码储存到数据库中,任何用户访问包含攻击代码的页面都会被殃及。比如,某个网站通过表单接收用户的留言,如果服务器接收数据后未经处理就存储到数据库中,那么用户可以在留言中插入任意Javascript代码。比如一行重定向代码:

 <script>window.location.href="http://attacker.com";</script>

其他用户一旦访问留言板页面,就会执行其中的JavaScript脚本。被重定向到攻击者写入的站点

(3)主要防范措施

  • HTML转义(对用户输入的内容进行HTML转义,转义后可以确保用户输入的内容在浏览中作为文本显示,而不是作为代码解析)

     # 使用Jinja2提供的escape()函数对用处传入的数据进行转义:from jinja2 import escape@app.route('/hello')def hello():name = request.args.get('name')response = '<h1>Hello,%s!</h1>' % escape(name)
  • 验证用户输入 XSS攻击可以在任何用户可定制内容的地方进行,例如图片引用、自定义链接。在某些HTML属性中,使用普通的字符也可以插入JavaScript代码。所以需要做好验证工作

     # 1、转义无法避免的XSS攻击情况,有下(链接):<a href="{{ url }}">Website</a>​# 如果不对url验证,用户写入代码:"javascript:alert('Bingo!');",最终的代码就会变为:<a href="javascript:alert('Bingo!');">Website</a># 2、图片<img src="{{ url }}"​# 类似,用户写入"123" onerror="alert('Bingo!')",最终的<img>标签就会变为:<img src="123" onerror="alert('Bingo!')">

5.4.3、CSRF攻击

CSRF(Cross Site Request Forgery,跨站请求伪造),又被称为One-Click Attack或Session Riding。

(1)攻击原理

攻击者利用用户在浏览器中保存的认证信息,想对应的站点发送伪造请求。

(2)攻击示例

假设我们是一个社交网站(A);攻击者可以是任意类型网站(B)。在A网站中,删除账户操作通过GET请求执行:

 @app.route('/account/delete')def delete_account():if not current_user.authenticated:abort(401)current_user.delete()return 'Deleted!'

用户登录后,访问http://example.com/account/delete就会删除账户。那么在攻击者的网站上,只需要创建一个显示图片的img标签,其中的src属性加入账户的URL:

 <img src="http://example.com/account/delete">

当用户访问B网站时,浏览器在解析网页时会自动向img标签的src属性中的地址发起请求。吸取教训,改用POST提交删除账户的请求。尽管如此,攻击者只需要在B网站中内嵌一个隐藏表单,然后设置在页面加载后执行提交表单的JavaScript函数,仍会被执行。

(3)主要防范措施

  • 正确使用HTTP方法(遵循原则)

    将这些按钮内嵌在使用了POST方法的form元素中。

    • GET方法属于安全方法,不会改变字眼状态,仅用于获取资源,因此又称幂等方法。页面中所有可以通过链接发起的请求都属于GET请求。
    • POST方法用于创建、修改和删除资源。
  • CSRF令牌校验 判断请求是否来源自己的网站。通过在客户端加入伪随机数来防御CSRF攻击,这个伪随机数通常被称为CSRF令牌(token)。对于AJAX请求,我们可以在 XMLHttpRequest请求首部添加一个自定义字段X-CSRFToken来保存CSRF令牌。 通常使用扩展来实现CSRF令牌的创建和验证工作,比如Flask-SeaSurf、Flask-WTF内置的CSRFProtect等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/322166.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【仪酷LabVIEW AI工具包案例】使用LabVIEW AI工具包+YOLOv5结合Dobot机械臂实现智能垃圾分类

‍‍&#x1f3e1;博客主页&#xff1a; virobotics(仪酷智能)&#xff1a;LabVIEW深度学习、人工智能博主 &#x1f384;所属专栏&#xff1a;『仪酷LabVIEW AI工具包案例』 &#x1f4d1;上期文章&#xff1a;『【YOLOv9】实战二&#xff1a;手把手教你使用TensorRT实现YOLOv…

数据结构学习——线性表、顺序表

1.线性表 线性表 &#xff08; linear list &#xff09; 是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使 用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构&#xff0c;也就说是连续的一…

项目管理-项目资源管理2/2

项目管理&#xff1a;每天进步一点点~ 活到老&#xff0c;学到老 ヾ(◍∇◍)&#xff89;&#xff9e; 何时学习都不晚&#xff0c;加油 资源管理&#xff1a;6个过程“硅谷火箭管控” ①规划资源管理&#xff1a; 写计划 ②估算活动资源&#xff1a;估算团队资源&…

渗透之sql盲注(时间/boolean盲注)

sql盲注&#xff1a;sql盲注意思是我们并不能在web页面中看到具体的信息&#xff0c;我们只能通过输入的语句的真假来判断。从而拿到我们想要的信息。 我们通常使用ascii值来进行盲注。 目录 手动注入&#xff1a; 时间盲注&#xff1a; 布尔盲注&#xff1a; python脚本注…

LabVIEW波浪发电平台浮筒取能效率数据采集系统

LabVIEW波浪发电平台浮筒取能效率数据采集系统 随着化石能源的逐渐减少以及能源价格的上升&#xff0c;寻找可替代的、可再生的、清洁的能源成为了世界各国的共识。波浪能作为一种重要的海洋能源&#xff0c;因其巨大的潜力和清洁性&#xff0c;近年来受到了广泛关注。开发了一…

(六)JSP教程——out对象

out对象是在JSP中经常使用到的对象&#xff0c;它本质上是一个输出流&#xff0c;前面已经多次使用&#xff0c;我们经常使用它的print()和println()方法&#xff0c;这些方法主要用于实现客户端数据的输出。通过out对象也可以直接向客户端发送一个由程序动态生成的HTML文件。 …

Docker-Compose 容器集群的快速编排

Docker-compose 简介 Docker-Compose项目是Docker官方的开源项目&#xff0c;负责实现对Docker容器集群的快速编排。 Docker-Compose将所管理的容器分为三层&#xff0c;分别是 工程&#xff08;project&#xff09;&#xff0c;服务&#xff08;service&#xff09;以及容器&…

JavaEE企业级开发中常用的JDK7和JDK8的时间类

JDK7时间类 全世界的时间有一个统一的计算标准 在同一条经线上的时间是一样的 格林威治时间 简称GMT 计算核心 地球自转一天是24小时 太阳直射正好是12小时 但是误差太大 现在用原子钟来代替 用铯原子震动的频率来计算时间&#xff0c;作为世界的标准时间UTC 中国标准时间…

在国企分公司做信息宣传新闻投稿的经验分享

作为一名国企分公司的信息宣传工作者,我亲历了从传统投稿方式到数字化转型的全过程,这段经历既充满了挑战,也收获了成长。回首最初的日子,那些用邮箱投稿的时光,至今仍让我感慨万千。 初尝辛酸,邮箱投稿的艰难岁月 刚接手信息宣传工作时,我满腔热情,却很快被现实的冷水浇了个透…

c语言实现贪吃蛇小游戏————附全代码!!!

目录 1.Win32 API 1.1控制台应用程序 1.2控制台的名称&#xff0c;控制台窗口大小 1.3设置控制台光标位置 COORD - 光标坐标 GetStdHandle - 获取句柄 SetConsoleCursorPosition - 设置光标位置 封装一个设置光标的函数 1.4设置控制台光标的属性 CONSOLE_CURSOR_INFO …

栈PART 1

目录 1. 栈 1.1 栈的概念和结构 1.2 栈的实现 1.2.1 栈的顺序储存结构 1.2.2 栈的基本操作 1.3 有效的括号 1. 栈 1.1 栈的概念和结构 堆栈又名栈&#xff08;stack&#xff09;&#xff0c;它是一种运算受限的线性表。 限定仅在表尾进行插入和删除操作的线性表。这一端…

C++进阶之路:深入理解编程范式,从面向过程到面向对象(类与对象_上篇)

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

java-函数式编程-语法

目录 1、函数表现形式 分类 lambda表达式 参数类型可以全写&#xff0c;也可以全不写&#xff0c;但不能一部分写&#xff0c;一部分不写lambda 的省略策略&#xff1a;凡是可推导&#xff0c;都可以省略 方法引用 练习-判断语法正确性 练习-写出与方法引用等价的lambda表达式…

TCP三次握手四次挥手 UDP

TCP是面向链接的协议&#xff0c;而UDP是无连接的协议 TCP的三次握手 三次传输过程是纯粹的不涉及数据&#xff0c;三次握手的几个数据包中不包含数据内容。它的应用层&#xff0c;数据部分是空的&#xff0c;只是TCP实现会话建立&#xff0c;点到点的连接 TCP的四次挥手 第四…

VMware虚拟机提示内存不足

VMware虚拟机&#xff0c;k8s集群搭建内存不足的问题 疑问&#xff1a;我的电脑是8G8G双通道的内存&#xff0c;当我在搭建k8s集群时给master-2G内存&#xff0c;node1-3G内存&#xff0c;node2-3G内存&#xff1b; 当依次打开虚拟机到node2时VM提示“物理内存不足&#xff0c;…

【大学物理】双语合集听课笔记

7.5 angular momentu(角动量)_哔哩哔哩_bilibili 6.4Energy in Rotation Motion 有质量有速度的物体有动能&#xff0c;是不是很有道理 international system&#xff08;from French systeme international&#xff0c;acronym&#xff0c;SI&#xff09;of ineria kg*m^2 转…

使用 Cython 加密 Python 代码防止反编译

文章目录 前言使用 Cython 加密 Python 代码环境Python 源代码编写 Cython 编译配置文件 编译查看输出文件使用 问题error: Microsoft Visual C 14.0 or greater is requiredpyconfig.h(59): fatal error C1083: 无法打开包括文件: “io.h”: No such file or directorydynamic…

鸿蒙开发全攻略:华为应用系统如何携手嵌入式技术开启新篇章~

鸿蒙操作系统是华为自主创新的成果&#xff0c;打破了传统操作系统的局限。通过结合嵌入式技术&#xff0c;鸿蒙实现了跨平台、跨设备的高度融合&#xff0c;提供了流畅、智能的体验。华为应用系统与嵌入式技术的结合&#xff0c;提升了性能&#xff0c;丰富了用户体验。鸿蒙与…

嵌入式Linux的QT项目CMake工程模板分享及使用指南

在嵌入式linux开发板上跑QT应用&#xff0c;不同于PC上的开发过程。最大的区别就是需要交叉编译&#xff0c;才能在板子上运行。 这里总结下嵌入式linux环境下使用CMake&#xff0c;嵌入式QT的CMake工程模板配置及如何使用&#xff0c;分享给有需要的小伙伴&#xff0c;有用到的…

【C++】---继承

【C】---继承 一、继承的概念及定义1、继承的概念2、定义语法格式3、继承基类成员访问方式的变化 二、基类 和 派生类 的对象之间的赋值转换1、赋值规则2、切片&#xff08;1&#xff09;子类对象 赋值 给 父类对象&#xff08;2&#xff09;子类对象 赋值 给 父类指针&#xf…