唐抉的个人博客

Flask Web开发学习笔记(一)

字数统计: 4.2k阅读时长: 17 min
2022/10/18

安装部署

创建环境

使用pip分别安装virtualenv、simplejson、flask、sqlalchemy模块,其命令用法为:pip install 安装的模块名

virtualenv安装完毕后,新建一个项目文件夹myproject,打开cmd,使用cd切换到myproject目录,通过下列命令来创建虚拟环境venv,命令完成后会在myproject文件夹里生成一个venv文件夹。

1
2
cd myproject
virtualenv venv

激活venv环境

1
venv\scripts\activate

激活virtualenv中的Flask

1
pip install Flask

快速入门

一个最小的应用

在一个.py文件中输入以下代码,并运行,可以得到一个最小的Flask应用。

1
2
3
4
5
6
7
8
9
from flask import Flask #导出Flask类
# 创建了该类的实例,第一个参数为应用模块名,单一模块使用__name__
app=Flask(__name__)

@app.route('/')# 使用装饰器告诉Flask什么样的URL能触发函数
def hello_world():
return 'Hello World!'
if __name__=='__main__':
app.run()# run函数让应用运行在本地服务器上

运行代码后访问http://127.0.0.1:5000,可以在页面中看到Hello World!的问候。

外部可访问的服务器:

若运行这个服务器时,会发现它只能从本地的计算机上访问,网络中其他的用户都不能访问。在调试模式下,用户可以在本地计算机上执行任意Python代码。因此这个行为是默认的。

若禁用了debug或信任所在网络的用户,可以修改调用run()方法来使服务器是公开可用的:

1
app.run(host='0.0.0.0')# 让操作系统监听所有公网IP

调试模式

若启动了调试支持,服务器会在代码修改后自动重新载入,并在发送错误时提供一个相对有用的调试器。

方法一启动调试模式:

直接在应用对象上设置:

1
2
app.debug=True
app.run()

方法二启动调试模式:

作为run方法的一个参数传入:

1
app.run(debug=True)

注意:交互式调试器绝对不能用于生产环境。

路由

Web应用的URL易于人们辨识记忆,对于面向使用低速网络连接移动设备访问的应用特别有用。用route()装饰器把一个函数绑定到对于的URL上,可以实现不访问索引页,直接访问想要的页面。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask 
app=Flask(__name__)

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

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

if __name__=='__main__':
app.run()
#运行结果如下:
默认页面 Index Page
输入127.0.0.1:5000/hello路由时,跳转到Hello World!的页面

不仅如此,还可以构造含有动态部分的URL,也可以在一个函数上附着多个规则。

变量规则

给URL添加变量部分,可以把这些特殊的字段标记为<variable_name>,这个部分将会作为命名参数传递到函数中。规则可以用<converter:variable_name>指定一个可选的转换器。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import Flask 
app=Flask(__name__)

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

@app.route('/user/<username>')
def show_user_profile(username):
return 'User %s' % username

@app.route('/post/<int:post_id>')
def show_post(post_id):
return 'Post %d' % post_id
if __name__=='__main__':
app.run()
#运行结果如下:
默认页面 Index Page
输入127.0.0.1:5000/user/ok路由时,跳转到User ok的页面
输入127.0.0.1:5000/post/8080路由时,跳转到Post 8080的页面

转换器有下面几种:

int 接受整数
float 同 int ,但是接受浮点数
path 和默认的相似,但也接受斜线

唯一URL/重定向行为:

Flack的URL规则基于Werkzeug的路由模块,该模块基于Apache以及更早的HTTP服务器主张的先例,保证优雅且唯一的URL。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from flask import Flask 
app=Flask(__name__)

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

@app.route('/projects/')
def projects():
return 'The project page'
@app.route('/about')
def about():
return 'The about page'
if __name__=='__main__':
app.run()
#运行结果如下:
默认页面 Index Page
输入127.0.0.1:5000/projects/路由时,跳转到The project page的页面
输入127.0.0.1:5000/projects路由时,跳转到The project page的页面
输入127.0.0.1:5000/about路由时,跳转到The about page的页面
输入127.0.0.1:5000/about/路由时,跳转到404 Not Found的页面

