@Mr-13
2019-12-21T02:28:15.000000Z
字数 11225
阅读 60
python3
Flask是一个轻量级的基于python的web框架。
安装Flask之前,关于python、pip的安装这里不多赘述
如果是在Linux直接编辑运行代码,需要先安装Flask;如果是使用Pycharm等IDE工具,在IDE直接引用安装即可
打开CMD,执行:
pip install flask
话不多说,用Pycharm开始第一个程序,新建一个工程,键入代码如下:
如果是在Linux下,编辑保存后,进入该文件目录下运行程序
#python hello.py
Pycharm直接运行,提示如下图字样后,就说明服务启动完成;
打开浏览器,访问 http://127.0.0.1:5000/
就可以看到运行效果了
简单解释下这段代码
1. 首先引入Flask包,并创建一个Web应用的实例"app"
这里给的实例名就是这个python模块名。
from flasf importFlask
app = Flask(__name__)
这个函数级别的注解指明了当前地址是根路径时,就调用下面的函数。可以定义多个路由规则;说的高大上些,这里就是MVC中的Contollr。
@app.route('/')
3. 处理请求
当请求的地址符合路由规则时,就会进入该函数。可以说,这里是MVC的Model层。你可以在里面获取请求的request对象,返回内容就是response。本例中的response就是大标题“HelloWorld”。
def index()
return '<h1>Hello world</h1>'
当程序为文件入口(也就是python命令直接执行本文件)时,就会通过 app.run()
启动Web服务器。如果不是程序入口,那么该文件就是一个模块。Web服务器会默认监听本地5000端口,但不支持远程访问。
如果想要支持远程,需要在 run()
方法中传入 host=0.0.0.0
,想改变监听端口的话,传入 port=端口号
,还可以设置调试模式。
具体例子如下:
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888, debug=True)
注意:
Flask自带的Web服务器主要还是给开发人员调试使用的,在生产环境中,最好还是通过WSGI将Flash部署到Apache或Nginx的服务器上。
从Hello World中,我们了解到URL的路由可以直接写在其要执行的函数上。有人会质疑,这样不是把MOdel和Controller绑在一起了么?
的确,如果你想灵活的配置Model和Controller,这样是不方便,但是对于轻量级的系统来说,灵活配置意义不大,反而写在一块更利于维护。
Flask的路由规则都是基于Werkzeug的路由模块的,它还提供了很多很强大的功能。
我们在上面Hello World的基础上,加上下面的函数。并运行程序
@app.route('/hello/<name>')
def hello(name)
return 'Hello %s' % name
当你在浏览器的地址栏中输入 http://127.0.0.1:5000/hello/man
,你将在页面上看到“Hello man”的字样。URL路径中 /hello/
后面的参数被作为 hello()
函数的 name
参数传了进来。
你还可以在URL参数前添加转换器来转换参数类型,我们再来加个函数:
@app.route(/user/<int:user_id>)
def get_user(user_id):
return 'User ID: %d' % user_id
试下访问 http://127.0.0.1:5000/user/man
,你会看到404错误。
但是试下 http://127.0.0.1:5000/user/123
,页面上就会有“User ID: 123”显示出来。
参数类型转换器 int:
帮你控制了传入参数类型只能是整形。目前支持的参数类型转换器有:
类型转换器 | 作用 |
---|---|
缺省 | 字符型,但不能有斜杠 |
int: | 整形 |
float: | 浮点型 |
path: | 字符型,可有斜杠 |
另外,大家有没有注意到,Flask自带的Web服务器支持热部署。当你修改好文件保存后,Web服务器自动部署完毕,你无需重新运行程序。
即:
在Linux下,只要一次执行了*.py文件;后续的修改,只要保存后,立即可以访问新增的内容;Flask会自动部署。
一个函数上可以设置多个URL路由规则:
@app.route('/')
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
if name is None:
name = 'World'
return 'Hello %s' % name
这个例子接受三种URL规则, /
和 /hello
都不带参数,函数参数 name
值为空,页面显示“Hello World”;
/hello/<name>
带参数,页面会显示参数 name
的值,效果与上面的例子相同。
http请求方法常用的有:get、post、put、delete;Flask路由规则也可以设置请求方法。
from flask import request
@app.route('/login',methods=['GET','POST'])
def login():
if request.methid == 'POST':
return 'This is a POST request'
else:
return 'This is a GET request'
当你请求地址 http://127.0.0.1:5000/login
,“GET”和“POST”请求会返回不同的内容,其他请求方法则会返回405错误。
Flask提供了 url_for()
方法来快速获取及构建URL,方法的第一个参数指向函数名(加过 @app.route
注解的函数),后续的参数对应于要构建URL的变量。下面是几个例子:
url_for('login') # 返回/login
url_for('login',id='1') # 将id作为URL参数,返回/login?id=1
url_for('hello',name='man') # 适配hello函数的name参数,返回/hello/man
url_for('static',filename='style.css') # 静态文件地址,返回/static/style.css
一个Web应用的静态文件包括了JS、CSS、图片等,Flask的风格是将所有的静态文件放在“static”子目录下。并且在代码或模板中,使用 url_for('static')
来获取静态文件目录。
如果你想改变这个静态目录的位置,你可以在创建应用时,指定 static_folder
参数。
在3.5中第四个例子就是通过 url_for()
函数获取“static”目录下的的指定文件
app = Flask(__name__, static_folder='files')
在前面,我们讲到了Flask中的Controller和Model,但是一个完整的MVC,没有View怎么行?前端代码如果都靠后台拼接而成,就太麻烦了。本章节,我们介绍下Flask中的View,即模板。
Flask的模板功能是基于Jinja2模板引擎实现的。我们先来实现一个例子。创建一个新的Flask运行文件,代码如下:
from flask import Flask
from flask import render_template
app = Flask(__name__)
@app.route('/hello')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True)
这段代码同上一章节中的多URL的例子非常相似,区别就是 hello()
函数并不是直接返回字符串,而是调用了 render_template()
方法来渲染模板。方法的第一个参数 hello.html
指向你想渲染的模板名称,第二个参数 name
是你要传到模板去的变量,变量可以传多个。
那么这个模板 hello.html
在哪儿呢,变量参数又该怎么用呢?接下来我们创建模板文件,在当前目录下,创建一个子目录“templates”(注意,一定要用这个名字)。然后在“template”目录下创建文件“hello.html”,内容如下:
<!doctype html>
<title>Hello Sample</title>
{% if name %}
<h1>Hello {{name}}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
这段很像HTML的代码,就是一个HTML模板,根据 name
变量的值,显示不同的内容。变量或表达式由 {{}}
修饰,而控制语句由 {% %}
修饰,其他的代码,就是我们常见的HTML。
运行上面的代码,打开浏览器访问 http://127.0.0.1:5000/hello/man
,浏览器打开页面如下:
果然,模板代码进入了 Hello {{name}}!
分支,并且变量 {{ name }}
被替换为了“man”,JinJa2的模板引擎还有更多强大的功能,包括for循环,过滤器等。模板里也可以直接访问内置对象,如:Request、Session等。
一般我们的网站虽然页面多,但是很多部分是重用的,比如页首、页脚、导航栏之类的。对于每个页面,都要写这些代码,很麻烦。Flask的Jinja2模板支持模板继承功能,省去了这些重复代码。让我们基于上面的例子,在“templates”目录下,创建一个名为“layout.html”的模板:
<!doctype html>
<title>Hello Sample</h1>
<link rel="stylesheet" type="text/css" href="{{url_for('static', filename='style.css')}}">
<div class="page">
{% block body %}
{% endblock %}
</div>
再修改之前的“hello.html”,把原来的代码定义在 {% block body %}
中,并在代码一开始“继承”上面的“layout.html”:
{% extends "layout.html" %}
{% block body %}
{% if name %}
<h1>Hello {{name}}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
{% endblock %}
打开浏览器,再看下: http://127.0.0.1:5000/hello/man
页面的源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello Sample</title>
</head>
<link rel="stylesheet" type="text/css" href="/static/style.css">
<div class="page">
<h1>Hello man!</h1>
</div>
</html>
你会发现,虽然 render_template()
加载了“hello.html”模板,但是“layout.html”的内容也一起被加载上了。而且“hello.html”中的内容被放置在了“layout.html”中的 {block body}
位置上。形象的说,就是“hello.html”继承了“layout.html”。
我们看下下面的代码:
@app.route('/')
def index():
return '<div>Hello %s</div> % <em>Flask</em>'
打开页面会看到“Hello Flask”的字样,而“Flask”是斜体的,因为我们加上了 <em>
标签。但有时我们并不想让这些HTML标签自动转义,特别是专递表单参数时,很容易导致HTML注入的漏洞。我们把上面的代码改一下,引入“Markup”类:
from flask import Flask, Markup
app = Flask(__name__)
@app.route('/')
def index():
return Markup('<div>Hello %s</div>') % '<em>Flask</em>'
再次打开页面,<em>
标签显示在页面上了。Markup还有很多方法,比如 escape()
呈现HTML标签, striptags()
去除HTML标签。这里就不一一列举了。
我们会在Flask紧接系列里对模板功能作更详细的介绍。
一个完整的http请求,包括了客户端的请求Request,服务器端的响应Response,会话Session等。一个基本的Web框架一定会提供内建的对象来访问这些信息,Flask当然也不例外,我们来看看在Flask中怎么使用这些内建对象。
引入flask保重的request对象,就可以直接在请求函数中直接使用该对象了。我们改进一下章节2中的 login()
方法:
from flask import request
@app.route('/login', methods=['POST','GET'])
def login():
if request.method == 'POST':
if request.form['user'] == 'admin':
return 'Admin login successfully!'
else:
return 'No such user!'
title = request.args.get('title', 'Default')
return render_template('login.html', title=title)
在上一章节的“templates”目录下,添加“login.html”文件,内容如下:
{% extends "layout.html"}
{% block body %}
<form name="login" action='/login' method="post">
Hello {{ title }}, please login by:
<input type="text" name="user" />
</form>
{% endblock %}
执行上面的例子,使用PostMan访问 http://127.0.0.1:5000/login
,结果如上图;简单解释下:
request
中的 method
变量可以获取当前的请求方法,即get、post、put、delete等;
form
变量是一个字典,可以获取post请求表单中的内容,在上例中,如果提交的表单不存在 user
项,则会返回一个 KeyError
,你可以不捕获,页面会返回400错误(想避免抛出这个 KeyError
,你可以用 request.form.get('user')
来替代)。
request.args.get()
方法可以获取“get”请求中的参数,该函数的第二个参数是默认值,当URL参数不存在时,则返回默认值。
request
的详细使用可参阅Flask的官方API文档。
会话可以用来保存当前请求的一些状态,以便于在请求之前共享信息。我们将上面的python代码改动下:
from flask import request, session
@app.route('/login', methods=['POST','GET'])
def login():
if request.method == 'POST':
if request.form['user'] == 'admin':
session['user'] = request.form['user']
return 'Admin login successfully'
else:
return 'No such user!'
if 'user' in session:
return 'Hello %s!' % session['user']
else:
title = request.args.get('title', 'Default')
return render_tamplate('login.html', title=title)
app.secret_key = '123456'
运行效果如上图;可以看到“admin”登陆成功之后,再打开“login”页面就不会出现表单了。session对象的操作就像一个字典一样。
特别提醒: 使用session时,一定要设置一个密钥 app.secret_key
,如上例。否则运行会报错,错误内容大致是: RuntimeError: the session is unavilable because no secret
。密钥要尽量复杂,最好使用一个随机数,上面的例子不是一个好密钥。
我们顺便写一个简单的登出的方法,清除字典里的键值:
from flask import Flask, request, session, redirect, url_for
@app.route('/logout')
def logout():
session.pop('user', None)
return redirect(url_for('login'))
在之前的例子中,请求的响应我们都是直接返回字符串内容,或者通过模板来构建响应内容然后返回。其实我们也可以先构建响应对象,设置一些参数(比如响应头)后,再将其返回。修改下上面例子中的“Get”请求部分:
...
else:
title = request.args.get('title', 'Default')
response = make_response(render_template('login.html', title=title),200)
response.headers['key'] = 'value'
return response
...
F12打开浏览器调试,在“Get”请求用户未登录状态下,可以看到响应头中有一个 key
项。make_response
方法就是用来构建 response
对象的,第二个参数代表响应状态码,缺省就是“200”。 response
对象的详细使用可参阅Flask的官方APIA文档。
提到了Session,当然也要介绍Cookie,毕竟没有Cookie,Session就根本没有用。Flask中使用Cookie也很简单:
...
def login():
response = None
if request.method == 'POST':
if request.form['user'] == 'admin':
session['user'] = request.form['user']
response = make_response('Admin login successfully!')
response.set_cookie('login_time', time.strftime('%Y-%m-%d %H:%M:%S'))
...
else:
if 'user' in session:
login_time = request.cookie.get('login_time')
response = make_response('Hello %s, you logged in on %s' % (session['user'], login_rime))
...
...
例子越来越长了,这次我们引入了 time
模块来获取当前系统时间。我们在返回响应时,通过 response.set_cookie()
函数来设置 Cookie
项,之后这个项值会被保存在浏览器中。这个函数的第三个参数 max_age
可以设置该Cookie项的有效期,单位是秒,不设的话,在浏览器关闭后,该Cookie项即失效。
在请求中, request.cookies()
对象就是一个保存了浏览器Cookie的字典,使用其 get()
函数就可以获取相应的键值。
flask.g
是Flask的一个全局对象,这里有点容易让人误解,其实 g
的作用范围,就在一个请求(也就是一个线程)里,它不能在多个请求间共享。你可以在 g
对象里保存任何你想保存的内容。一个最常用的例子,就是在进入请求前,保存数据库连接。这个我们会在介绍数据库集成的时候讲到。
本章节将补充一些Flask的基本功能,包括错误处理,URL重定向,日志功能,还有一个很有趣的消息闪现功能。
使用 abort()
函数可以直接退出请求,返回错误代码:
from flask import Flask, abort
app = Flask(__name__)
@app.route('/error')
def error():
abort(404)
if __name__ == '__main__':
app.run()
上例会显示浏览器的404错误页面。有时候,我们想要在遇到特定错误代码时做些事情,或者重写错误页面,可以用下面的方法:
@app.errorhandler(404)
def page_not_found(error):
return render_template('404.html'), 404
此时,当再次遇到404错误时,即会调用 page_not_found()
函数,其返回“404.html”的模板页。第二个参数代表错误代码。
不过,在实际开发过程中,我们并不会经常使用 abort()
来退出,常用的错误处理方法一般都是异常的抛出或捕获。装饰器 @app.errorhandler()
除了可以注册错误代码外,还可以注册指定的异常类型。让我们定义一个异常:
class InvalidUsage(Exception):
status_code 400
def __int__(self,message,status_code=400):
Exception.__int__(self)
self.message = message
self.status_code = status_code
@app.errorhandler(InvalidUsage)
def invalid_usage(error):
response = make_response(error.message)
response.state_code = error.status_code
return response
我们在上面的代码中定义了一个异常 InvalidUsage
,同时我们通过装饰器 @app.errhandler()
修饰了函数 invalid_usage()
,装饰器中注册了我们刚刚定义的异常类。这也就意味着,一旦遇到 InvalidUsage
异常被抛出,这个 incalid_usage()
函数就会被调用。写个路由试一下:
@app.route('/exception')
def exception():
raise InvalidUsage('No privilege to access the resource', status_code = 403)
重定向 redirect()
函数的使用,在章节5.2 logout
的例子中已有出现。作用就是当客户端浏览某个网址时,将其导向另一个网址。常见的例子,比如用户在未登录时浏览某个需授权的页面,我们将其重定向到登录页要求其先登录。
from flask import Flask
from flask import session, redirect
@app.route('/')
def index():
if 'user' in session:
return 'Hello $s!' % session['user']
esle:
return redirect(url_for('login.html'),302)
redirect()
第二个参数是HTTP状态码,可取的值有301,302,303,305和307,默认即302。
提到错误处理,那一定要说日志。Flask提供 logger
对象,它是一个标准的 Python Logger
类。修改上面例子中的 exception()
函数:
@app.route('/exception')
def exception():
app.logger.debug('Enter exception method')
app.logger.error('403 error happened')
raise InvalidUsage('No privilege to access the resource', status_code=403)
执行后,可以在控制台看到日志信息(如上图)。在debug模式下,日志会默认输出到标准错误stderr中。你可以添加 FileHandler
来使其输出到日志文件中,也可以修改日志的记录格式,下面演示一个简单的日志配置代码:
server_log = TimedRotatingFileHandler('server.log', 'D')
serevr_log.setLevel(logging.DEBUG)
server_log.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
error_log = TimedRotatingFilehandler('error.log', 'D')
error_log.setLevel(logging.DEBUG)
error_log.setFormatter(logging.Formatter('%(asctime)s: %(message)s [in %(pathname)s:%(lineno)d]'))
app.logger.addHandler(server_log)
app.logger.addHandler(error_log)
添加上述代码并执行后,会发现在*.py执行文件同目录下生成了两个日志文件:error.log
、server.log
;打开查看内容分别为:
# server.log
2019-12-21 10:11:00,467 ERROR: 403 error happened
# error.log
2019-12-21 10:11:00,467: 403 error happened [in F:\1. Code\1. Python\flasktest\basetest.py:39]
上例中,我们在本地目录下创建了两个日志文件,分别是:
server.log : 记录所有级别日志;
error.log : 只记录错误日志。
我们分别给两个文件不同的内容格式。另外,我们使用了 TimedRotatingFileHandler
并给了参数 D
,这样日志会每天创建一个新文件,并将旧文件加日期后缀来归档。