Python Web Flask框架学习(二)

Flask框架学习(一):Python Web Flask框架学习(一)

参考

Request对象

  • 通过flask的requst对象可以访问由POST或PUT请求传来的数据,只需调用request中的form属性即可。
  • form属性本身默认为ImmutableMultiDict属性,但是可以通过设置parameter_storage_class来改变其类型。
  • 利用request,我们可以实现一个简单的登陆页面:
    @app.route("/")
    @app.route("/<name>")
    def hello(name=None):
        return render_template("hello.html", name=name)
    
    
    @app.route("/login", methods=["GET", "POST"])
    def login():
        if request.method == "POST":
            if request.form['username'] == "admin" and request.form['password'] == "adminonly": # 通过form属性访问login页面POST来的用户名和密码
                return hello(name=request.form['username']) # 这里会引发一个问题,后文引入Redirect后会解决
            else:
                errmsg = "Username or Password is invalid."
                return error(errmsg=errmsg)
        return render_template("login.html")
    
    
    @app.route("/error")
    @app.route("/error/<ErrMsg>")
    def error(errmsg):
        return render_template("error.html", ErrMsg=errmsg)

    hello页面:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Hello page</title>
    </head>
    <body>
        <h2>
            This is a Hello Page
        </h2>
    
        {% if name %}
        <p>
            Hello, {{name}}
        </p>
        {% else %}
        <p> Hello World</p>
        {% endif %}
    </body>
    </html>

    Login页面:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Login</title>
    </head>
    <body>
        <form method="post" action="login">
            <table>
                <tr>
                    <td>Username:</td>
                    <td>
                        <input type="text" id="username" name="username" />
                    </td>
                </tr>
                <tr>
                    <td>Password:</td>
                    <td>
                        <input type="password" id="password" name="password" />
                    </td>
                </tr>
                <tr>
                    <td></td>
                    <td>
                        <input type="submit" value="Submit" />
                    </td>
                </tr>
            </table>
        </form>
    </body>
    </html>

    Error界面:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Error</title>
    </head>
    <body>
        <h2>
            This is a Error Page
        </h2>
        <label>
            {{ ErrMsg }}
        </label>
    </body>
    </html>

    对于此登录模块,输入正确用户名密码后会转向hello界面并显示用户名,输入账号密码不合法则会转向error页面。

  • 在请求form属性中的值时,如果form中没有对应的键,会抛出一个特殊的KeyError异常。其可以被标准的KeyError捕获到,但如果置之不理则会显示一个HTTP 400 Bad Request页面出来。
  • 对于通过URL传来的数据(如?key=value),可以使用args属性来获取
    searchword = request.args.get('key', '')
  • 代码中的注释小问题如下图,虽然显示为hello页面,但URL处依然为login。原因是return时只调用了hello方法,hello方法只是返回了一个HTML页面模板,使其显示在浏览器中,实际上还是停留在login这个方法中的,这里应该使用后文的redirect

文件上传

  • 只要在前端的form中将enctype设置为multipart/form-data即可实现文件上传。如果不指定为multipart/form-data,浏览器不会传输文件。(注意:是form的enctype,区别于input)
  • 使用requst.files实现文件上传
    @app.route("/upload", methods=["GET", "POST"])
    def upload_file():
        if request.method == "POST":
            f = request.files['txt'] # 获取post来的文件
            f.save('your/path/to/save/file') # 保存到本地
        return render_template("upload.html")

    upload页面:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Upload</title>
    </head>
    <body>
        <form method="post" enctype="multipart/form-data">
            <input type="file" id="txt" name="txt"/>
            <input type="submit" value="Submit" >
        </form>
    </body>
    </html>
  • 如果想以原文件名(客户端上传时的文件名)保存到服务端,可以通过访问filename属性实现。但是这个属性是可以被伪造的,所以不能相信这个值。官方建议使用Werkzeug提供的secure_filename()方法来实现这一功能:
    f.save('/var/www/uploads/' + secure_filename(f.filename))