如此可见,指向projects的规范带结尾斜线,访问结尾不带斜线的URL会被Flask重定向到带斜线的规范URL中。而指向about的规范不带结尾斜线,访问结尾带斜线的URL会产生一个404"Not Found"错误。

这一行为使得在遗忘结尾斜线时,允许关联的URL接任工作,同时也保证了URL的唯一,有助于避免搜索引擎索引同一个页面两次。

构造URL

FLask不仅可以匹配URL,还可以用url_for()来给指定的函数构造URL。它接收函数名作为第一个参数,也接受对于URL规则的变量部分的命名函数。位置变量部分会添加到URL末尾作为查询函数。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from flask import Flask,url_for
app=Flask(__name__)

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

@app.route('/user/<username>')
def profile(username):
return 'The profile page'

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

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

#运行结果如下:
默认页面 Index Page
输入127.0.0.1:5000/login路由时,跳转到The login page的页面
输入127.0.0.1:5000/login?next=路由时,跳转到The login page的页面
输入127.0.0.1:5000/user/Bob路由时,跳转到The profile page的页面

构建URL而非在模板中硬编码的理由:

  • 反向构建通常比硬编码的描述性更好,它允许一次性修改URL,而不是到处边找边改
  • URL构建会转义特殊字符和Unicode数据
  • 若应用不位于URL的根路径,url_for()会妥善处理这个问题

HTTP方法

HTTP方法告知服务器,客户端想对请求的页面做些什么,下面是常见的HTTP方法

GET

浏览器告知服务器:只获取页面上的信息并发给我。

浏览器告诉服务器:欲获取信息,但是只关心消息头。应用应像处理GET请求一样来处理它,但不分发实际内容。在Flask中完全无需人工干预。

POST

浏览器告诉服务器:想在URL上发布新消息。并且服务器必须确保数据已存储且只存储一次。这是HTML表通常发送数据到服务器的方法。

PUT

类似于POST但服务器可能触发了存储过程多次,多次覆盖掉旧值。考虑到传输中连接可能会丢失,在这种情况下浏览器和服务器之间的系统可能安全地第二次接收请求,而不破坏其他东西。

DELETE

删除给定位置的信息。

OPTIONS

给客户端提供一个途径来弄清这个URL支持哪些HTTP方法。

HTTP有许多不同的访问URL方法。默认情况下,路由只回应GET请求,但是通过route()装饰器传递methods参数可以改变这个行为。例如:

1
2
3
4
5
6
@app.route('login',methods=['GET','POST'])
def login():
if request.method=='POST':
de_the_login()
else:
show_the_login_form()

若存在GET,会自动添加HEAD,无需干预,确保遵照HTTP协议处理HEAD请求。

静态文件

动态web应用也会需要静态文件,通常是CSS和JavaScript文件。理想状况下,已经配置好Web服务器来提供静态文件,但在开发中,Flask也可以做到。只要在包中或是,模块所在的目录中创建一个名为static的文件夹,在应用中使用/static即可访问。

给静态文件生成URL,使用特殊的static端,所生成的文件应该存储在static/style.css

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

模板渲染

Flask配备了Jinja2模板引擎来生成HTML,可以使用render_template()方法来渲染模板,将模板名和关键字参数传入模板的变量。例如:

1
2
3
4
5
from flask import render_template
@app.route('/hello/')
@app.route('hello/<name>')
def hello(name=None):
return render_template('hello.html',name=name)

Flask会在templates文件夹里寻找模板,若应用是个模块,则该文件夹与模块通缉,它是个包,则该文件夹作为包的子目录:

1
2
3
4
5
6
7
8
9
#应用是个模块
/application.py
/templates
/hello.html
#应用是个包
/application.py
/__init__.py
/templates
/hello.html

自动转义功能在.html.htm.xmlxhtml扩展名的模板中是默认是开启的,若name包含HTML,它将会被自动转义。从字符串加载的模板禁用自动转义。若信任一个变量,并且知道它是安全的,可以用Markup类或|safe过滤器在模板中把它标记为安全的。

