Flask学习笔记

本文介绍如何使用Python的Flask框架快速构建Web应用,包括环境配置、基本应用搭建、动态URL处理、请求处理、模板渲染等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

最近要把工程项目部署到服务器上。由于项目是用python3写的,所以大致学习了一下python的Flask架构,以此构建网络应用。初步体验了一下,只需要一两分钟就可以编写出简单的网站。

也可以直接看官网资料学习

用Flask可以方便快捷地实现后端服务(因为python用起来更无脑简单),为小规模的访问提供支持。

配置环境

  1. 配置pipvenv虚拟环境,便于迁移 pip -m venv .venv(可选)
  2. 安装Flask:pip install Flask

简单运行

最简单的Flask应用只需要一个文件就可以了。创建web_server.py文件,注意不能用flask.py作为文件名

#导入包
from flask import Flask 

#安全策略,escape函数可以过滤<,>,',",&等特殊字符
from markupsafe import escape  

#将当前文件作为单一模块导入。__name__也可以换成一个包名
app = Flask(__name__)	

#装饰器,当url为根时执行index()函数
@app.route('/')			
def index():
    return 'Index Page'

@app.route('/hello')
def hello():
    return 'Hello, World'

编辑好后,临时设置环境变量并运行:

  • Windows下,使用cmd时:set FLASK_APP=web_server.py
  • Windows下,使用powershell时:$env:FLASK_APP = "web_server.py"
  • Linux下:export FLASK_APP=web_server.py

不设置FLASK_APP时,会查找文件名为wsgi.py或app.py的文件来启动。

然后使用flask run就可以开启本地的flask服务(不是执行这个py文件)。

在这里插入图片描述

然后在浏览器里访问http://127.0.0.1:5000即可看到网站了。当然,这里只是本地调试方便,而不是正式部署在服务器上。如果5000端口已经被占用,可以自行指定别的端口,比如flask run --port 5001

也可以直接python web_server.py启动python文件本身:

if __name__ == '__main__':
	app.run(host='0.0.0.0', port = 4000)

这里介绍一种比较简单的部署方法,使用的是CGI,其适应于所有主流服务器,但是其性能稍弱。

像上面这些使用装饰器修饰的函数又称为视图函数,这些函数的返回值会自动转化为一个响应对象。根据响应对象的类型,有如下转化规则:

  1. 如果视图返回的是一个响应对象,那么就直接返回它。
  2. 如果返回的是一个字符串,那么根据这个字符串和缺省参数生成一个用于返回的 响应对象。
  3. 如果返回的是一个字典,那么调用 jsonify 创建一个响应对象。
  4. 如果返回的是一个元组,那么元组中的项目可以提供额外的信息。元组中必须至少 包含一个项目,且项目应当由 (response, status) (response, headers) 或者 (response, status, headers) 组成。 status的值会重载状态代码, headers 是一个由额外头部值组成的列表 或字典。
  5. 如果以上都不是,那么 Flask 会假定返回值是一个有效的 WSGI 应用并把它转换为 一个响应对象。

Flask可以方便地构建动态url,并且页面内容可以据此进行响应。有点像临时创建动态网页的感觉,十分方便。

#使用<varname>来表示动态参数传递,可以使用<vartype:varname>指定参数类型和参数名
#这里的 int 范围是自然数 还可以使用float指定非负浮点数
@app.route('/post/<int:post_id>') # post_id必须和下面函数参数post_id命名保持一致
def show_post(post_id):
    # show the post with the given id, the id is an integer
    return 'Post %d' % post_id

#字符串参数使用 string 来指定,但是可以缺省
#string是不包含 '/'的字符串
#这里的@app是根据之前的app=Flask(__name__)来确定的
@app.route('/user/<username>')
def show_user_profile(username):
    # show the user profile for that user
    return 'User %s' % escape(username)

# 如果字符串包含'/',那么它是一个路径串,需要用path指定
@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    # show the subpath after /path/
    return 'Subpath %s' % escape(subpath)

此外还有一种uuid类型。至于uuid是什么,详情见这里

#projects后面加上'/'后,访问/projects时将自动补上'/'
@app.route('/projects/')
def projects():
    return 'The project page'

#如果不加'/',访问/about/会404
@app.route('/about')
def about():
    return 'The about page'

flask也可以根据GET、POST等不同的HTTP方法来应答。默认时,只处理GET方法。

如果当前使用了 GET 方法, Flask 会自动添加 HEAD 方法支持,并且同时还会 按照 HTTP RFC 来处理 HEAD 请求。同样, OPTIONS 也会自动实现。

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()

程序可以使用url_for()函数来构建URL,该函数的第一个参数是@app.route修饰的那些函数名字符串,其他的参数如果和那些参数使用的参数名一样则可以表示传参,不同则用作查询,比如下面的例子:

from flask import url_for

app = Flask(__name__)

@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'))                                # /login
    print(url_for('login', next='/'))                      # /login?next=/
    print(url_for('profile', username='John Doe'))         # /user/John%20Doe

可见url_for()可以自动处理地址、url转义保护。

