Flask-4-请求和响应

一、请求对象:request

后去前端传过来的请求数据是后端服务来管理的,Flask帮我们把请求数据相关的都从环境变量中获取出来储存在request代理对象上了,主要基于Request类实现。

我们只需要from flask import request 使用request对象获取请求数据即可

1、Get请求

  • 获取get请求的参数:request.args
  • 获取的数据类型:ImmutableMultiDict(不可变字典)
  • 可以用 to_dict() 方法转换成普通的可变字典

2、post请求

获取表单数据,请求头是:application/x-www-form-urlencoded

  • 获取表单数据:request.form
  • 获取的数据类型:ImmutableMultiDict(不可变字典)

获取json数据,请求头是:applocation/json

  • 获取json数据:request.json
  • 数据类型:普通字典dict类型,内部帮我们用json.loads(data)转成dict类型。方便我们的后续操作
  • 其实内部是调用的get_json()方法

3、其他参数

request.values

  • 可以同时获取args参数和form表单参数
  • 类型是CombinedMultiDict,内容是args和form
    • CombinedMultiDict([ImmutableMultiDict([('age', '17')]), ImmutableMultiDict([('name', 'zhangsan'), ('greden', 'nv')])])
    • 前端一个是args,后面是form表单参数
  • 一样可根据字典的方法取获取对应的参数名的值

request.cookies

  • 故名思意,获取请求头的cookie数据
  • 类型是dict

request.headers

  • 获取请求头
  • 类型dict

request.data

  • 包含传入的请求数据作为字符串,以防它与 Werkzeug无法处理mimetype。
  • 比如json数据可以用这个获取到,但是获取出来的数据类型bytes,一般用的很少,我们request.json更方便

request.files:比较重要

  • 获取post或put请求的文件
  • 类型MultiDict

request.environ

  • 获取WSGI隐含的环境配置
  • 当我需要的数据request对象没有帮我封装的时候,我也可以通过environ获取,和我们最小原型获取env里的数据一样

request.method

  • 获取请求方法
  • 主要用于在同一个视图函数中实现get和post不同请求方法时的两种逻辑会经常用到

request.remote_addr

  • 获取远程ip
  • 主要用于我们要限制ip的场景下

reuqest.user_agent

  • 获取请求头中的user-agent数据
  • 可以做一些反扒和恶意攻击的处理

request.is_josn

  • 获取mimetype是不是application开头,并且是json结尾
  • 布尔类型

request.is_xhr

  • 判断是否ajax请求发来的
  • request.is_xhr要废弃了
  • 我们也可以通过request.environ["HTTP_X_REQUESTED_WITH"] 来获取
  • 但是不一定是这个就一定是ajax请求,因为从前端获取的数据都是不可信的

4、代理模式

request本质Request对象,Request类继承BaseRequest类,只能和路由请求绑定使用,单独定义的一个使用request是获取不到的

下图是request对像封装了哪些数据,我们也可以自己打断点查看,更多说明可以查看Request类中继承的BaseRequest类

  • args:是从环境变量中QUERY_STRING获取的。
  • 其他很多都是从环境变量中获取的

5、文件上传(简单的图床功能)

  • 写一个上传文件的入口(HTML)
  • 后端写一个接口get请求返回HTMl,post请求接收图片保存本地static目录下
  • 前端用ajax发送请求

1、前端HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传</title>
    <link rel="stylesheet" href="../static/css/reset.css" type="text/css">
    <script type="text/javascript" src="../static/js/jquery-1.12.4.min.js"></script>
    <script type="text/javascript" src="../static/js/upload.js"></script>
</head>
<body>
<div>登录成功,进入文件上传</div>
<div>
    <!--  上传文件必须带上enctype="multipart/form-data,ajax请求时可以不带  -->
    <form id="uploadForm">
        <input id="file" type="file" name="img"/>
        <button id="upload" type="button">上传按钮</button>
    </form>
</div>
</body>
</html>

2、upload.js 发送ajax请求

ajax基础教程点击跳转

$(function () {
    //点击上传按钮时触发
    $("#upload").click(function () {
        $.ajax({
            url: '/up_img',
            type: 'POST',
            data: new FormData($('#uploadForm')[0]),
            processData: false,
            contentType: false
        }).done(function (res) {
            alert(res.msg)
        }).fail(function () {
            alert("请求失败")
        });
    })

});

3、后端服务

import os
import time
from flask import Flask, render_template, request, jsonify, url_for
from werkzeug.utils import secure_filename