访问请求数据

对于Web应用,与客户端发送给服务端的数据交互至关重要。在Flask中由全局的request对象来提供这些信息。环境作用域保证了对象是全局的情况下,线程也是安全的。

环境局部变量

依赖于一段请求对象的代码,因没有请求对象无法正常运行,需要自行创建一个请求对象并把它绑定到环境中。做单元测试最简单的解决方案是:用test_request_context()环境管理器,结合with声明,绑定一个测试请求使之能进行交互。例如:

1
2
3
4
5
6
7
from flask import request 
with app.test_request_context('/hello',method='POST'):
assert request.path=='/hello'
assert request.method=='POST'
#或者传递整个WSGI环境给request_context()方法
with app.request_context(environ):
assert request.method=='POST'

请求对象

当前请求的HTTP方法可通过method属性来访问,通过request.form属性来访问表单数据。例如:

1
2
3
4
5
6
7
8
9
@app.route('login',methods=['GET','POST'])
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'
return render_template('login.html',error=error)

当访问form属性中不存在的键时会抛出一个特殊的KeyError异常,可以想捕获标准的KeyError一样来捕获它,若对其不做处理,他会显示一个HTTP 400 Bad Request页面。

可以通过args属性来访问URL中 提交的参数,可以用get参数或捕获KeyError。:

1
searchword=request.args.get('q','')

文件上传