渲染返回页面模板

使用render_template()方法来渲染模板,如下所示。此外需要注意的是,这里两个@app.route说明不同的url可以由相同的函数处理。第一个/hello/没有加name,所以hello函数的name参数需要添加默认值,否则访问时会报错。

from flask import render_template

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

render_template会在templates文件夹下寻找模板文件。文件组织结构应当如下所示:

#如果服务器代码写在一个文件里
/application.py
/templates
    /hello.html

#如果服务器是一个包
/application
    /__init__.py
    /templates
        /hello.html

其中,模板文件应当遵照Jinja2的语法规则,比如对于上面的代码,hello.html可以这么写:

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

其中,

  • {% xxx %}用于存放函数语句,用来控制文档结构
  • {{ xxx }}用于存放表达式,用来显示文本
  • {# xxx #}用于写注释

请求数据处理

使用request来处理请求,如下所示。此处request.method可以获取请求的方法,request.form['xxx']用于处理具体的表单数据。若form没有拿到变量,会抛出KeyError。此外也可以用request.args.get('key', '')拿到url中?key=value对应的参数。如果前端请求头是application/json格式,后端可以用request.get_json()来拿数据

from flask import request
app = Flask(__name__)

@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)

from werkzeug.utils import secure_filename
app.config['UPLOAD_FOLDER'] = 'upload/'
#也可以用来处理文件上传,注意表单需要设定enctype="multipart/form-data"
@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')
        f.save(os.path.join(app.config['UPLOAD_FOLDER'],secure_filename(f.filename))")
    ...

文件下载

使用send_from_directory函数来管理文件下载

from flask import send_from_directory
app = Flask(__name__)
app.config['DOWNLOAD_FOLDER'] = 'download/'

@app.route('/api/download', methods=['GET'])
def download():
	# 获取访问请求中 ?filename=xxx中的xxx
	filename = request.args.get('filename','')
    try:
        return send_from_directory(app.config['DOWNLOAD_FOLDER'],filename )
    except Exception as e:
        return str(e)

Cookie管理

管理cookies时,如下所示:

# 读取cookies
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.


# 储存cookies
from flask import make_response
@app.route('/')
def index():
    resp = make_response(render_template(...))
    resp.set_cookie('username', 'the username')
    return resp

Session管理

session允许在不同请求之间传递信息,相当于密钥加密的cookies。如下所示:


from flask import session

# 使用session需要设置密钥,相当于加密的cookies。一般使用随机字符,密钥必须严格保密
# 可以使用python -c 'import secrets; print(secrets.token_hex())' 快捷生成一个密钥
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'))

页面重定向与错误返回

使用redirect()方法来重定向页面,使用abort()方法来退出请求并返回错误代码,如下所示:

from flask import abort, redirect, url_for

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

@app.route('/login')
def login():
    abort(401) # 401,禁止访问
    this_is_never_executed()

使用errorhandler()方法来定制出错页面

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

使用make_response()方法来在试图内部创建响应对象,如下所示:

@app.errorhandler(404)
def not_found(error):
    resp = make_response(render_template('error.html'), 404)
    resp.headers['X-Something'] = 'A value'
    return resp

Dict返回值自动处理为json响应

如果从视图返回一个dict,会自动转化为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),
    }

#也可以使用jsonfy来进行更复杂的转化
@app.route("/users")
def users_api():
    users = get_all_users()
    return jsonify([user.to_json() for user in users])

实用性配置

CORS规避

有时,浏览器会禁止跨站访问,常见于前后端url不一致。可以在脚本里添加响应头:

试加这个库 pip install flask-cors

from flask_cors import CORS

# 跨域支持
app = Flask(__name__)
CORS(app)
if __name__ == '__main__':
    app.run(host='0.0.0.0', port=4000)

支持HTTPS

if __name__ == '__main__':
	app.run(host='0.0.0.0', port=4000, ssl_context=('<公钥文件路径>', '<私钥文件路径>'))

缓存

可以用python装饰器来实现缓存功能

from functools import wraps

def cached(max_cache_time=300):
    """
    缓存装饰器,可以自定义最大缓存时间(秒)
    """
    def decorator(func):
        # 缓存字典
        cache = {}

        @wraps(func)
        def wrapper(*args, **kwargs):
            # 生成缓存键
            key = (func.__name__, args, frozenset(kwargs.items()))

            # 检查缓存
            if key in cache and time.time() - cache[key][0] < max_cache_time:
                return cache[key][1]

            # 执行函数并缓存结果
            result = func(*args, **kwargs)
            cache[key] = (time.time(), result)
            return result

        return wrapper
    return decorator

#之后使用,举例

# 显示服务器硬件信息
@app.route('/server/hardware')
@cached(max_cache_time=86400)
def hardware():
    connection = get_db()
    result = []
    with connection.cursor() as cursor:
        cursor.execute(f'SELECT * FROM {Database.TB_BASIC} WHERE `display` is true;')
        while True:
            row = cursor.fetchone()
            if row is None:
                break
            result.append(row)
    return result

例子

