【Flask】微型web框架flask大概介绍

时间:2020-12-21 16:57:40

Flask

  Flask是一个基于python的,微型web框架。之所以被称为微型是因为其核心非常简单,同时具有很强的扩展能力。它几乎不给使用者做任何技术决定。

  安装flask时应该注意其必须的几个支持包比如Jinja2,Werkzeug等。如果使用easy_install或者pip这样的安装工具的话那么就不必担心这么多了。另外flask还有一些可选的辅助模块,使用它们可以让程序更加简洁易懂,比如SQLAlchemy用于数据库的连接和操作,flask-WTForm用于网页上表单的设计。

  最简单的一个flask的web应用如下:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
return "<h1>Hello,World</h1>" if __name__ == '__main__':
app.run(host='0.0.0.0',port=5000,debug=True,threaded=True)

  因为这段很熟悉了,很多要素就不多做解释。只是最近看书看到的,以前也不是很清楚的一点,就是Flask是基于flask的web应用的基础,而flask程序在运行过程中会使用这个参数作为定位模板和其他静态文件的基础。当我们传递__name__时,就保证了不管这个脚本是以__main__形式被调用还是被其他脚本调用,都能让app指代flask的web应用。换句话说,如果我们直接跑这个脚本的话,那么大可以把__name__直接换成"__main__",只不过这样做会让其他脚本调用这里的app时出错。

  在被route修饰的index函数在我这被称为响应函数,响应函数一定要有返回值,这个例子中最后返回的是一个HTML的代码,当然也可以返回XML,JSON消息体等等。另外需要注意的是在这个脚本里面,app.run开始之后的所有代码都不会被执行,因为程序进入了监听HTTP请求的模式。关于run方法中的几个参数,host指定了监听的ip地址,默认是127.0.0.1,即只有本地可以访问而外部是无法访问的;port是监听端口,默认5000,debug是指出是否处于调试模式,如果是那么当程序执行出错时的调用栈和错误信息全部都会返回给客户端浏览器。当上生产环境的时候千万记得要把这个改成False否则会有暴露代码的风险。

■  渲染模板

  现在的网站很少有全静态页面的了,而动态页面靠的就是模板的渲染。flask的默认模板引擎是Jinja2,它有一套自己的模板语言,一个简单的模板长成下面这样:

<html>
<body>
{% if name %}
<h1>Hello,{{name}}!</h1>
{% else %}
<h1>Hello,Stranger!</h1>
{% endif %}
</body>
</html>

  这个模板体现了Jinja2中的选择结构,类似的还有:

循环结构:
{% for name in names %}
<h1>Hello,{{ name }}</h1>
{% endfor %}
宏(类似函数):
{% macro rend_mess(item) %}
<li>{{ item }}</li>
{% endmacro %}
然后就可以在下文中
{% for item in items %}
{{ rend_mess(item) }}
{% endfor %}
引入外部文件:
{% import 'macro.html' as macros %}
<ul>
{% for item in items %}
{{ macros.rend_mess(item) }}
{% endfor %}
</ul>

  这些模板文件,应该放在主脚本的同目录下的templates目录。

  那么这些模板到底要如何和主程序关联起来呢?这就是需要主程序中调用render_template这个函数。比如对于上面那个选择结构的模板可以在某个路由下:

@app.route("/")
@app.route('/<name>')
def show_name(name=None):
return flask.render_template('template.html',name=name)

  从这段代码中我们可以看到几点。

  首先,路由可以不是固定的,由<变量名>的形式可以制作动态路由(后面应该会在讲路由的时候再细说)。

  第二,函数的第一个参数只写了模板的文件名而不是模板的路径,这就是把模板放在固定的templates目录下的意义。

  第三,一个响应函数可以关联多个路由,使这几个路由都定向到这个函数。

  第四,render_template函数的参数可以有很多,除了第一个指定了模板名之外,还有就是模板中变量的名字,比如这里的name,或者写一个items=[1,2,3]就可以把这个items列表传递到{% for item in items %}中去。此例中如果不写name参数也不会报错,走else分支显示Stranger就是了。如果响应函数没有为name设置默认值None,这样在访问'/'的时候会报错找不到变量name而此时访问/Frank是OK的。这说明动态路由中变量的值被作为相同名字的参数的值传递到相应函数中去了。如果把name这个参数拿掉,那么不论访问/还是/Frank都是报错的,前者报错因为找不到name这个变量的值,后者则是因为明明要传递一个值进响应函数做参数的但是响应函数却规定没有参数。

  在模板渲染中还存在一个小问题,就是对HTML的特殊字符如 < > / 空格 这些字符的处理。在flask中有flask.Markup这个类,将特殊字符转义成HTML可以识别的形式。比如下面这个例子:

