【Flask Web】开发实战——第二章学习笔记

2.1 请求响应循环

image

Flask工作流程

2.2 HTTP请求

2.2.1 请求报文

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

2.2.2 Request对象

属性属性
pathu’/hello’base_urlhttp://helloflask.com/hello
full_pathu’/hello?name=Grey’urlhttp://helloflask.com/hello?name=Grey
hostu’helloflask,com¹url_roothttp://helloflask.com/
host_urlhttp://helloflask.com/
属性/方法说 明
argsWerkzeug的ImmutableMultiDict对象。存储解析后的查询字符串,可通过字典方式获取键值。如果你想获取未解析的原生查询字符串,可以使用query_string属性
blueprint当前蓝本的名称,关于蓝本的概念在本书第二部分会详细介绍
cookies一个包含所有随请求提交的cookies的字典
data包含字符串形式的请求数据
endpoint与当前请求相匹配的端点值
filesWerkzeug的MultiDict对象,包含所有上传文件,可以使用字典的形式获取文件。使用的键为文件input标签中的name属性值,对应的值为Werkzeug的FileStorage对象,可以调用save()方法并传入保存路径来保存文件
formWerkzeug的ImmutableMultiDict对象。与files类似,包含解析后的表单数据。表单字段值通过input标签的name属性值作为键获取
valuesWerkzeug的CombinedMultiDict对象,结合了args和form属性的值
get_data(cache=True,as_text=False,parse_from_data=False)获取请求中的数据,默认读取为字节字符串(bytestring),将as_text设为True则返回值将是解码后的unicode字符串
get_json(self,force=False,silent=False,cache=True)作为JSON解析并返回数据,如果MIME类型不是JSON,返回None(除非force设为True);解析出错则抛出Werkzeug提供的BadRequest异常(如果未开 启调试模式,则返回400错误响应,后面会详细介绍),如果silent设为True则返 回None;cache设置是否缓存解析后的JSON数据
headers一个Werkzeug的EnvironHeaders对象,包含首部字段,可以以字典的形式操作
is_json通过MIME类型判断是否为JSON数据,返回布尔值
json包含解析后的JSON数据,内部调用get_json().可通过字典的方式获取键值
method请求的HTTP方法
referrer请求发起的源URL,即referer
scheme请求的URL模式(http或https)
user_agent用户代理(User Agent,UA),包含了用户的客户端类型,操作系统类型等信息

2.2.3 在Flask中处理请求

  1. 路由匹配

    1. app.url_map中存储了对应关系
  2. 设置监听的HTTP方法

    @app.route('/hello', methods=['GET',...])
    
  3. URL处理

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

将变量转换为相应的数据类型

@app.route('/color/<any(blue, white, red):color')

如果color部分替换成any转换器中设置的可选值外的任意字符都会返回一个404错误响应。

2.2.4 请求钩子

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

请求钩子使用装饰器实现,通过程序实例app的调用,

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

这部分修饰器可以用来用户认证、记录请求日志以及确保数据库连接在请求结束时被正确关闭。

一个简单的应用实例

from flask import Flask, request, jsonify

app = Flask(__name__)

# 假设的用户认证信息
valid_users = {"john": "password123"}

# before_request钩子:简单用户认证
@app.before_request
def check_authentication():
    # 检查特定路由是否需要认证
    if request.endpoint in ['protected']:
        auth = request.authorization
        if not auth or not (auth.username in valid_users and auth.password == valid_users[auth.username]):
            return jsonify({"message": "Authentication required"}), 401

# after_request钩子:记录请求日志
@app.after_request
def log_request(response):
    with open("request_log.txt", "a") as logfile:
        logfile.write(f"{request.method} {request.path} - {response.status_code}\n")
    return response

# teardown_request钩子:模拟关闭数据库连接
@app.teardown_request
def close_db_connection(exception=None):
    print("Closing database connection (if any).")
# 注意:此处仅为示例,实际应用中应替换为真实数据库连接关闭逻辑

app.teardown_request(close_db_connection)

# 示例路由
@app.route('/')
def hello_world():
    return 'Hello, World!'

@app.route('/protected')
def protected_route():
    return jsonify({"message": "Welcome to the protected route!"})

if __name__ == '__main__':
    app.run(debug=True)

2.3 HTTP响应

2.3.1 响应报文

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

2.3.2 在Flask中生成响应

响应在Flask中使用Response对象表示,响应报文中大部分内容由服务器处理,大多数情况下,我们只负责返回主题内容。

如果找到路由对应的资源,则调用对应的视图函数,返回值构成了响应报文的主题内容,正确返回时状态码默认为200。

Flask会调用make_response()方法将视图函数返回值转换为响应对象

@app.xxx
def xxx():
    ...
    # 视图函数最多可以返回由三个元素组成的元组:响应主体、状态码、首部字段
    return '<h1>hello</h1>'  # 普通响应
    return '<h1>hello, boy</h1>', 201  # 指定状态码
    return '', 302, {'Location':'http://www.example.com'}
    
# 重定向,默认状态码 302,如果要修改状态码,则第二个参数或关键字code
return redirect('http://xxx.com')
# 错误响应,abort()函数被调用是,该函数之后的代码不会被执行
abort(404)  # 返回404错误响应
    

2.3.3 响应格式

不同的相应数据需要设置不同的MIME类型,MIME类型在首部的Content-Type字段中定义

MIME 类型列表参照:https://www.iana.org/assignments

如果想使用其他MIME类型,使用Flask提供的 make_response() 方法生成响应对象,传入响应的主体作为参数

