0x00 安装环境

  • sudo apt install python3-venv

0x01 虚拟环境

  • 创建目录:mkdir my_flask_app && cd my_flask_app
  • 创建新的虚拟环境:python3 -m venv venv
  • 运行activate脚本来激活它:source venv/bin/activate
  • 安装Flask:pip install Flask

0x02 HelloFlask

from flask import Flask

# 创建Flask应用程序实例
app = Flask(__name__)

# Flask中定义路由是通过装饰器实现的
@app.route('/')
def hello_world():
    #除了字符串还可以返回模板(网页)内容,后面会学。
    return 'Hello World!'

if __name__ == '__main__':
    app.run()
  • export FLASK_APP=hello.py
  • flask run

输出类似如下的内容:

 * Serving Flask app "hello.py"
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

定义其他的路径:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello_world():
    return "Hello World!<br>I am cominig!"

@app.route('/test')
def test():
    return "This is a test!"

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

排错:

  • 物理机访问不了:
    • sudo ufw allow 5000
    • flask run –host=[虚拟机IP地址]

0x03 请求方式

默认只支持GET,如果需要增加,需要以列表方式自行指定。

from flask import Flask

app = Flask(__name__)

@app.route('/',methods=['GET'])
def hello_world():
    return "Hello World!<br>I am cominig!"

@app.route('/test',methods=['GET','POST'])
def test():
    return "This is a test!"

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

例如这时,以POST形式访问根目录会出现:

Method Not Allowed

The method is not allowed for the requested URL.

0x04 传参示例

使用同一个视图函数,来显示不同页数的文本内容。路由传递的参数默认当string处理。

from flask import Flask

app = Flask(__name__)

@app.route('/',methods=['GET'])
def hello_world():
    return "A Easy Book."

# <>定义路由的参数,<>内需要起一个名字
@app.route('/books/<book_id>',methods=['GET','POST'])
def get_book_id(book_id):
    # 需要在视图函数()内填入形参名,后面的代码才能使用。
    return "what you look is page %s" % book_id

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

优化,对传参的参数类型进行一个限定。

@app.route('/books/<int:book_id>',methods=['GET','POST'])

这样接收到数字以外的字符会返回404 NOT FOUND。

除了int,还可以改为float等。

0x05 返回模板

my_flask_app中新建templates文件夹,现在路径树如下:

├── hello.py
├── templates
└── venv

templates文件夹中新建index.html,随便写写:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>

<h1>My First WebSite</h1>
<p>Could not more easy than this :)</p>

</body>
</html>

在hello.py中导入render_template:

from flask import Flask , render_template

app = Flask(__name__)

@app.route('/',methods=['GET'])
def hello_world():
    return render_template('index.html')

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

0x06 填充数据

如果不想一开始就把模板写死,可以使用{{ }},例如:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>

<h1>My First WebSite</h1>
<p>My Blog:{{ urlstr }}</p>

</body>
</html>

然后在hello.py中给urlstr赋值。

from flask import Flask , render_template

app = Flask(__name__)

@app.route('/',methods=['GET'])
def hello_world():
    url = "shawroot.cc"
    return render_template('index.html',urlstr=url)

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

这样访问网站根目录{{ urlstr }}就被替代为shawroot.cc。