print '<strong>Hi,%s</strong>' % '<blink>Dav</blink>'
#获得结果是<strong>Hi <blink>Dav</blink>!</strong>
print flask.Markup('<strong>Hi %s</strong>') % '<blink>Dav</blink>'
#得到结果是<strong>Hi &lt;blink&gt;Dav&lt;/blink&gt;!</strong>

  模板还存在继承机制,用法是在子模板第一行写上{% extends "xxx.html" %}。一般而言,在父类模板中会有{% block xxx %}...{% endblock %}这样的标记,这表示这一片区域中的内容被归属于block xxx。在子模板中可以以block为单位对block中的内容进行重写。此外在子模板中还可以调用super()宏,表示在子模板中的特定位置填充父模板这个block中的内容。如下面这个例子:

<!-- parent.html -->
<html>
<head>
{% block head %}
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
{% endblock %}
</head>
<body>
{% block body %}
<p>some original content</p>
{% endblock %}
</body>
</html> <!-- child.html -->
{% extends "parent,html" %}
{% block title %}Title{% endblock %}
{% block head %}{{ super() }}{% endblock %}
{% block body %}{{ super() }}<h1>Hello,World!</h1>{% endblock %}

  子模板就不用再写一些重复的内容,只要对想要作出修改的block做出修改即可。需要注意的是重写是覆盖式的,所以如果要保留父模板原有内容的话就需要调用super。另外在这个例子中,看起来似乎是先对block title做出修改,然后再对block head重载,因为block head包含了block title,之前对title的修改可能会无效。但是不会。原因可能是因为针对block的修改是在block内非子block区域内,block之间看似是有包含关系的,实际上并不。

  

■  重定向和错误处理

  flask模块中还自带了处理重定向和错误处理的函数。

  重定向是用了flask.redirect函数。比如在某个路由的响应函数下写return redirect('/index'),这样就可以把这个针对这个路由的访问重定向到主页去。注意是要return redirect函数返回的内容而不是直接执行redirect函数就好了。比如:

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

  HTTP访问中难免出现各种各样的错误,比如我们熟悉的404之类的。那么在编程中自然需要我们可以手动地抛出错误以及规定处理错误的方式。抛出错误用abort函数,用法如下:

@app.route('/error')
def error():
abort(401)
#之后的代码都不会被执行

  这样就抛出了一个401错误,访问/error这个路由就会报401错了。有了抛出错误的操作自然就需要catch错误的操作。在flask中有一个装饰器@app.errorhandler可以用来指定发生相关错误的时候应该如何处理。比如:

@app.errorhandler(400)
def error_handler_400(error): #自带一个error参数
return render_template("bad_request.html"),400

  可以看到,错误处理的响应函数返回的不单单是一些html内容,而是一个元组,元组的第二项是这个错误的错误码。需要注意的是,客户端在页面上看到的错误信息,是由响应函数返回值决定的。比如上例中bad_request.html中提示了401错误,那么客户端看到的就是401错误的提示,尽管发生的是400错误。另一方面,返回元组的第二项,这个返回码也是人为规定的,比如明明发生了401错误而我故意返回400,那么在客户端浏览器控制台等地方看到的就是400返回码,甚至可以返回一个666这种不存在的码。当然返回一个标准规定的码的好处在于,浏览器可以根据这个码给出一些提示。

■  路由和route函数

  之前就说过了,路由中可以添加变量,以<variable_name>这种形式。再次强调一下,想要在路由中设置一个变量,这个变量必须声明两次。第一次是在route函数的第一个参数中,以<variable_name>的形式,第二次是以和相关路由关联的响应函数的参数的形式,两次声明的变量必须同名。

  进一步深挖route函数中的路由设置,还可以发现以下几点。首先,路由中的变量可以设置类型。比如'/addone/<int:number>',这样访问这个路由时number这个变量的部分就自动被识别成一个int类型的变量传入响应函数中处理。可用的类型除了int,还有float和path。其中path是默认值,即接受路径的一个字符串。

  另外还有一个小细节,路径末尾还可以添加上一个额外的斜杠,比如@app.route('/school/'),这么做的意义在于,当访问地址hostname/school和hostname/school/都可以访问到。如果不写这个斜杠,后者是无法访问到的。

●  规定HTTP方法

  route函数可以添加methods参数来规定相关路由认可的访问HTTP方法,默认情况下是认可GET方法。比如:

@app.route('/SendMessage',methods=['GET','POST'])
def sendMessage():
if request.method == 'GET':
show_send_form()
if request.method == 'POST':
do_send()
#request是flask中一个全局对象,用来抽象每一次访问时的请求对象。

  同时也可以通过对methods的控制来实现一个路由的灵活应用:

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