# 在前面我们提到flask的配置项,其中一个就可以非常简单限制上传文件的大小
app.config["MAX_CONTENT_LENGTH"] = 2 * 1024 * 1024

@app.route("/up_img", methods=['GET', 'POST'])
def load_img():
    # 如果get方法,返回上传文件的HTML页面
    if request.method == "GET":
        return render_template("upload.html")
    # 否则获取上传的文件,img是前端请求参数名
    img = request.files["img"]
    # 获取后缀名判断类型,文件大小我们通过config配置了
    img_name_end = img.filename.split(".")[-1]
    if img_name_end in ("jpg", "png"):
        # 保存文件,secure_filename主要作用是处理空格会被url转移的特殊字符
        # 方法内定义了很多规则进行转换,比如会把空格转成下划线
        save_name = "".join((str(time.time()), ".", img_name_end))
        img.save(os.path.join(app.static_folder, "img", secure_filename(save_name)))
        # 利用url_for生成url 供前端访问该图片
        # 前端f12查看接口返回结果访问即可
        return jsonify(
            {"status": 200, "data": {
                "lmg_url": url_for("index", _external=True) + "static/img/" + secure_filename(save_name)},
             "msg": "上传成功"})
    return jsonify({"status": 100, "msg": "不支持的文件格式"})
    
if __name__ == '__main__':
    app.run(debug=True)

备注

  • 上传文件的时候可能我们文件名中有空格之类的。可以要用secure_filename效验参数名,来动态处理参数名的空格 等转义问题
  • 可以在app.config中限制文件大小 MAX_CONTENT_LENGTH 字段来设置
  • 也可以size = len(upload_file.read())用这方法获取到 然后在自己写逻辑判断
  • 不要忘记我们之前分享提到的,falsk项目的默认模板目录和静态资源目录,当然可以初始化app核心对象时,通过对应参数使用修改。具体请回顾Flask-1-快速开始-flask核心类初始化参数说明
  • HTMl默认清空下需要保存在templates目录下
  • js文件默认清空下需要保存在static
  • 前端访问静态资源,访问图片默认url是http://locakhost:5000/static/.....
  • 利用url_for()生成绝对url。

二、响应对象

如果不做任何限制,返回字符串游览器自动会把它变成HTML,因为响应对象默认定义的mimetype,也就响应头对应content-type是text/html。

1、主要的响应对象

  • 文本:text/plain
  • HTML:text/html
  • XML:aplication/xml
  • json:aplication/json

2、手动修改响应状态码和媒体类型

flask构造响应对象时,如何定义响应状态码,响应头,影响数据的???

其实在我们return的时时候,可以接收三个参数,第一个响应数据,第二个响应状态码,第三个响应头

@app.route("/", methods=["GET", "POST"])
def index():
    return json.dumps({"username": "jiangmingbai"}), 201, {"content-type": "application/json"}

预览器的响应发生了变化

3、实现原理:make_response方法

其实return的响应信息也是基于make_response()方法实现的,我们也可以return一个make_response()对象,

make_response()里基于current_app.make_response(args)方法,这里面最后调用的Response()类,Response()类,继承的BaseResponse(),其中需要传响应对象的初始化参数

  def __init__(
        self,
        response=None,  # 响应数据
        status=None,  # 状态码
        headers=None,  # 头信息
        mimetype=None,  # 媒体类型
        content_type=None,  
        direct_passthrough=False,
    ):

注意:

  • 具体细节可以导入falsk下的make_reponse,查看源码,注释。都有案例
  • 在make_response()传参时, status可以时int类型,内部做了判断如果时int会赋值给,status_code,但是如果用实例对象在赋值时必须时字符串。
from flask import Flask, make_response

app = Flask(__name__)

@app.route("/", methods=["GET", "POST"])
def index():
    r = make_response(json.dumps({"username": "jiangmingbai"}), 201, {"content-type": "application/json"})
    print(r)  # <Response 28 bytes [201 CREATED]>实例对象
    r.status = "202 OK"  # 通过实例在赋值stutas时必须是字符串
    return r
if __name__ == '__main__':
    app.run(debug=True)

4、json响应头:jsonify

我在返回json数据到前端的时候,前面我们用的是自己使用json.dumps方法把数据转成json,并把响应头媒体类型定义成application/json

其实flask帮我们封装了一个方法,专门用于返回json数据响应对象。我只需要调用改方法即可

其实也是用Response类处理的,
帮我动态的自动获取config里面的源类型 默认是application/json
所以一样可以接收返回值,也就是Response实例对象来操作初始化参数和属性值