Cookies

  • 通过cookies属性可以访问cookies。通过response对象的set_cookie方法可以设置cookies。如果使用Session,则不要直接使用cookies,因为Sessions更安全。(原文:If you want to use sessions, do not use the cookies directly but instead use the Sessions in Flask that add some security on top of cookies for you.)
  • 我们可以在cookies中保存用户名,然后在hello页面中读取cookies来显示用户名,而不用将用户名传递给hello页面:
    @app.route("/")
    @app.route("/<name>")
    def hello(name=None):
        flash("Welcome!")
        get_name = request.args.get("name")
        cookie_name = request.cookies.get('username') # 读取cookies
        # 当cookies或者URL参数为空时,name将会为None
        if get_name != "" and get_name is not None:
            name = get_name
        elif cookie_name != "" and cookie_name is not None: 
            name = cookie_name
            
        return render_template("hello.html", name=name)
    
    
    @app.route("/login", methods=["GET", "POST"])
    def login():
        if request.method == "POST":
            if request.form['username'] == "admin" and request.form['password'] == "adminonly":
                resp = redirect(url_for("hello")) # 这里的redirect方法在后文会讲到
                resp.set_cookie('username', request.form['username'], max_age=30) # max_age参数设置cookies的有效时间
                return resp
            else:
                errmsg = "Username or Password is invalid."
                return error(errmsg=errmsg)
        return render_template("login.html")

  • 另外,set_cookies方法必须由response对象调用。Flask中可以使用make_response方法来新建一个response对象,但是实际使用中有很多方法的返回值就是response对象(例如上面代码中的redirect),不需要专门新建response对象。具体有哪些方法可以查看官方文档。

重定向和错误

  • 重定向

    • 之前通过直接调用方法来跳转所引发的问题可以使用redirect方法解决,其直接将用户重定向至另一个端点:
      resp = redirect(url_for("hello"))
      # 这里的redirect返回了一个response对象
      # 使用url_for生成URL,以免手打出现错误
      # url_for传入的是方法的名称,不是具体的URL
  • 错误页面

    • abort方法可以在拒绝请求时返回错误代码,默认情况下会显示一个黑白的错误界面
      @app.route("/oldpath")
      def old_path():
          abort(401)
          return "This is a secret page, you should not see this message."

    • 如果想修改错误页面,可以使用errorhandler()修饰器
      @app.errorhandler(401)
      def fashion_oldpath(error): # 参数error是必须声明的,不谈会报错
          return render_template("fashion_401_page.html"), 401 # 载入一个自订模板

Sessions

  • 使用session前,需要为session设定密钥
    app = Flask(__name__)
    app.secret_key = b'Thisisareallycomplicateduniquesecrectkey!'

    如果没有设置密钥,运行到session代码时会报错

  • 通过session对象来使用session,通过session显示用户名:
    def hello(name=None):
        flash("Welcome!")
        get_name = request.args.get("name")
        cookie_name = request.cookies.get('username')
        
        if get_name != "" and get_name is not None:
            name = get_name
        elif cookie_name != "" and cookie_name is not None:
            name = cookie_name
        elif 'username' in session:
            name = session['username']
            
        return render_template("hello.html", name=name)
    
    
    @app.route("/login", methods=["GET", "POST"])
    def login():
        if request.method == "POST":
            if request.form['username'] == "admin" and request.form['password'] == "adminonly":
                session['username'] = request.form['username']
                return redirect("/")
            else:
                errmsg = "Username or Password is invalid."
                return error(errmsg=errmsg)
        return render_template("login.html")
  • 当session被使用时,cookies会被加密,用户依然可以查看cookies,但是无法修改(除非拿到密钥)。
  • 官方推荐的生成密钥指令
    $ python -c 'import os; print(os.urandom(16))'