●  反向生成路由地址

  有时候,知道了某个响应函数,想要知道其对应的url路由,可以使用url_for这个函数。这个函数接受一个字符串函数名作为参数,然后返回本web应用中这个函数名作为响应函数的相关路由字符串。当路由中存在变量的时候,那么url_for函数应该添加上一些额外的,以变量名为名称的参数才能确保正确返回路由字符串。当路由没有变量而url_for有额外参数时默认将返回GET方法带参数的URL。当多个路由关联到同一个响应函数时,url_for只返回写在最下面的那个修饰器的路由。举例如下:

@app.route('/one')
def one():
return 'one' @app.route('/two/<var>')
def two(var):
return 'two' @app.route('/three')
@app.route('/four')
def threeandfour():
return 'four' print url_for('one') #返回内容'/one'
print url_for('one',var='MyVar') #返回内容'/one?var=MyVar'
print url_for('two',var='MyVar') #返回内容'/two/MyVar',这里var这个参数是必须的否则报错
print url_for('threeandfour') #返回内容'/four',而不是'/three'

  *上面这个例子还不是很准确,url_for这个函数正确运行需要上下文的支持,也就是说要保证url_for这个函数所处的地方必须是一个app里面。一般而言把它写在某个响应函数里面的话肯定没有问题,但是如果像这样想在一个app外面的url_for的话,可以在前面用with关键字指定上下文,比如:

with app.test_request_context():
print url_for("index",var="MyVar")

  test_request_context方法用于告诉解释器为在其作用域中的代码模拟一个HTTP请求的上下文环境,这个环境由app提供。这样就可以正确运行了。

  另外,url_for可以加上参数_external=True来把原来生成的相对URI路径转换成带域名的,完整的URL路径。

  在程序中应该尽量使用url_for这种灵活的函数,因为反向解析比硬编码有更好的可读性和维护性,另外url_for还会自动转化一些需要转义的特殊字符。比如上例中如果var=My Var,那么最终会自动把空格转化成URL的形式,返回'/index/My%20Var'。

■  静态文件

  一般所有flask项目都会内置一个默认的路由,是hostname/static/<filename>。这个路由是专门开放给客户端用来访问服务端的静态文件的。它将映射到主脚本同目录下的static目录。需要注意的是这个目录的名字只能是static,访问路由时也必须完整地指出文件的名字,否则都是访问失败的。

  static这个文件夹可以算是flask为开发者提供了一个窗口,让外部访问者可以接触到一些服务端的文件,而哪些文件可以对外部开放就有开发者自己决定。

■  上下文

  上下文在web编程中是一个很重要的概念,它是服务器端获得应用及请求相关信息的对象。从功能目的上说,上下文一般可以让多次操作间共享一些信息,提高效率,而这里的“操作”有好多层面,可以是同一个客户端的多次不同请求,可以是同一个请求的不同处理函数或者单次请求。

●  会话上下文

  在web环境中,所谓的会话是一种客户端和服务端保持状态的解决方案。同一个用户的多个请求共享一个会话对象。会话机制通常是通过cookie来实现。基本原理就是开启会话时会在cookie中添加一条当前会话的SessionID作为这个会话标识。之后检查会话的情况和存活就只要关注这个ID即可。

  通俗来说,我打开一个浏览器后,会话成立,就可以往这个会话中存储一些信息。操作期间我可能打开或关闭很多标签页或其他浏览器窗口,这期间会话一直保持不会断,信息也可以一直共享。但是一旦把所有浏览器都关掉了,会话就断了,再开一个浏览器后原会话中的信息也就不见了。

  在flask中,我们不必再cookie层面上操作,flask自带了一个flask.session对象,这个对象支持__setitem__和__getitem__等方法,所以可以像操作字典一样操作这个对象。借用书上的一个例子:

app.secret_key = 'SECRET_KEY'
@app.route('/write')
def write_session():
flask.session['time'] = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
return flask.session['time'] @app.route('/read')
def read_session():
return flask.session.get('time') #访问write可以将当前时间写入会话对象中,然后紧接着访问read,此时会话没有改变所以可以获取到上一次写入时的时间。

  关于session这个对象,还有几点要提。它和之前说到过的url_for一样必须在请求环境中调用,否则会报错。呆想想也是这样,如果它都不在请求环境中那程序哪会知道会话含有一些什么信息呢。然后session还有几个自带的属性可以用来查看其状态。比如session.new和session.modified,可以用来判断当前的会话是不是刚刚新建的或者是刚刚被修改过。

  在使用session前,一定要为app指定一个秘钥,这个秘钥用来加密sessionID。

