Flask学习记录

记录服务框架的学习

参考官方教程 Flask框架学习

Flask是什么?

Flask是一个基于Python的微型web框架,它能够快速搭建一个网页或web服务。

目录结构

Flask的目录结构大致如下:

flask-demo/
  ├ run.py           # 应用启动程序
  ├ config.py        # 环境配置
  ├ requirements.txt # 列出应用程序依赖的所有Python包
  ├ tests/           # 测试代码包
  │   ├ __init__.py 
  │   └ test_*.py    # 测试用例
  └ myapp/
      ├ admin/       # 蓝图目录
      ├ static/
      │   ├ css/     # css文件目录
      │   ├ img/     # 图片文件目录
      │   └ js/      # js文件目录
      ├ templates/   # 模板文件目录
      ├ __init__.py    
      ├ forms.py     # 存放所有表单,如果多,将其变为一个包
      ├ models.py    # 存放所有数据模型,如果多,将其变为一个包
      └ views.py     # 存放所有视图函数,如果多,将其变为一个包

快速上手

安装直接pip安装就行,就省略了

HelloWorld

from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
    return 'Hello World'
if __name__ == '__main__':
    app.debug = True # 设置调试模式,生产模式的时候要关掉debug
    app.run()

python运行后访问localhost:5000即可看到

  • Flask类是主要的框架,__name__使得Flask能够找到template等其他东西,让flask.helpers.get_root_path函数通过传入这个名字确定程序的根目录,以便获得静态文件和模板文件的目录。
  • 在flask中,定义路由最简便的方式,是使用程序实例的app.route装饰器,把装饰的函数注册为路由

绑定参数

除了字符串这种静态路由,route也可以绑定参数的路由通过""

from markupsafe import escape

@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return f'User {escape(username)}'

@app.route('/post/<int:post_id>')
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return f'Post {post_id}'

@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    # show the subpath after /path/
    return f'Subpath {escape(subpath)}'
  • 这里的path类指带斜杠的字符串

url_for

url_for可以通过反向路径构造指定函数的路由

from flask import url_for

@app.route('/')
def index():
    return 'index'

@app.route('/login')
def login():
    return 'login'

@app.route('/user/<username>')
def profile(username):
    return f'{username}\'s profile'

with app.test_request_context():
    print(url_for('index'))
    print(url_for('login'))
    print(url_for('login', next='/'))
    print(url_for('profile', username='John Doe'))

'''
Outputs:
/
/login
/login?next=/
/user/John%20Doe
'''

静态文件

静态文件在flask中存储在static文件夹下,使用url_for()能够构造静态文件的URL

url_for('static', filename='style.css')

HTTP方法

HTTP常用的请求方法

  1. get 用来获取资源
  2. post 传输实体获得回复
  3. put 传输文件
  4. head 获取首部
  5. delete等等

在默认情况下,一个路由只会回应GET请求,但是可以使用route()装饰器中的method参数来处理不同的HTTP方法。

from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return do_the_login()
    else:
        return show_the_login_form()

如果GET被设定为可接受,则flask自动的会支持head方法

加载模板

Flask配置了Jinja2模板引擎,我们可以使用它来绘制界面。 通过render_template():

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)

这个hello.html存储在当前目录中的templates文件夹 Jinja2的模板提供了许多强大功能,例如:

<!doctype html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello, World!</h1>
{% endif %}

在模板中同样可以使用各种各样的函数并访问变量。

模板继承

模板继承对于需要频繁重用的网页部件例如导航栏等十分有用。 我们可以创建一个父类模板layout.html:

<!doctype html>

<title>Hello Sample</title>

<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">

<div class="page">

    {% block body %}

    {% endblock %}

</div>

然后再创建一个子类,并继承layout.html:

{%  extends  "layout.html"  %}

{%  block  body  %}

{%  if  name  %}

<h1>Hello {{ name }}!</h1>

{% else %}

<h1>Hello World!</h1>

{%  endif  %}

{%  endblock  %}

此时子类的内容会被加载到block body中,父类的其他内容依然会被显示。

HTML自动转义

我们有时不希望提交的表单中含有的HTML标签被自动转移,因此可以使用Markup类:

from  flask import  Flask,  Markup