from flask import Flask, jsonify

app = Flask(__name__)

@app.route("/user", methods=["GET", "POST"])
def user():
    r = jsonify({"age": 18})
    r.status = "202 ok"
    return r

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

5、重定向 redirect

之前讲在分享路由redirect_to参数的时候其实有提到过实现重定向的两种方式,一致在路由加redirect_to参数。一种是用在视图函数种return redirect() 并推荐结合url_for一起用,因为端点变化的几率更小

具体请跳转-->https://www.cnblogs.com/jiangmingbai

三、自定义错误响应

当我们服务器遇到问题比如500,404 等,flask会帮我们自动返回一个一个它封装好的页面,但是这个页面并不友好

还有就是当用户未授权访问服务时,肯定需要抛401的错误,此时flask帮我返回的页面,一样也不友好。

那如何自定义错误响应页面呢??

flask帮我们实现了自定义错误响应的装饰器:@app.errorhandler(code_except):响应码或者错误类型

注意: 不要用debug调试模式,返回帮我们把错误信息输出到前端,而不是自定义的错误页面

1、不可预见的错误响应:500,502,404等

有一些错误是我们不可预见的,我们也不知道哪里会发生错误,我们此时可以定义一个全局的错误响应来替换

案例:500 ,404 自己写好页面,放在项目templates目录下

  • 利用@app.errorhandler(500)定义好,返回对应500错误页面的方法
  • 程序在遇到500时,会自动帮我们返回自定义500的页面
from flask import Flask, render_template

app = Flask(__name__)

# 全局捕获的500错误的
@app.errorhandler(500)
def error_500(error_msg):
    return render_template("error_500.html", error_msg=error_msg), 500


# 全局捕获的404错误的
@app.errorhandler(404)
def error_500(error_msg):
    return render_template("404.html"), 404
    

# 遇到不可遇见的返回我们定义500错误页面
# 当发生500错误时,自动返回我们定义error_500.html
@app.route("/")
def index():
    1 / 0
    return render_template("login.html")

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

2、可以遇见的错误:比如:401,403 等等

还有一种是我们可以遇见的,就是我们知道我们想要什么,如果不对,我们就主动抛处一个错误。而不是return。

而falsk就我们封装了一个abort方法,帮我统一主动raise,我们只需要传对应code码即可,但是都是flask帮我们定制了所有对应code
页面,对我们来说并不友好, 如何自定义呢?

我本一样可以@app.errorhandler(code_except),然后在封装方法返回我们自定义的页面即可

案例:比如 登录未授权,返回401页面

  • 自定义错误类型的装饰器,实现返回对应状态码的错误页面
  • 程序到预知到此种错误的时候,利用abort(401),会自动返回对应401的页面
from flask import Flask, render_template, abort, request

app = Flask(__name__)

# 自定义401错误响应页面,配合abort使用
@app.errorhandler(401)
def error_401(error_smg):
    return render_template("error_401.html"), 401
    
# 可遇见,用abort处理
@app.route("/project")
def get_pro():
    # 假如没有获取到项目id,就抛未授权,当然实际种时判断cookie
    if not request.args.get("pro_id"):
        abort(401)
    return "获取成功"

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

3、自定义错误类型

当然我们也可以自定义错误类型进行返回,我们自己主动raise一个错误类型,而不用abort处理

此时我们需要自定义错误类型,,只需要继承一下Exception,然后在用@app.errorhandler(自定义错误类型(except)),装饰我们的方法,返回自定义的错误类型对应的页面即可

flask很好为我们提供了abort,也很方便,具体根据实际情况想咋用都行,其实abort处理更加方便

案例:

  • 定义错误类型
  • @app.errorhandler(except),处理返回对应错误类型的自定义页面
  • 遇到此种错误的时候,只用raise 这个错误类型,就会返回我们自定义的错误页面
from flask import Flask, render_template, abort, request

app = Flask(__name__)

# 自定义一个错误类型,用户错误
class UserError(Exception):
    pass
    
# 自定义错误类型返回的页面
@app.errorhandler(UserError)
def error_401(error_smg):
    return render_template("username_error.html"), 401
    
# 自定义错误类型,raise处理
@app.route("/user")
def get_user():
    if not request.args.get("username"):
        # 自己主动抛出我们定义的错误类型即可
        raise UserError
    return "后获取用户成功"
if __name__ == '__main__':
    app.run()
原文地址:https://www.cnblogs.com/jiangmingbai/p/13179950.html