●  全局对象

  在flask中,可能会有一个请求触发多个响应函数的情况,此时如果想要在不同的响应函数之间共享一些信息就需要全局对象,flask.g(没错,这个对象名就一个字母g)。g中存储信息的方式不是__setitem__的中括号,而是__setattr__的点。

  相比于session,g并不能跨浏览器甚至不能跨标签页地共享数据,但是在一个标签页内的一个请求过程中,g中存储的数据可以共享。也就是说,g的生命周期和一个request是差不多长的。

  一般函数式编程中,不同函数间的信息交流可以通过全局变量来实现。但是在flask这种装饰器模式和线程环境下用全局变量的global不太合理或者说不太保险,所以用flask.g是一个更好的选择。比如:

@app.route('/connect')
def connect():
cursor = connect_to_db(addr,user,passwd)
flask.g.cursor = cursor
return 'connected' @app.route('/getdata')
def get_data():
if hasattr(flask.g,cursor):
return flask.g.cursor.get_data_from_db()

●  请求上下文

  如果说flask.g是一个在服务端提供的,函数间通信的容器的话,request(请求上下文)就是承载了客户端请求的信息的容器。而且因为要触发响应函数必然需要一个请求,所以request这个对象天生就是带有数据的(数据就是那些请求的信息了)。flask中包含了很多信息,如下:

  request.method  本次请求的方法

  host  服务端的主机名

  args  GET方法的请求的数据,是一个字典

  form  POST或者PUT方法请求中的数据,是一个字典

  json  如果客户端是通过JSON格式提交的数据,那么就可以通过本方法进行解析。

  cookies  从客户端传送过来的cookie,以字典的形式读写

  headers  请求头信息,以字典的形式读写

  files  访问通过POST或者PUT方法向服务端上传的文件。(详细做法可参见文档http://docs.jinkan.org/docs/flask/patterns/fileuploads.html)

  URL族属性,比如对于URL:"http://localhost/app/page.html?x=y"而言有:

    base_url  代表http://localhost/app/page.html

    path  代表/page.html

    script_root  代表/app

    url  代表http://localhost/app/page.html?x=y

    url_root  代表http://localhost/app/page.html

 ●  回调接入点

  其实在使用flask框架的时候,很多操作框架都代替开发人员做了,在这些操作的一些节点上如果我们想附加一些操作的话就要用到回调接入点。使用方法是把接入点函数作为一个装饰器来装饰某些函数。比如:

@app.before_request
def before_request():
'''Something done before every request
'''

  接入点主要有下面这几个:

  before_request  每个请求在被处理之前首先被接入这个接入点,不必一定要返回一个什么值,且可以绑定多个处理函数,这种场合下解释器将依次一一执行这些函数。

  after_request  在每个请求的URL响应函数被调用后调用本接入点,其绑定的处理函数可以有一个固有参数response,代表request得到的response(此时还没发送回给客户端)。可以对这个response的内容进行修改和检查。

  teardown_request  和after_request基本类似,不过after_request不会在出错的响应函数之后被调用。换言之,某个response产出前响应函数出错了的话,after_request就没用了,而teardown_request依然会被执行,所以teardown_request也可以被用于异常处理。

  before_fisrt_request  这个是在app开始run之后接收到第一个请求之前做的事。之前在还没有会用flask-script之前,用这个装饰器来进行db.create_all()的工作

  before_app_request  这个用起来似乎和before_request一样,但是实际上是有微妙不同的。在应用中,我们点击一个超链接发起的请求属于用户手动发起请求,而程序内设定的重定向,虽然也会向某个特定路径发起请求,但是属于程序自动发起的请求。before_request只在手动发起请求时被调用,before_app_request则不论手动自动,只要有请求发起就被调用。

  下面是一个利用了回调接入点的,实现了缓存静态页面的常用逻辑:

from werkzeug.contrib.cache import SimpleCache
from flask import request,render_template CACHE_TIMEOUT = 300 cache = SimpleCache()
cache.default_timeout = CACHE_TIMEOUT @app.before_request #每个请求被处理之前先检查想要的页面是否在缓存里,如果在就可以直接返回它
def return_cached():
if not request.values: #当请求没有附加数据时,认为是个单纯的页面请求,开始检查缓存
response = cache.get(request.path)
if response:
print "Got the page from cache"
return response
print "Loading page..." @app.after_request #做出处理之后,再检查回复的页面是否在缓存里,如果不在就保存进去供下次快速反应使用。
def cache_response(response):
if not request.values:
cache.set(request.path,response,CACHE_TIMEOUT)
return response @app.route('/get_index')
def get_index():
return render_template("index.html")

  写了一下flask中比较基础的一些内容,还有一些附加的模块和其他更细节的东西会另开笔记写。