app  =  Flask(__name__)

@app.route('/')
def  index():
    return  Markup('<div>Hello %s</div>')  %  '<em>Flask</em>'

format应当放在Markup外面

Markup还有许多方法

request

request对象用来处理请求

@app.route('/login', methods=['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':
        if valid_login(request.form['username'],
                       request.form['password']):
            return log_the_user_in(request.form['username'])
        else:
            error = 'Invalid username/password'
    # the code below is executed if the request method
    # was GET or the credentials were invalid
    return render_template('login.html', error=error)

常用方法

  • request.method可以用来获取当前的HTTP方法
  • request.form可以得到提交表单的内容,如果form中没有需要的键值就会报KeyError,并出现400错误页面
  • request.args.get可以获取URL中的参数

文件上传

传输文件需要在HTML提交表单中设置enctype="multipart/form-data"

上传的文件可以通过request.file获得,是一个python的file对象,并拥有一个save()方法用来存储到本地。

from flask import request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/uploaded_file.txt')
    ...

Cookies

可以使用request.cookies访问cookies,使用set_cookie()设置cookie

from flask import request

@app.route('/')
def index():
    username = request.cookies.get('username')
    # use cookies.get(key) instead of cookies[key] to not get a
    # KeyError if the cookie is missing.

from flask import make_response

@app.route('/')
def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp

make_response()可以生成一个response对象用来对网页返回的内容进行更多修改(例如cookies)

每个经过app装饰器修饰的函数会将return的内容自动转换为response对象。

重导向与报错

redirect()可以改变访问的路由,abort(int)可以进行报错,实际开发中并不经常使用abort,而是进行异常的抛出或捕获。

from flask import abort, redirect, url_for

@app.route('/')
def index():
    return redirect(url_for('login'))

@app.route('/login')
def login():
    abort(401)
    this_is_never_executed()

使用errorhandler()装饰器来装饰报错页面

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

此外,errorhandler也可以自定义异常类型

API

常用的API接口调用会返回JSON,使用Flask返回Json十分简单,返回字典格式即可

@app.route("/me")
def me_api():
    user = get_current_user()
    return {
        "username": user.username,
        "theme": user.theme,
        "image": url_for("user_image", filename=user.image),
    }

对于返回不是字典的api,也可以通过jsonify()来序列化数据

from flask import jsonify

@app.route("/users")
def users_api():
    users = get_all_users()
    return jsonify([user.to_json() for user in users])

Session

session会话,和request一样是flask中的全局对象,用来保存当前请求的一些状态,用来再之后与同一用户的通信中共享信息。session将信息加密并保存在cookies中。

from flask import session

# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'

@app.route('/')
def index():
    if 'username' in session:
        return f'Logged in as {session["username"]}'
    return 'You are not logged in'

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form method="post">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # remove the username from the session if it's there
    session.pop('username', None)
    return redirect(url_for('index'))

日志

app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

SocketIO

WebSocket - 基于 Python 的主流实现方式总结 websocket可以用来进行服务器与客户端之间的持续双工通信 flask作为服务端可以使用flask-socketio库 注意安装需要同时安装python-socketio、python-engineio等等,且版本需要匹配

异步任务

有时需要处理一些耗时的任务,那么就可以通过多线程、协程等异步的进行。 Python Flask后端异步处理

python自带的多线程

我们可以使用ThreadPoolExecutor进行

from concurrent.futures import ThreadPoolExecutor

executor = ThreadPoolExecutor(4)

@app.route('/')
def someTask():
    executor.submit(longTimeTask, *args)

Celery

python自带的库虽然很方便,但是有时我们需要随时获取任务的各种结果,对任务进行更加复杂的操作。 我们还可以使用Celery进行异步分布式任务队列服务,它支持定时任务、即时任务等等。

将localhost映射到公网

通过使用 ngrok ,我们可以把本地的端口映射到公网,在别的地方都能访问,方便我们进行测试 打开下载的exe,输入

> ngrok http 5000

就可以将端口5000映射到公网,具体的网址会显示在cmd中。

测试工具

API

  • 通过postman,我们可以对网站进行各种接口的测试,对网站发送各种形式的请求
  • apipost是一款中文的api测试工具,同样可以对接口进行各种测试。

待补充……