zoukankan      html  css  js  c++  java
  • 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()
    
  • 相关阅读:
    ORA01034:ORACLE not available 问题的解决方法
    利用Bulk Insert将Excel中的大批量数据入库
    【Hibernate】*.hbm.xml配置
    lib和dll文件的区别和联系
    oracle ,mysql总date的比较
    C++ Primer 4 CPP Note 1.5 类的简介
    C++ Primer 4 CPP Note 1.4 控制结构
    未找到方法: Dispose System.IO.Stream
    pragma comment的使用
    C++ Primer 4 CPP Note 2.1 基本内置类型
  • 原文地址:https://www.cnblogs.com/jiangmingbai/p/13179950.html
Copyright © 2011-2022 走看看