下面将以上各种内容进行汇总,快速实现后端逻辑时直接复制粘贴使用:


import json
from flask import Flask, jsonify, send_from_directory
from flask import request
import pymysql.cursors
import time
import datetime
import re
from flask_cors import CORS
from functools import wraps
from pathlib import Path

# 缓存字典
cache = {}
# 如果Flask同时担任网页前端服务器功能,可以不使用nginx,后续用send_from_directory直接返回构建好的页面
#app = Flask(__name__, static_folder='experiment_viewer')
#WORKSPACE_ROOT = Path(__file__).resolve().parent
#EXPERIMENT_VIEWER_DIR = WORKSPACE_ROOT / 'experiment_viewer'
#@app.route('/')
#def serve_index():
#    return send_from_directory(EXPERIMENT_VIEWER_DIR , 'index.html') 
app = Flask(__name__)
CORS(app)
class Database:
    TB_BASIC = 'server_basic'
    TB_APPLY = 'user_apply'
    def __init__(self):
        host = "localhost"

def cached(max_cache_time=300):
    """
    缓存装饰器,可以自定义最大缓存时间(秒)
    """
    def decorator(func):
        # 缓存字典
        cache = {}
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 生成缓存键
            key = (func.__name__, args, frozenset(kwargs.items()))
            # 检查缓存
            if key in cache and time.time() - cache[key][0] < max_cache_time:
                return cache[key][1]
            # 执行函数并缓存结果
            result = func(*args, **kwargs)
            cache[key] = (time.time(), result)
            return result
        return wrapper
    return decorator

# 数据库连接
def get_db():
    connection = pymysql.connect(host='localhost',
                                 port=1111,
                                 user='root',
                                 password='xxx',
                                 db='xxx',
                                 cursorclass=pymysql.cursors.DictCursor)
    return connection

# 更新服务器数据
@app.route('/api/server/upload', methods=['GET','POST'])
def upload_info():
    post_data = request.get_json()
    connection = get_db()
    if post_data['code'] == 0:
        _st = f"FROM_UNIXTIME({post_data['_st']})"
        hostname = f"'{post_data['hostname']}'"
        sql = []
        if post_data['service'] == 'check_disk_usage':
            data = "'"+json.dumps(post_data['data'])+"'"
            sql.append(f"UPDATE {Database.TB_BASIC} SET storage={data}, _st={_st} WHERE hostname={hostname}")
        with connection.cursor() as cursor:
            for target_sql in sql:
                cursor.execute(target_sql)
                connection.commit()
    return {'hostname': post_data['hostname'], 'service': post_data['service']}

# 显示服务器硬件信息
@app.route('/api/server/hardware')
@cached(max_cache_time=86400)
def hardware():
    connection = get_db()
    result = []
    with connection.cursor() as cursor:
        cursor.execute(f'SELECT * FROM {Database.TB_BASIC} WHERE `display` is true;')
        while True:
            row = cursor.fetchone()
            if row is None:
                break
            result.append(row)
    return result

# 申请账号
@app.route('/api/user/apply', methods=['GET','POST'])
def apply():
    if request.method == 'GET':
        result = []
        connection = get_db()
        with connection.cursor() as cursor:
            cursor.execute(f'SELECT * FROM {Database.TB_APPLY} ORDER BY `id` DESC;')
            while True:
                row = cursor.fetchone()
                if row is None:
                    break
                row['_st'] = (row['_st'] ).strftime('%Y-%m-%d %H:%M:%S')
                result.append(row)
        return result
    elif request.method == 'POST':
        post_data = request.get_json()
        token = post_data['token']
        if len(token) == 64:
            connection = get_db()
            with connection.cursor() as cursor:
                cursor.execute(f"SELECT * FROM {Database.TB_APPLY} WHERE `token` = '{token}';")
                row = cursor.fetchone()
                if row is None:
                    realname = post_data['realname']
                    username = post_data['username']
                    pubkey = post_data['pubkey']
                    usernote = post_data['usernote']
                    cursor.execute(f"INSERT INTO {Database.TB_APPLY} (`realname`,`username`,`pubkey`,`usernote`,`token`) VALUES ('{realname}','{username}','{pubkey}','{usernote}','{token}');")
                    connection.commit()
                    return {'error':0,'msg':'申请已提交'}
                else:
                    return {'error':1,'msg':'请勿重复提交申请'}
        else:
            return {'error':1,'msg':'未知错误'}
    
# 申请记录
@app.route('/api/db/fetch')
def get_apply():
    connection = get_db()
    result = []
    with connection.cursor() as cursor:
        cursor.execute(f'SELECT * FROM {Database.TB_APPLY} ORDER BY `id` DESC;')
        while True:
            row = cursor.fetchone()
            if row is None:
                break
            result.append(row)
    return result


if __name__ == '__main__':
    # app.run(host='0.0.0.0', port=4000, ssl_context=('xxx.pem', 'xxx.key'))
    app.run(host='0.0.0.0', port=4000, debug=True)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

c01dkit

好可怜一博主,都没人打赏>_<

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值