上传文件需要先在HTML表单中设置`enctype="multipart/form-data"属性。已上传的文件存储在内存或者是临时文件夹里,可以通过请求对象的files属性访问它们。每个上传的文件都会存储在这个字典里,可以通过save()方法把文件保存到服务器的文件系统上。例如:

1
2
3
4
5
6
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)

若想知道上传前文件在客户端的文件名是什么,可以访问filename属性,但由于这个值是可以伪造的,故不要信任这个值。若要把文件按客户端提供的文件名存储在服务器上,可以把它传递给Werkzrug提供的secure_filename()函数:

1
2
3
4
5
6
7
8
from flask import request
from werkzeug import secure_filename

@app.route('/upload',methods=['GET','POST'])
def upload_file():
if request.method=='POST':
f=request.files['the file']
f.save('/var/www/uploads/'+secure_filename(f.filename))

Cookies

可以通过cookies属性来访问Cookies,用响应对象的set_cookie方法来设置Cookies。请求对象的cookies属性是一个内容为客户端提交的所有Cookies的字典。

读取Cookies:

1
2
3
4
from flask import request
@app.route('/')
def index():
username=request.cookies.get('username')

存储Cookies:

1
2
3
4
5
6
from flask import request
@app.route('/')
def index():
resp=make_response(render_template(...))
resp.set_cookie('username','the username')
return resp

Cookie是设置在响应对象上的。由于通常视图函数只是返回字符串,之后Flask将字符串转换为响应对象。若要显式地转换,可以使用make_response()函数然后再进行修改。

重定向和错误

用redirect()函数把用户重定向到其他地方。用abort()函数放弃请求并返回错误代码。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
from flask import Flask,request
app=Flask(__name__)

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

@app.route('/login')
def login():
abort(401)
this_is_never_executed()
if __name__=='__main__':
app.run()

默认情况下,错误代码会显示一个黑白的错误页面。可以用errorhandler()装饰器来定制错误页面:

1
2
3
4
5
from flask import render_template
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'),404
#404即告诉Flask该页的错误代码是404,即没有找到,默认为200,即一切正常

关于响应

视图函数的返回值会被自动转换为一个响应对象。若返回值是一个字符串,它 被转换为该字符串为主体的、状态码为200 OK,MIME类型是text/html的响应对象。

Flask把返回值转换为响应对象的逻辑如下:

  1. 若返回的是一个合法的响应对象,它会从视图直接返回。
  2. 若返回的是一个字符串,响应对象会用字符串数据和默认参数创建。
  3. 若返回的是一个元组且元组中的元素可以提供额外的信息。这样的元组必须是(response,status,headers)的形式,且至少包含一个元素。status值会覆盖状态代码,headers可以是一个列表或字典,作为额外的消息标头值。
  4. 若上述条件都不满足,、Flask会假设返回值是一个合法的WSGI应用程序,并转换为一个请求对象。
  5. 若想再视图里操纵上述步骤结果的响应对象,可以使用make_response()函数。
1
2
3
4
5
6
7
8
9
10
#假设有以下视图
@app.errorhandler(404)
def not_found(error):
return render_template('error.html'),404
#只需要把返回值表达式传递给make_response(),获取结果对象并修改,然后再返回它
@app.errorhandler(404)
def not_found(error):
resp=make_response(render_template('error.html'),404)
resp.headers['X-Somthing']='A value'
return resp

会话

除请求对象外,还有一个会话(session)对象。它允许在不同请求间存储特定用户的信息。他说在Cookies的基础上实现的,并且对Cookies进行密钥签名。这意味着用户可以查看Cookies内容但由于没有签名的密钥不能修改它。会话工作的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from flask import Flask,session,redirect,url_for,escape,request
app=Flask(__name__)

@app.route('/')
def index():
if 'username' in session:
return 'Logged in as %s' % escape(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 action="" method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
@app.route('/logout')
def logout():
session.pop('username',None)
return redirect(url_for('index'))
app.secret_kry='A0Zr98j/3yX R~XHH!jmN]LWX/,?R'
if __name__=='__main__':
app.run()

生成强密钥:

1
2
3
4
import os    
print(os.urandom(24))
#运行结果如下
b'\x9b\xc9@\x9bN]\xd9]H\xd6,\xfam\x9c\x90\xddu\x19^\x14COG\x93'

使用基于cookie的会话需注意:Flask会将放进会话对象的值序列化值Cookies。若发现某些值在请求之间没有持久存在,而Cookies已经启用且没有明显的错误信息。此时应检查页面响应中Cookies的大小,并与Web浏览器所支持的大小对比。

消息闪现

Flask提供了消息闪现系统,可以简单地给用户返回。消息闪现系统通常会在请求结束时记录信息,并在下一个(且仅在下一个)请求中访问记录的信息。展现这些信息通常要结果模板布局。使用flash()方法可以闪现一条消息,在模板中也可以使用。

日志记录

Flask预置了日志系统,附带的logger是一个标准日志类Logger:

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

整合WSGI中间件

若想给应用添加WSGI中间件,可以封装内部WSGI应用。如想用Werkzrug包中的某个中间件来应付lighttpd中的bugs:

1
2
from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app=LighttpdCGIRootFix(app.wsgi_app)
CATALOG
  1. 1. 安装部署
    1. 1.1. 创建环境
    2. 1.2. 激活venv环境
    3. 1.3. 激活virtualenv中的Flask
  2. 2. 快速入门
    1. 2.1. 一个最小的应用
    2. 2.2. 外部可访问的服务器:
    3. 2.3. 调试模式
      1. 2.3.1. 方法一启动调试模式:
      2. 2.3.2. 方法二启动调试模式:
    4. 2.4. 路由
    5. 2.5. 变量规则
    6. 2.6. 唯一URL/重定向行为:
    7. 2.7. 构造URL
    8. 2.8. HTTP方法
      1. 2.8.1. GET
      2. 2.8.2. HEAD
      3. 2.8.3. POST
      4. 2.8.4. PUT
      5. 2.8.5. DELETE
      6. 2.8.6. OPTIONS
    9. 2.9. 静态文件
    10. 2.10. 模板渲染
    11. 2.11. 访问请求数据
      1. 2.11.1. 环境局部变量
      2. 2.11.2. 请求对象
    12. 2.12. 文件上传
    13. 2.13. Cookies
    14. 2.14. 重定向和错误
    15. 2.15. 关于响应
    16. 2.16. 会话
    17. 2.17. 消息闪现
    18. 2.18. 日志记录
    19. 2.19. 整合WSGI中间件