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 Flask
 app = 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 routes
 Endpoint     Methods  Rule                   
 -----------  -------  -----------------------
 greet        GET      /greet/<name>
 greet        GET      /greet
 hello_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_request
 def 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

 # 示例
 Note
 to:Peter
 from:Jane
 heading:Reminder
 body: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     # 写入session
     return 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_request
 def 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_appcontext
 def 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变量值是否属于程序内部URL
 from urllib.parse import urlparse, urljoin
 ​
 def is_safe_url(target):
     ref_url = urlparse(request.host_url)    # 获取程序内的主机URL
     test_url = urlparse(urljoin(request.host_url, target))  # 将目标URL转换为绝对URL,使用urlparse()函数解析两个URL
     return 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:
             continue
         if 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等。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

信号,信号列表,信号产生方式,信号处理方式

什么是信号 信号在我们的生活中非常常见&#xff1b;如红绿灯&#xff0c;下课铃&#xff0c;游戏团战信号&#xff0c;这些都是信号&#xff1b;信号用来提示接收信号者行动&#xff0c;但接收信号的人接收到信号会进行一系列的行为&#xff0c;完成某个动作&#xff1b;这就…

基于Java EE平台项目管理系统的设计与实现(论文 + 源码)

【免费】基于javaEE平台的项目管理系统.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89267688 基于Java EE平台项目管理系统的设计与实现 摘 要 随着社会信息化的发展&#xff0c;很多的社会管理问题也一并出现了根本性变化&#xff0c;项目公司的报表及文…

【YOLO】目标检测 YOLO框架之train.py参数含义及配置总结手册(全)

1.一直以来想写下基于YOLO开源框架的系列文章&#xff0c;该框架也是日常项目开发中常用的一款工具&#xff0c;最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下YOLO目标检测相关知识体系&#xff0c;之前实战配置时总是临时性检索些注释含义&#xff0c;但…

JVM组成之类加载器

类加载器&#xff08;ClassLoader&#xff09;&#xff1a;是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。 类加载器多数是有Java编写的&#xff0c;也有部分是c编写的&#xff0c;负责接收来自外部的二进制数据&#xff0c;然后执行JNI&#xff08;也就是本…

【Java】山外有山,类外还有类

【Java】山外有山&#xff0c;类外还有类 内部类是Java语言中的一种特性&#xff0c;它允许在另一个类中定义一个类。 内部类可以是静态的&#xff08;不依赖于外部类的实例&#xff09;&#xff0c;也可以是非静态的&#xff08;依赖于外部类的实例&#xff09;。 在本篇博…

在R的 RGui中,使用devtools 安装trajeR

创建于&#xff1a;2024.5.5 文章目录 1. 报错信息2. 尝试使用指定的清华镜像&#xff0c;没有解决3. 找到原因&#xff1a;官网把包删除了4. 尝试从网上下载&#xff0c;然后安装。没有成功5. 使用devtools安装5.1 尝试直接安装&#xff1a;install.packages("devtools&q…

【智能算法应用】混合粒子群算法求解CVRP问题

目录 1.算法原理2.数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】粒子群算法&#xff08;PSO&#xff09;原理及实现 经典PSO算法用于连续空间优化问题&#xff0c;VRP问题为离散组合优化问题&#xff0c;涉及如何有效地分配一组车辆去访问多个客户点&…

OSEK的设计哲学与架构

1 前言 OSEK是为单核分布式嵌入式控制单元量身定制的实时系统&#xff0c;对事件驱动&#xff08;event driven&#xff09;的硬实时控制系统具有良好的适配性。OSEK没有强求不同软件模块间的完全兼容性&#xff0c;而是将重心放到了软件的可移植性上来。简单来说&#xff0c;与…

[报错解决]Communications link failure

报错 主机IDEA项目连接虚拟机的数据库报错。 主要报错信息有&#xff1a; com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received a…

智慧旅游引领未来风尚,科技助力旅行更精彩:科技的力量推动旅游业创新发展,为旅行者带来更加便捷、高效和智能的旅行服务

