Flask与HTTP

一、请求响应循环

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

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.mzph.cn/bicheng/6890.shtml

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

相关文章

【计算机网络】FTP站点配置搭建教程以及相关问题解决方案(超详细)

文章目录 1、安装Window Server 20082、搭建FTP环境&#xff08;1&#xff09;安装FTP服务器&#xff08;2&#xff09;配置FTP服务器&#xff08;3&#xff09;测试FTP连接 3、遇到的问题以及解决方案&#xff08;1&#xff09;Windows无法访问此文件夹&#xff08;2&#xff…

上传文件到 linux

一、mac 法一&#xff1a;scp 先进入mac的 Node_exporter文件&#xff08;要上传的文件&#xff09;目录下 输入scp -P 端口号 文件名 rootIP:/存放路径 scp -P 22 node_exporter-1.8.0.linux-amd64.tar.gz root192.***.2:/root 法二、 rz mac 安装 lrzsz&#xff0c;然后…

dp 动态规划 力扣

64. 最小路径和 给定一个包含非负整数的 m x n 网格 grid &#xff0c;请找出一条从左上角到右下角的路径&#xff0c;使得路径上的数字总和为最小。 说明&#xff1a;每次只能向下或者向右移动一步。 示例 1&#xff1a; 输入&#xff1a;grid [[1,3,1],[1,5,1],[4,2,1]] 输…

Python基础学习之logging模块

在Python编程中&#xff0c;日志记录&#xff08;Logging&#xff09;是一个非常重要的功能。它不仅可以帮助我们追踪和调试代码中的错误&#xff0c;还可以记录程序运行时的关键信息&#xff0c;以便后续分析和优化。Python标准库中的logging模块为我们提供了强大的日志记录功…

AI日报|gpt2-chatbot神秘聊天机器人引热议,疑似GPT-4.5?《金融时报》与OpenAI达成战略合作...

欢迎大家在 GitHub 上 Star 我们&#xff1a; 分布式全链路因果学习系统 OpenASCE: https://github.com/Open-All-Scale-Causal-Engine/OpenASCE 大模型驱动的知识图谱 OpenSPG: https://github.com/OpenSPG/openspg 大规模图学习系统 OpenAGL: https://github.com/TuGraph-…

踏准芯片定制风口的灿芯股份,护城河足够深吗?

近年来&#xff0c;芯片定制渐成风潮&#xff0c;不仅位于下游、自身有巨大芯片需求的科技巨头如谷歌、OpenAI等纷纷转向定制&#xff0c;而且产业中游主打标准化芯片的主流芯片设计公司如博通、英伟达等&#xff0c;也相继开辟或加码定制业务。 风潮背后&#xff0c;一方面是…

老人摔倒监测识别摄像机

随着社会老龄化程度的不断加深&#xff0c;老年人的健康和安全问题日益凸显。在家中独居的老人&#xff0c;一旦发生意外摔倒等情况&#xff0c;往往难以及时得到帮助&#xff0c;造成了严重的安全隐患。为了解决这一问题&#xff0c;近年来&#xff0c;老人摔倒监测识别摄像机…

AI大模型探索之路-训练篇7:大语言模型Transformer库之HuggingFace介绍

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…

鸿蒙组件样式复用简介

鸿蒙组件样式复用简介 使用Style进行复用在Component内部复用在Component外部复用使用Extend复用指定类型组件Extend支持参数传递 使用Style进行复用 在页面开发过程中&#xff0c;会遇到多个组件都在使用相同的样式&#xff0c;这时候就要考虑是不是可以将相同的样式的进行复…

【深度优先搜索 图论 树】2872. 可以被 K 整除连通块的最大数目

本文涉及知识点 深度优先搜索 图论 树 图论知识汇总 LeetCode 2872. 可以被 K 整除连通块的最大数目 给你一棵 n 个节点的无向树&#xff0c;节点编号为 0 到 n - 1 。给你整数 n 和一个长度为 n - 1 的二维整数数组 edges &#xff0c;其中 edges[i] [ai, bi] 表示树中节点…