from flask import make_response

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

2.3.4 Cookie

set_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内容进行加密,Flask提供了session对象来将Cookie数据加密存储。

  1. 设置程序密钥

    SECRET_KEY='xxx'  # 写在.env文件中
    
    ----------------------------------------
    
    import os
    # ...
    app.secret_key = os.getenv('SECRET_KEY', 'default string')
    # getenv实现获取
    
  2. 模拟用户认证

    使用session模拟用户的认证功能

    @app.route('/login')
    def login():
        session['logged_in'] = True  # 写入session
        return redirect(url_for('hello'))
    

    使用session对象添加cookie时,数据会使用程序的密钥进行签名,加密后的数据存储在一块名为session的Cookie里

    Content部分对应的加密处理后生成的session值。

    from flask import session
    
    @app.route('/logout')
    def logout():
        if 'logged_in' in session:
            session.pop['logged_in']
        return redirect(url_for('hello'))
    

2.4 Flask上下文

程序上下文 application context:记录了程序的状态和一些数据信息

请求上下文 request context:记录了包含请求额各种信息

2.4.1 上下文全局变量

视图函数里的request就是一个全局变量,这是因为Flask会在每个请求产生后自动激活当前请求的上下文,激活后,request被临时全局可访问,每个请求结束后,Flask就销毁对应的请求上下文。

在多线程服务器中每个request只在自身的线程内是全局的,Flask通过本地线程(thread local)技术将请求对象在特定的线程和请求中全局可访问。

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

对于current_app,程序也会有等多个程序实例的情况,为了能获取对应的程序实例,而不是固定的某一个程序实例,就需要使用current_app

对于g,存储在程序上下文中,如果在某个视图中查询得到了某个参数的值,且后续的每个视图都需要这个值,那么可以利用 [g.xxx](http://g.xxx) = yyy 这个是直接可写的。同时,g支持get(),pop(),setdefault()

2.4.2 激活上下文

在特定的情况下,Flask会自动激活程序上下文

  • flask run启动程序时
  • app.run()启用程序时
  • 执行@app.cli.command() 注册的flask命令
  • flask shell 运行Python Shell

同样依赖于上下文的还有 url_for(), jsonify() 等函数,前者依赖请求上下文,后者内部调用使用了current_app变量。

程序上下文对象 app.app_context() 获取,使用with类似于文件打开的形式,也可以显示地通过push,pop来进行,程序上下文通过 test_request_context()方法创建

>>> from app import app
>>> from flask import current_app
>>> with app.app_context():
    ... current_app.name  # 此时的current_app已经具备程序上下文信息
    
''' 第二种实现方案 '''
>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>> current_app.name
'app'  # 这里已经实现了加载程序上下文
>>> app_ctx.pop()

''' 请求上下文的创建 '''
>>> from app import app
>>> from flask import request
>>> with app.test_request_context('/hello'):
...    request.method  # 针对特定路由构造请求上下文
'GET'

2.4.3 上下文钩子

Flask为上下文提供了一个teardown_appcontext钩子,在每个请求结束后销毁数据库连接

@app.teardown_appcontext
def teardown_db(exception):  # 该装饰器注册的回调函数需要接收异常对象作为参数
    ...                      # 当请求正常处理时参数值将是None,该函数的返回值将被忽略 
    db.close()

2.5 HTTP进阶

2.5.1 重定向回上一个页面

  1. 获取上一个页面的URL

    1. HTTP refer

      return redirec (request.referrer or url_for( ' hello ' ))  # or 来防止自动清除或修改了referrer字段
      
    2. 查询参数

      ''' 在URL中手动加入包含当前页面URL的查询参数,这个参数一般命名为next '''
      return redirect(request.args.get('next', url_for('hello')))
      
    3. 复合使用

      def redirect_back(default='hello', **kwargs):
          for target in request.args.get('next'), request.referer:
              if target:
                  return redirect(target)
          return redirect(default, **kwargs)  
      
  2. 对URL进行安全验证

    1. 创建了URL验证函数 is_safe_url()

      from urlparse 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
      ''' netloc是获取url的域名部分,该验证是确保只有程序内部的URL可以跳转 '''    
      

2.5.2 使用AJAX发送异步请求

  1. AJAX指异步Javascript 和 XML
  2. 详细使用方法在后续的实践部分更新

2.5.3 HTTP服务器端推送

在聊天室等环境下我们需要服务器端的主动推送 server push。这一系列技术被合称为HTTP Server Push (HTTP 服务器端推送)

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

2.5.4 Web安全规范

  1. 注入攻击
    1. 攻击原理:将用户传入的数据作为参数使用字符串拼接的方式插入到查询中
    2. 主要防范方法
      1. ORM
      2. 验证输入类型
      3. 参数化查询
      4. 转义特殊字符
  2. XSS攻击 跨站脚本攻击
    1. 代码注入网站中,一旦访问就会被注入恶意代码
    2. 反射型XSS攻击和存储型XSS攻击
    3. 主要防范措施
      1. HTML转义

        from jinja2 import escape
        
        @app.route('/hello')
        def hello():
            name = request.args.get('name')
            response =  '<hl>Hello , %s!</hl>' % escape(name)
        
      2. 验证用户输入

  3. CSRF攻击 跨站请求伪造
    1. 攻击原理:利用用户在浏览器中保存的认证消息,向对应的站点发送伪造请求
    2. 防范措施:
      1. 正确使用HTTP方法:GET、POST
      2. CSRF令牌检验:Flask-SeaSurf 、CSRFProtect
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值