目录 一、引言 二、智慧旅游的概念与特点 &#xff08;一&#xff09;智慧旅游的概念 &#xff08;二&#xff09;智慧旅游的特点 三、科技推动旅游业创新发展 &#xff08;一&#xff09;大数据技术的应用 &#xff08;二&#xff09;人工智能技术的应用 &#xff08;…

Linux Ubuntu 开机自启动浏览器

终端输入命令&#xff1a;gnome-session-properties 打开启动设置 如果提示&#xff1a;Command ‘gnome-session-properties’ not found, but can be installed with: apt install gnome-startup-applications 则执行&#xff1a;apt install gnome-startup-applications安装…

一、写给Android开发者之harmony入门

一、创建新项目 对比 android-studio&#xff1a;ability类似安卓activity ability分为两种类型(Stage模型) UIAbility和Extensionability&#xff08;提供系统服务和后台任务&#xff09; 启动模式 1、 singleton启动模式&#xff1a;单例 2、 multiton启动模式&#xff1…

数据结构十:哈希表

本次将从概念上理解什么是哈希表&#xff0c;理论知识较多&#xff0c;满满干货&#xff0c;这也是面试笔试的一个重点区域。 目录 一、什么是哈希表 1.0 为什么会有哈希表&#xff1f; 1.1 哈希表的基本概念 1.2 基本思想 1.3 举例理解 1.4 存在的问题 1.5 总结 二、…

libcity笔记:参数设置与参数优先级

1 参数优先级 高优先级的参数会覆盖低优先级的同名参数 Libcity中的优先级顺序维&#xff1a; 命令行参数&#xff08;命令行python run_model.py时导入的&#xff09; > 用户定义配置文件&#xff08;命令行python run_model.py时由config_file导入的&#xff09; >…

javascript 练习 写一个简单 另类录入 电脑组装报价表 可打印

数据格式 &#xff08;1代表cpu、2代表主板、3代表内存、。。。&#xff09; 1i3 12100 630 2H610 480 3DDR4 3200 16G 220 4500G M.2 299 5300W电源 150 6小机箱 85 7GT 730G 4G 350 8WD 2T 399 9飞利浦 24Led 580 主代码 Html JS <!DOCTYPE html> <html lang&qu…

02_Java综述

目录 面向对象编程两种范式抽象OOP 三原则封装继承多态多态、封装与继承协同工作 面向对象编程 面向对象编程(Object-Oriented Programming&#xff0c;OOP)在Java中核心地位。几乎所有的Java程序至少在某种程度上都是面向对象的。OOP与java是密不可分的。下面说一下OOP的理论…

SSM+Vue酒店管理系统

SSMVue酒店管理系统&#xff0c;JavaWeb酒店管理系统&#xff0c;项目由maven工具管理依赖&#xff0c;数据库Mysql&#xff0c;一共19张表&#xff0c;前端用Vue写的管理端&#xff0c;功能丰富&#xff0c;需要可在最后位置联系我&#xff0c;可加购调试&#xff0c;讲解&…

自注意力架构大成者_Transformer(Pytorch 17)

1 模型简介 在上节比较了 卷积神经网络&#xff08;CNN&#xff09;、循环神经网络&#xff08;RNN&#xff09;和 自注意力&#xff08;self‐attention&#xff09;。值得注意的是&#xff0c; 自注意力同时具有并行计算和最短的最大路径长度这两个优势。因此&#xff0c;使…

Llama3本地部署与高效微调入门

前言 为了保持公司在AI&#xff08;人工智能&#xff09;开源大模型领域的地位&#xff0c;社交巨头Meta推出了旗下最新开源模型。当地时间4月18日&#xff0c;Meta在官网上宣布公布了旗下最新大模型Llama 3。目前&#xff0c;Llama 3已经开放了80亿&#xff08;8B&#xff09…

8086 汇编学习 Part 9

端口的读写 CPU 的邻居 CPU 内部的寄存器内存单元端口&#xff08;各种接口卡、网卡&#xff0c;显卡&#xff0c;主板上的接口芯片等&#xff09; 各种芯片工作时&#xff0c;都有一些寄存器由 CPU 读写从 CPU 角度&#xff0c;将各寄存器当端口&#xff0c;并统一编制CPU …