VMware 虚拟机打开一段时间后卡死,VNX进程CPU占比高

一、问题描述 打开虚拟机后可以正常运行 运行几分钟后突然卡死 然后通过任务管理器可以观察到VMware Workstation VMX应用进程的CPU占比高&#xff0c;CPU也出现异常 关闭虚拟机重新开启&#xff0c;还是一样卡死 二、系统环境 系统: Windows10 VMware: Workstation 17 Pro …

奇门辅助软件v2024.5

废话不说&#xff0c;先上链接 链接&#xff1a;https://pan.baidu.com/s/1_i11lMx4P_vrTs-6lpWoHA?pwd8v1m 提取码&#xff1a;8v1m 功能介绍 【宫内信息】是点击宫内某属性时显示的宫内基本信息。 【古籍宝鉴】是《御定奇门宝鉴》里的对应时局内容&#xff0c;但差补法置…

GPT:利用LLM Studio在本地运行语言模型

请关注微信公众号&#xff1a;拾荒的小海螺 博客地址&#xff1a;http://lsk-ww.cn/ 1、简述 随着人工智能和自然语言处理技术的发展&#xff0c;语言模型技术正逐渐成为博客和内容创作领域的重要工具。LLM Studio是一种允许用户在本地环境中运行语言模型的工具&#xff0c;它…

备考2024年小学生古诗文大会:吃透10道历年真题和知识点(持续)

对上海小学生的小升初和各种评优争章来说&#xff0c;语文、数学、英语的含金量较高的证书还是很有价值和帮助的。对于语文类的竞赛&#xff0c;小学生古诗文大会和汉字小达人通常是必不可少的&#xff0c;因为这两个针对性强&#xff0c;而且具有很强的上海本地特色。 今天我…

C语言 | Leetcode C语言题解之第69题x的平方根

题目&#xff1a; 题解&#xff1a; int mySqrt(int x) {long int i 0;for(i0;;i){long int a i*i;long int b (i1)*(i1);if(a < x&&b > x){break;}}return i; }

【第三版 系统集成项目管理工程师】第2章 信息技术发展(知识总结)

持续更新。。。。。。。。。。。。。。。 【第2章】 信息技术发展 考情分析2. 1信息技术及其发展2.1.1 计算机软硬件-P501.计算机硬件2.计算机软件-P51 2.1.2计算机网络1.通信基础-P522.网络基础-P534.网络标准协议-P543.网络设备-P535.软件定义网络-P576.第五代移动通信技术-P…

【C++题解】1434. 数池塘(四方向)

问题&#xff1a;1434. 数池塘&#xff08;四方向&#xff09; 类型&#xff1a;深搜 题目描述&#xff1a; 农夫约翰的农场可以表示成 NM个方格组成的矩形。由于近日的降雨&#xff0c;在约翰农场上的不同地方形成了池塘。每一个方格或者有积水&#xff08;W&#xff09;或者…

项目管理-项目沟通管理

项目管理&#xff1a;每天进步一点点~ 活到老&#xff0c;学到老 ヾ(◍∇◍)&#xff89;&#xff9e; 何时学习都不晚&#xff0c;加油 1.项目沟通管理-主要内容 项目沟通管理过程--重点&#xff1a; ①ITTO 输入&#xff0c;输出工具和技术。 ②问题和解决方案。 ③论文…

手机异地组网方案?

现代社会&#xff0c;随着信息技术的快速发展&#xff0c;人们之间的通信需求也日益增加。尤其是在异地工作、异地学习、异地旅游等情况下&#xff0c;我们需要实现不同地区间的快速组建局域网&#xff0c;以解决电脑与电脑、设备与设备、电脑与设备之间的信息远程通信问题。本…

【Linux】基于 Jenkins+shell 实现更新服务所需文件 -->两种方式:ssh/Ansible

&#x1f468;‍&#x1f393;博主简介 &#x1f3c5;云计算领域优质创作者   &#x1f3c5;华为云开发者社区专家博主   &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社区&#xff1a;运维交流社区 欢迎大家的加入&#xff01; &#x1f40b; 希望大家多多支…