@Mr-13
2019-12-21T02:28:15.000000Z
字数 11225
阅读 74
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 importFlaskapp = 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') # 返回/loginurl_for('login',id='1') # 将id作为URL参数,返回/login?id=1url_for('hello',name='man') # 适配hello函数的name参数,返回/hello/manurl_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 Flaskfrom flask import render_templateapp = 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, Markupapp = 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 = Noneif 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, abortapp = 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 400def __int__(self,message,status_code=400):Exception.__int__(self)self.message = messageself.status_code = status_code@app.errorhandler(InvalidUsage)def invalid_usage(error):response = make_response(error.message)response.state_code = error.status_codereturn 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 Flaskfrom 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.log2019-12-21 10:11:00,467 ERROR: 403 error happened# error.log2019-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 ,这样日志会每天创建一个新文件,并将旧文件加日期后缀来归档。