0x07 变量进阶

  • 注释:{#我是一个没什么用的注释。#}
  • 取列表(list_example = [1,3,5,7,9])的下标:{{ list_example.2 }}{{ list.example[2] }}
  • 取字典,hello.py中内容如下
from flask import Flask , render_template

app = Flask(__name__)

@app.route('/',methods=['GET'])
def hello_world():
    url = "shawroot.cc"
    my_dict = {
        'name':'ShawRoot',
        'Age':17,
        'Job':'Stuent'
    }
    return render_template('index.html',urlstr=url,info=my_dict)

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

调用:

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>

<h1>My First WebSite</h1>
<p>My Blog:{{ urlstr }}</p>
<p>My Name:{{ info['name'] }}</p>
<p>My Age:{{ info['Age'] }}</p>
</body>
</html>

0x08 控制代码

  • 使用{% %}来控制代码块。例如,实现字典的循环遍历,给它打印出来(列表同理)。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>

<h1>My First WebSite</h1>
<hr>

{#简单来一个for循环。#}
{% for i in info%}
    {{ i }} <br>
{% endfor %}

</body>
</html>
  • 参考:https://www.codenong.com/24163579/。实现字典的循环遍历,输出长度大于4的内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>ShawRoot</title>
</head>
<body>

<h1>My First WebSite</h1>
<hr>
{% for i in info%}
    {% if i|length >=4 %}
        {{ i }} <br>
    {% endif %}
{% endfor %}
</body>
</html>

0x09 用过滤器

过滤器本质就是函数。在模板中不能调用python的某些方法,这时就用到了过滤器。

  • 过滤器使用的格式:变量名|过滤器
  • 常见的过滤器:https://blog.csdn.net/rusi__/article/details/102805022
  • 过滤器支持链式调用,例如{{ 'shawroot' | reverse | upper }}

0x0A 表单验证

例如,如果想用普通方法实现一个简单的登录表单,模板中的内容如下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ShawRoot</title>
</head>
<body>

<form method="post">
  <label>username:</label><input type="text" name="username"><br>
  <label>password:</laber><input type="password" name="password"><br>
  <label>confirm password:</laber><input type="password" name="password2"><br>
  <input type="submit" value="submit"><br>
</form>

</body>
</html>

在视图函数中需要检查三个字段(username、password、password2)是否为空,以及“输入密码”和“确认密码”字段的内容是否相同。引入request库,判断请求类型,获取参数值的方法如下所示:

from flask import Flask , render_template , request

app = Flask(__name__)

@app.route('/',methods=['GET','POST'])
def index():
    # 判断请求方式
    if request.method == 'POST':
        # 获取请求参数
        username = request.form.get('username')
        password = request.form.get('password')
        password2 = request.form.get('password2')
        # 判断请求参数是否为空
        if username != "" and password != "" and password2 != "":
            # 两次输入的密码是否相同
            if password != password2:
            	return 'Please Check Out Your Password.'
            else:
            	return 'Login Success!'
        else:
            return 'NOT ALLOWED.'

    return render_template('index.html')

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

如果希望给模板中传递消息(即return 'Login Success!'更改为flash( 'Login Success!' )),可以导入flash库。

然后在模板底部需要遍历所有消息:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ShawRoot</title>
</head>
<body>

<form method="post">
  <label>username:</label><input type="text" name="username"><br>
  <label>password:</laber><input type="password" name="password"><br>
  <label>conform password:</laber><input type="password" name="password2"><br>
  <input type="submit" value="submit"><br>

  {# 使用遍历获取闪现的消息。#}
  {# 因为是函数,所以别忘记get_flashed_messages后要加()。#}
  {% for message in get_flashed_messages() %}
      {{ message }}
  {% endfor%}

</form>

</body>
</html>

flash闪现的消息是需要保密的,要设置一个secret_key,做一个加密消息的混淆(不设置会报错):

from flask import Flask , render_template , request , flash

app = Flask(__name__)
app.secret_key = "shawroot"

@app.route('/',methods=['GET','POST'])
def index():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        password2 = request.form.get('password2')
        if username != "" and password != "" and password2 != "":
            if password != password2:
            	flash('Please Check Out Your Password.')
            else:
            	flash('Login Success!')
        else:
            flash('NOT ALLOWED.')

    return render_template('index.html')

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

这样,内容即可在表单下面回显。

0x0B WTF表单

  • 安装Flask-WTF扩展:pip install Flask-WTF
  • 在Flask中,为了处理web表单,一般使用Flask-WTF扩展。它封装了WTForms,具有验证表单数据的功能。
  • 常用的支持的HTML标准字段如下:
字段类型说明
StringField文本字段
TextAreaField多行文本字段
PasswordField密码文本字段
HiddenField隐藏文本字段
DateField文本字段,值为datetime.date格式
IntegerField文本字段,值为整数
FloatField文本字段,值为浮点数
BooleanField复选框,值为True和False
RadioField一组单选框
SelectField下拉列表
SelectMultipleField下拉列表,可选择多个值
FileField文件上传字段
SubmitField表单提交按钮
WTForms 常用的支持的HTML标准字段

使用WTF时,会在视图函数中定义类,在类中说明要写入的表单的格式:

from flask import Flask,render_template,request,flash
from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,SubmitField 

app = Flask(__name__)
app.secret_key = "shawroot"

class LoginForm(FlaskForm):
    username = StringField('UserName:')
    password = PasswordField('Password:')
    confirm = PasswordField('Confirm Password:')
    submit = SubmitField('Submit')
    
@app.route('/',methods=['GET','POST'])
def login():
    login_form = LoginForm()
    return render_template('index.html',form=login_form)

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

然后在模板中使用:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ShawRoot</title>
</head>
<body>

<form method="post">
    {{ form.username.label }}{{ form.username }} <br>
    {{ form.password.label }}{{ form.password }} <br>
    {{ form.confirm.label }}{{ form.confirm }} <br>
    {{ form.submit }}
    
</form>

</body>
</html>

0x0C WTF验证

0x0b只介绍了WTF表单如何放置在模板中,但是没有介绍如何进行逻辑验证。

因为是登录表单,要确保用户名、密码、确认密码字段不能为空。这时需要引用WTForms提供的Validators来导入验证函数:

from wtforms.validators import DataRequired,EqualTo

常见的验证函数如下:

验证函数说明
DataRequired确保字段中有数据
Email验证电子邮件地址
EqualTo比较两个字段的值,常用于要求输入两次密码进行确认的情况
IPAddress验证IPv4网络地址
Length验证输入字符串的长度
NumberRange验证输入的值在数字范围内
Optional无输入值时跳过其他验证函数
Regexp使用正则表达式验证输入值
URL验证URL
AnyOf确保输入值在可选值列表中
NoneOf确保输入值不在可选列表中
WTForms 常用的验证函数

一个变量可以导入多个验证函数,EqualTo('需要比较的另一个字段','不一致时显示的消息')

class LoginForm(FlaskForm):
    username = StringField('UserName:',validators=[DataRequired()])
    password = PasswordField('Password:',validators=[DataRequired()])
    confirm = PasswordField('Confirm Password:',validators=[DataRequired(),EqualTo('password','NOT MATCH!')])
    submit = SubmitField('Submit')

还需要判断请求方式,WTF表单可以一句代码搞定验证逻辑。

  • 如果校验成功(if login_form.validate_on_submit())判断就会返回真。
  • 校验成功的前提还需要加上CSRF Token,需要在表单前面开启CSRF({{ form.csrf_token() }})。
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>ShawRoot</title>
</head>
<body>

<form method="post">
    {{ form.csrf_token() }}
    {{ form.username.label }}{{ form.username }} <br>
    {{ form.password.label }}{{ form.password }} <br>
    {{ form.confirm.label }}{{ form.confirm }} <br>
    {{ form.submit }}
    
    {% for message in get_flashed_messages() %}
      {{ message }}
    {% endfor%}
</form>

</body>
</html>

视图函数代码如下:

from flask import Flask,render_template,request,flash
from flask_wtf import FlaskForm
from wtforms import StringField,PasswordField,SubmitField 
from wtforms.validators import DataRequired,EqualTo

app = Flask(__name__)
app.secret_key = "shawroot"

class LoginForm(FlaskForm):
    username = StringField('UserName:',validators=[DataRequired()])
    password = PasswordField('Password:',validators=[DataRequired()])
    confirm = PasswordField('Confirm Password:',validators=[DataRequired(),EqualTo('password','NOT MATCH!')])
    submit = SubmitField('Submit')
    
@app.route('/',methods=['GET','POST'])
def login():
    login_form = LoginForm()
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        confirm = request.form.get('confirm')
        if login_form.validate_on_submit():
            return 'Login Success!'
        else:
            flash('Something Wrong :(')    
    return render_template('index.html',form=login_form)

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

需要注意的是,如果这时在表单输入不一致的密码,返回的不是“NOT MATCH”而是“Something Wrong :(”,我们需要捕获里面的错误进行显示。