Python框架Flask基础学习

原文地址:http://www.bjhee.com/flask-1.html

Contents

简介

Flask是由python实现的一个web微框架,让我们可以使用Python语言快速实现一个网站或Web服务。而且有对应的python3及python2版本。我这里用的是python3
安装Flask

pip install flask

目录结构

通过别人的目录大致了解一下flask框架的目录结构。

flask-demo/
  ├ run.py           # 应用启动程序
  ├ config.py        # 环境配置
  ├ requirements.txt # 列出应用程序依赖的所有Python包
  ├ tests/           # 测试代码包
  │   ├ __init__.py 
  │   └ test_*.py    # 测试用例
  └ myapp/
      ├ admin/       # 蓝图目录
      ├ static/
      │   ├ css/     # css文件目录
      │   ├ img/     # 图片文件目录
      │   └ js/      # js文件目录
      ├ templates/   # 模板文件目录
      ├ __init__.py    
      ├ forms.py     # 存放所有表单,如果多,将其变为一个包
      ├ models.py    # 存放所有数据模型,如果多,将其变为一个包
      └ views.py     # 存放所有视图函数,如果多,将其变为一个包

开始 Hello world

from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
    return 'Hello World'
if __name__ == '__main__':
    app.debug = True # 设置调试模式,生产模式的时候要关掉debug
    app.run()

这是flask框架制作的一个最小的应用。使用python运行后访问localhost:5000就能看到网页显示Hello world。
这里首先引入了Flask类,然后给这个类创建了一个实例,__name__代表这个模块的名字。因为这个模块是直接被运行的所以此时__name__的值是__main__。然后用route()这个修饰器定义了一个路由,告诉flask如何访问该函数。最后用run()函数使这个应用在服务器上运行起来。

路由

flask通过route()装饰器将一个函数绑定到对应的URL上了。
例如:

@app.route('/')
def index():
    return 'Hello World!'
@app.route('/home')
def index():
    return 'This is home page!'

我访问localhost:5000/home,就能在页面看到This is home page!。

flask支持在路由上制定参数及参数类型,通过<variable_name>可以标记变量,这个部分将会作为命名参数传递到你的函数,也可以通过<converter:variable_name>指定一个可选的装饰器:

@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

这里访问localhost:5000/user/test,会看到User test。
访问localhost:5000/post/1,会看到Post 1,且必须为int型,因为这里限制了参数类型。

类型转换器 作用
缺省 字符型,但不能有斜杠
int: 整型
float: 浮点型
path: 字符型,可有斜杠

HTTP方法

如果需要处理具体的HTTP方法,在Flask中也很容易,使用route装饰器的methods参数设置即可。

from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        do_the_login()
    else:
        show_the_login_form()

当你请求地址http://localhost:5000/login,”GET”和”POST”请求会返回不同的内容,其他请求方法则会返回405错误。

唯一 URL / 重定向行为

Flask的URL规则是基于Werkzeug的路由模块。模块背后的思想是基于 Apache 以及更早的 HTTP 服务器主张的先例,保证优雅且唯一的 URL。

@app.route('/projects/')
def projects():
    return 'The project page'

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

访问第一个路由不带/时,Flask会自动重定向到正确地址。
访问第二个路由时末尾带上/后Flask会直接报404 NOT FOUND错误。

构造URL

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程序中常常需要处理静态文件,在Flask中需要使用url_for函数并指定static端点名和文件名。在下面的例子中,实际的文件应放在static/文件夹下。

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

Request 对象

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)

request.args.get()方法则可以获取Get请求URL中的参数,该函数的第二个参数是默认值,当URL参数不存在时,则返回默认值
templates目录下,添加layout.html文件

<!doctype html>
<title>Hello Sample</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
<div class="page">
    {% block body %}
    {% endblock %}
</div>

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 %}

图片.png
图片.png

全局对象g

flask.g是Flask一个全局对象,g的作用范围,就在一个请求(也就是一个线程)里,它不能在多个请求间共享。你可以在g对象里保存任何你想保存的内容。

构建响应

我们可以先构建响应对象,设置一些参数(比如响应头)后,再将其返回。

from flask import request, session, make_response

@app.route('/login', methods=['POST', 'GET'])
def login():
    if request.method == 'POST':
        ...
    if 'user' in session:
        ...
    else:
        title = request.args.get('title', 'Default')
        response = make_response(render_template('login.html', title=title), 200)
        response.headers['key'] = 'value'
        return response

make_response方法就是用来构建response对象的,第二个参数代表响应状态码,缺省就是200。

会话对象session

会话可以用来保存当前请求的一些状态,以便于在请求之前共享信息。

登入

from flask import Flask,request,render_template,session

app=Flask(__name__)

@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_template('login.html', title=title)
app.secret_key='123456'
if __name__=='__main__':
    app.run()

使用session时一定要设置一个密钥app.secret_key,密钥要尽量复杂,最好使用一个随机数。

登出

from flask import request, session, redirect, url_for

@app.route('/logout')
def logout():
    session.pop('user', None)
    return redirect(url_for('login'))

模板简介

模板生成

Flask默认使用Jinja2作为模板,默认情况下,模板文件需要放在templates文件夹下。
使用 Jinja 模板,只需要使用render_template函数并传入模板文件名和参数名即可。

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)

hello()函数并不是直接返回字符串,而是调用了render_template()方法来渲染模板。方法的第一个参数hello.html指向你想渲染的模板名称,第二个参数name是你要传到模板去的变量,变量可以传多个。
相应的模板文件hello.html如下。

<!Doctype html>
<title>Hello from Flask</title>
{% if name %}
  <h1>Hello {{ name }}!</h1>
{% else %}
  <h1>Hello, World!</h1>
{% endif %}

变量或表达式由{{ }}修饰,而控制语句由{% %}修饰,其他的代码,就是我们常见的HTML。

模板标签

代码块需要包含在{% %}块中

{% extends 'layout.html' %}
{% block title %}主页{% endblock %}
{% block body %}

    <div class="jumbotron">
        <h1>主页</h1>
    </div>

{% endblock %}

双大括号中的内容不会被转义,所有内容都会原样输出,它常常和其他辅助函数一起使用。

<a class="navbar-brand" href={{ url_for('index') }}>Flask小例子</a>

继承

模板可以继承其他模板,我们可以将布局设置为父模板,让其他模板继承,这样可以非常方便的控制整个程序的外观。

例如这里有一个layout.html模板,它是整个程序的布局文件。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>{% block title %}{% endblock %}</title>
    <link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap.css') }}"/>
    <link rel="stylesheet" href="{{ url_for('static',filename='css/bootstrap-theme.css') }}"/>

</head>
<body>

<div class="container body-content">
    {% block body %}{% endblock %}
</div>

<div class="container footer">
    <hr>
    <p>这是页脚</p>
</div>

<script src="{{ url_for('static',filename='js/jquery.js') }}"></script>
<script src="{{ url_for('static',filename='js/bootstrap.js') }}"></script>

</body>
</html>

其他模板可以这么写。对比一下面向对象编程的继承概念:

{% extends 'layout.html' %}
{% block title %}主页{% endblock %}
{% block body %}

    <div class="jumbotron">
        <h1>主页</h1>
        <p>本项目演示了Flask的简单使用方法,点击导航栏上的菜单条查看具体功能。</p>
    </div>

{% endblock %}

控制流

条件判断可以这么写,类似于JSP标签中的Java 代码。

{%_%}中也可以写Python代码

  <div class=metanav>
  {% if not session.logged_in %}
    <a href="{{ url_for('login') }}">log in</a>
  {% else %}
    <a href="{{ url_for('logout') }}">log out</a>
  {% endif %}
  </div>

循环,和在Python中遍历差不多。

        <tbody>
        {% for key,value in data.items() %}
            <tr>
                <td>{{ key }}</td>
                <td>{{ value }}</td>
            </tr>
        {% endfor %}
        <tr>
            <td>文件</td>
            <td></td>
        </tr>
        </tbody>

不是所有的Python代码都可以写在模板里,如果希望从模板中引用其他文件的函数,需要显式将函数注册到模板中。

错误处理

使用abort()函数可以直接退出请求,返回错误代码:

from flask import abort

@app.route('/error')
def error():
    abort(404)

上例会显示浏览器的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 __init__ (self,message,status_code=400):
        Exception.__init__(self)
        self.message=message
        self.status_code=status_code

@app.errorhandler(InvalidUsage)
def invalid_usage(error):
    response=make_response(error.message)
    response.status_code=error.status_code
    return response

这里定义了一个异常InvalidUsage,同时我们通过装饰器@app.errorhandler()修饰了函数invalid_usage(),装饰器中注册了我们刚定义的异常类。一但遇到InvalidUsage异常被抛出,这个invalid_usage()函数就会被调用

@app.route('/exception')
def exception():
    raise InvalidUsage('No privilege to access the resource', status_code=403)

日志

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')
server_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.ERROR)
error_log.setFormatter(logging.Formatter(
    '%(asctime)s: %(message)s [in %(pathname)s:%(lineno)d]'
))

app.logger.addHandler(server_log)
app.logger.addHandler(error_log)

在本地目录下创建了两个日志文件,分别是server.log记录所有级别日志;error.log只记录错误日志。
分别给两个文件不同的内容格式。
使用TimedRotatingFileHandler并给了参数D,这样日志每天会创建一个新的文件,并将旧文件加日期后缀来归档。

消息闪现

Flask Message一个操作完成后,在页面上闪出一个消息,告诉用户操作的结果。用户看完后,这个消息就不复存在了。
app.py

from flask import render_template, request, session, url_for, redirect, flash,Flask
app = Flask(__name__)
@app.route('/')
def index():
    if 'user' in session:
        return render_template('hello.html', name=session['user'])
    else:
        return redirect(url_for('login'), 302)

@app.route('/login', methods=['POST', 'GET'])
def login():
    if request.method == 'POST':
        session['user'] = request.form['user']
        flash('Login successfully!!!!')
        return redirect(url_for('index'))
    else:
        return '''
        <form name="login" action="/login" method="post">
            Username: <input type="text" name="user" />
        </form>
        '''
app.secret_key='123456'
if __name__=='__main__':
    app.run()

app.secret_key不赋值会报错。
hello.html

<!doctype html>
<title>Hello Sample</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
{% with messages = get_flashed_messages() %}
  {% if messages %}
    <ul class="flash">
    {% for message in messages %}
      <li>{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}
<div class="page">
    {% block body %}
    {% endblock %}
</div>

get_flashed_messages()函数会获取我们在login()中通过flash()闪出的消息。
图片.png
再刷新文字就会消失。
flash()方法的第二个参数是消息类型,可选择的有message, info, warning, error可以在获取消息时,同时获取消息类型,还可以过滤特定的消息类型。

请求上下文环境

请求上下文的生命周期

上下文装饰器@app.before_request@app.teardown_request@app.after_request,用其修饰的函数也可以称为上下文Hook函数。
before_request修饰的函数会在请求处理之前被调用,after_requestteardown_request会在请求处理完成后被调用。
区别是after_request只会在请求正常退出时才会被调用,它必须传入一个参数来接受响应对象,并返回一个响应对象,一般用来统一修改响应的内容。而teardown_request在任何情况下都会被调用,它必须传入一个参数来接受异常对象,一般用来统一释放请求所占有的资源。同一种类型的Hook函数可以存在多个,程序会按代码中的顺序执行。

from flask import Flask, g, request

app = Flask(__name__)

@app.before_request
def before_request():
    print 'before request started'
    print request.url

@app.before_request
def before_request2():
    print 'before request started 2'
    print request.url
    g.name="SampleApp"

@app.after_request
def after_request(response):
    print 'after request finished'
    print request.url
    response.headers['key'] = 'value'
    return response

@app.teardown_request
def teardown_request(exception):
    print 'teardown request'
    print request.url

@app.route('/')
def index():
    return 'Hello, %s!' % g.name

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

访问http://localhost:5000/后,会在控制台输出:
图片.png
如果一个before_request函数中有返回response,则后面的before_request以及该请求的处理函数将不再被执行。直接进入after_request

@app.before_request
def before_request():
    print('before request started')
    print(request.url)
    return 'Hello'
......

图片.png
request对象只有在请求上下文的生命周期内才可以访问

构建请求上下文环境

一个请求一般是由客户端发起的,Flask提供了在没有客户端的情况下实现自动测试,可通过test_request_context()来模拟客户端请求。

from werkzeug.test import EnvironBuilder

ctx = app.request_context(EnvironBuilder('/','http://localhost/').get_environ())
ctx.push()
try:
    print request.url
finally:
    ctx.pop()

request_context()会创建一个请求上下文RequestContext类型的对象,其需接收werkzeug中的environ对象为参数。werkzeug是Flask所依赖的WSGI函数库。
上面的代码可以用with语句来简化:

from werkzeug.test import EnvironBuilder

with app.request_context(EnvironBuilder('/','http://localhost/').get_environ()):
    print request.url

请求上下文的实现方式

对于Flask Web应用来说,每个请求就是一个独立的线程。请求之间的信息要完全隔离,避免冲突,这就需要使用本地线程环境(ThreadLocal)。ctx.push()方法,会将当前请求上下文,压入flask._request_ctx_stack的栈中,这个_request_ctx_stack是内部对象,我们在应用开发时最好不要使用它,一般在Flask扩展开发中才会使用。同时这个_request_ctx_stack栈是个ThreadLocal对象。也就是flask._request_ctx_stack看似全局对象,其实每个线程的都不一样。请求上下文压入栈后,再次访问其都会从这个栈的顶端通过_request_ctx_stack.top来获取,所以取到的永远是只属于本线程中的对象,这样不同请求之间的上下文就做到了完全隔离。请求结束后,线程退出,ThreadLocal线程本地变量也随即销毁,ctx.pop()用来将请求上下文从栈里弹出,避免内存无法回收。

应用上下文环境

current_app代理

from flask import Flask, current_app

app = Flask('SampleApp')

@app.route('/')
def index():
    return 'Hello, %s!' % current_app.name

我们可以通过current_app.name来获取当前应用的名称,也就是SampleAppcurrent_app是一个本地代理,它的类型是werkzeug.local. LocalProxy,它所代理的即是我们的app对象,也就是说current_app == LocalProxy(app)使用current_app是因为它也是一个ThreadLocal变量,对它的改动不会影响到其他线程。
既然是ThreadLocal对象,那它就只在请求线程内存在,它的生命周期就是在应用上下文里。离开了应用上下文,current_app一样无法使用。

app = Flask('SampleApp')
print(current_app.name)

RuntimeError: working outside of application context

构建应用上下文环境

with app.app_context():
    print(current_app.name)

app_context()方法会创建一个AppContext类型对象,即应用上下文对象,此后我们就可以在应用上下文中访问current_app对象了。

应用上下文的实现方式

在请求线程创建时,Flask会创建应用上下文对象,并将其压入flask._app_ctx_stack的栈中,然后在线程退出前将其从栈里弹出。这个_app_ctx_stack同请求上下文中的_request_ctx_stack一样,都是ThreadLocal变量。也就是说应用上下文的生命周期,也只在一个请求线程内,我们无法通过应用上下文在请求之间传递信息。

应用上下文Hook函数

应用上下文也提供了装饰器来修饰Hook函数,不过只有一个@app.teardown_appcontext。它会在应用上下文生命周期结束前,也就是从_app_ctx_stack出栈时被调用。

@app.teardown_appcontext
def teardown_db(exception):
    print(teardown application)

信号

信号(Signal)就是两个独立的模块用来传递消息的方式,它有一个消息的发送者Sender,还有一个消息的订阅者Subscriber。信号的存在使得模块之间可以摆脱互相调用的模式,也就是解耦合。发送者无需知道谁会接收消息,接收者也可自由选择订阅何种消息。

订阅一个信号

from flask import template_rendered, request

def print_template_rendered(sender, template, context, **extra):
    print 'Using template: %s with context: %s' % (template.name, context)
    print request.url

template_rendered.connect(print_template_rendered, app)

访问http://localhost:5000/hello会在控制台看到

Using template: hello.html with context: {'session': , 'request': , 'name': None, 'g': }
http://localhost:5000/hello

而访问http://localhost:5000/时,却没有这些信息
flask.template_rendered是Flask的核心信号。当任意一个模板被渲染成功后,这个信号就会被发出。
信号的connect()方法用来连接订阅者,它的第一个参数就是订阅者回调函数,当信号发出时,这个回调函数就会被调用;第二个参数是指定消息的发送者,也就是指明只有app作为发送者发出的template_rendered消息才会被此订阅者接收。
如果不指定发送者,任何发送者发出的”template_rendered”都会被接收。
connect()方法可以多次调用,来连接多个订阅者。

这个回调函数,它有四个参数,前三个参数是必须的。
* sender: 获取消息的发送者
* template: 被渲染的模板对象
* context: 当前请求的上下文环境
* **extra: 匹配任意额外的参数。如果上面三个存在,这个参数不加也没问题。但是如果你的参数少于三个,就一定要有这个参数。一般习惯上加上此参数。

我们在回调函数中,可以获取请求上下文,也就是它在一个请求的生命周期和线程内。所以,我们可以在函数中访问request对象。
Flask同时提供了信号装饰器来简化代码,上面的信号订阅也可以写成:

from flask import template_rendered, request

@template_rendered.connect_via(app)
def with_template_rendered(sender, template, context, **extra):
    print 'Using template: %s with context: %s' % (template.name, context)
    print request.url

Flask核心信号

| 信号 | 作用 | 回调函数参数|
| ------ | ------ |-----|
| request_started |请求开始时发送| 1、sender: 消息的发送者
| request_finished| 请求结束后发送 | 1、sender: 消息的发送者 2、response: 待返回的响应对象
|got_request_exception|请求发生异常时发送|1、sender: 消息的发送者 2、exception: 被抛出的异常对象
|request_tearing_down| 请求被销毁时发送,不管有无异常都会被发送|1、sender: 消息的发送者 2、exc: 有异常时,抛出的异常对象
|appcontext_tearing_down|应用上下文被销毁时发送|1、sender: 消息的发送者
|appcontext_pushed| 应用上下文被压入”_app_ctx_stack”栈后发送|1、sender: 消息的发送者
|appcontext_popped|应用上下文从”_app_ctx_stack”栈中弹出后发送|1、sender: 消息的发送者
|message_flashed|消息闪现时发送|1、sender: 消息的发送者 2、message: 被闪现的消息内容 3、category: 被闪现的消息类别
注,所有回调函数都建议加上**extra作为最后的参数

自定义信号

from blinker import Namespace

signals = Namespace()
index_called = signals.signal('index-called')

我们在全局定义了一个index_called信号对象,表示根路径被访问了。然后我们在根路径的请求处理中发出这个信号:

@app.route('/')
def index():
    index_called.send(current_app._get_current_object(), method=request.method)
    return 'Hello Flask!'

发送信号消息的方法是send(),它必须包含一个参数指向信号的发送者。current_app._get_current_object()用来获取应用上下文中的app应用对象。这样每次客户端访问根路径时,都会发送index_called信号。

视图装饰器

当用户访问admin页面时,必须先登录。可以在admin()方法里判断会话session。这样的确可以达成目的。不过当我们有n多页面都要进行用户验证的话,判断用户登录的代码就会到处都是。即便我们封装在一个函数里,至少调此函数的代码也会重复出现。Flask没有提供特别的功能来实现这个,因为Python本身有,那就是装饰器。

from functools import wraps
from flask import session, abort

def login_required(func):
    @wraps(func)
    def decorated_function(*args, **kwargs):
        if not 'user' in session:
            abort(401)
        return func(*args, **kwargs)
    return decorated_function

app.secret_key = '12345678'

在调用函数前,先检查session里有没有用户。此后,我们只需将此装饰器加在每个需要验证登录的请求方法上即可:

@app.route('/admin')
@login_required
def admin():
    return '<h1>Admin Dashboard</h1>'

这个装饰器就被称为视图装饰器(View Decorator)。
我们也可以把页面的路径作为键,页面内容作为值,放在缓存里。每次进入请求函数前,先判断缓存里有没有该页面,有就直接将缓存里的值返回,没有则执行请求函数,将结果存在缓存后再返回。

URL集中映射

Flask也支持像Django一样,把URL路由规则统一管理,而不是写在视图函数上。
views.py

def foo():
    return '<h1>Hello Foo!</h1>'

然后在Flask主程序上调用app.add_url_rule方法:

app.add_url_rule('/foo', view_func=views.foo)

这样,路由/foo就绑定在views.foo()函数上了,效果等同于在views.foo()函数上加上@app.route(‘/foo’)装饰器。通过app.add_url_rule方法,我们就可以将路由同视图分开,将路由统一管理,实现完全的MVC。

定义的装饰器本质上是一个闭包函数,所以我们当然可以把它当函数使用:

app.add_url_rule('/foo', view_func=login_required(views.foo))

可插拔视图Pluggable View

视图类

URL集中映射,就是视图可插拔的基础,因为它可以支持在程序中动态的绑定路由和视图。Flask提供了视图类,使其可以变得更灵活

from flask.views import View

class HelloView(View):
    def dispatch_request(self, name=None):
        return render_template('hello-view.html', name=name)

view = HelloView.as_view('helloview')
app.add_url_rule('/helloview', view_func=view)
app.add_url_rule('/helloview/<name>', view_func=view)

创建了一个flask.views.View的子类,并覆盖了其dispatch_request()数,渲染视图的主要代码必须写在这个函数里。然后我们通过as_view()方法把类转换为实际的视图函数,as_view()必须传入一个唯一的视图名。此后,这个视图就可以由app.add_url_rule方法绑定到路由上了。
例如

class RenderTemplateView(View):
    def __init__(self, template):
        self.template = template

    def dispatch_request(self):
        return render_template(self.template)

app.add_url_rule('/hello', view_func=RenderTemplateView.as_view('hello', template='hello-view.html'))
app.add_url_rule('/login', view_func=RenderTemplateView.as_view('login', template='login-view.html'))

视图装饰器支持

在使用视图类的情况下,使用视图装饰器

class HelloView(View):
    decorators = [login_required]

    def dispatch_request(self, name=None):
        return render_template('hello-view.html', name=name)

只需将装饰器函数加入到视图类变量decorators中即可,它是一个列表,所以能够支持多个装饰器,并按列表中的顺序执行。

请求方法的支持

class MyMethodView(View):
    methods = ['GET', 'POST']

    def dispatch_request(self):
        if request.method == 'GET':
            return '<h1>Hello World!</h1>This is GET method.'
        elif request.method == 'POST':
            return '<h1>Hello World!</h1>This is POST method.'

app.add_url_rule('/mmview', view_func=MyMethodView.as_view('mmview'))

只需将需要支持的HTTP请求方法加入到视图类变量methods中即可。没加的话,默认只支持GET请求。

基于方法的视图

from flask.views import MethodView

class UserAPI(MethodView):
    def get(self, user_id):
        if user_id is None:
            return 'Get User called, return all users'
        else:
            return 'Get User called with id %s' % user_id

    def post(self):
        return 'Post User called'

    def put(self, user_id):
        return 'Put User called with id %s' % user_id

    def delete(self, user_id):
        return 'Delete User called with id %s' % user_id

分别定义get, post, put, delete方法来对应四种类型的HTTP请求,注意函数名必须这么写。
绑定到路由

user_view = UserAPI.as_view('users')
# 将GET /users/请求绑定到UserAPI.get()方法上,并将get()方法参数user_id默认为None
app.add_url_rule('/users/', view_func=user_view, 
                            defaults={'user_id': None}, 
                            methods=['GET',])
# 将POST /users/请求绑定到UserAPI.post()方法上
app.add_url_rule('/users/', view_func=user_view, 
                            methods=['POST',])
# 将/users/<user_id>URL路径的GET,PUT,DELETE请求,
# 绑定到UserAPI的get(), put(), delete()方法上,并将参数user_id传入。
app.add_url_rule('/users/<user_id>', view_func=user_view, 
                                     methods=['GET', 'PUT', 'DELETE'])

app.add_url_rule()可以传入参数default,来设置默认值;参数methods,来指定支持的请求方法。

如果API多,可以将其封装成函数:

def register_api(view, endpoint, url, primary_id='id', id_type='int'):
    view_func = view.as_view(endpoint)
    app.add_url_rule(url, view_func=view_func,
                          defaults={primary_id: None},
                          methods=['GET',])
    app.add_url_rule(url, view_func=view_func,
                          methods=['POST',])
    app.add_url_rule('%s<%s:%s>' % (url, id_type, primary_id),
                          view_func=view_func,
                          methods=['GET', 'PUT', 'DELETE'])

register_api(UserAPI, 'users', '/users/', primary_id='user_id')

一个register_api()就可以绑定一个API了

文件和流

当我们要往客户端发送大量的数据,比如一个大文件时,将它保存在内存中再一次性发到客户端开销很大。比较好的方式是使用流。

响应流的生成

Flask响应流的实现原理就是通过Python的生成器,也就是yield的表达式,将yield的内容直接发送到客户端。

from flask import Flask, Response

app = Flask(__name__)

@app.route('/large.csv')
def generate_large_csv():
    def generate():
        for row in range(50000):
            line = []
            for col in range(500):
                line.append(str(col))

            if row % 1000 == 0:
                print 'row: %d' % row
            yield ','.join(line) + '\n'

    return Response(generate(), mimetype='text/csv')

这段代码会生成一个5万行100M的csv文件,每一行会通过yield表达式分别发送给客户端。运行时你会发现文件行的生成与浏览器文件的下载是同时进行的,而不是文件全部生成完毕后再开始下载。
这里我们用到了响应类flask.Response,它是werkzeug.wrappers.Response类的一个包装,它的初始化方法第一个参数就是我们定义的生成器函数,第二个参数指定了响应类型。

我们将上述方法应用到模板中,如果模板的内容很大,我们要自己写个流式渲染模板的方法。`

# 流式渲染模板
def stream_template(template_name, **context):
    # 将app中的请求上下文内容更新至传入的上下文对象context,
    # 这样确保请求上下文会传入即将被渲染的模板中
    app.update_template_context(context)
    # 获取Jinja2的模板对象
    template = app.jinja_env.get_template(template_name)
    # 获取流式渲染模板的生成器
    generator = template.stream(context)
    # 启用缓存,这样不会每一条都发送,而是缓存满了再发送
    generator.enable_buffering(5)

    return generator

现在我们就可以在视图方法中,采用stream_template(),而不是以前介绍的render_template()来渲染模板了:

@app.route('/stream.html')
def render_large_template():
    file = open('server.log')
    return Response(stream_template('stream-view.html',
                                    logs=file.readlines()))

上例的代码会将本地的server.log日志文件内容传入模板,并以流的方式渲染在页面上。

另外注意,在生成器中是无法访问请求上下文的。不过Flask从版本0.9开始提供了”stream_with_context()”方法,它允许生成器在运行期间获取请求上下文:

from flask import request, stream_with_context

@app.route('/method')
def streamed_response():
    def generate():
        yield 'Request method is: '
        yield request.method
        yield '.'
    return Response(stream_with_context(generate()))

因为我们初始化Response对象时调用了stream_with_context()方法,所以才能在yield表达式中访问request对象。

文件上传

首先建立一个让用户上传文件的页面,我们将其放在模板upload.html

<!DOCTYPE html>
<title>Upload File</title>
<h1>Upload new File</h1>
<form action="" method="post" enctype="multipart/form-data">
  <p><input type="file" name="file">
     <input type="submit" value="Upload">
</form>

定义一个文件合法性检查函数

# 设置允许上传的文件类型
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg'])

# 检查文件类型是否合法
def allowed_file(filename):
    # 判断文件的扩展名是否在配置项ALLOWED_EXTENSIONS中
    return '.' in filename and \
           filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS

这样避免用户上传脚本文件如js或php文件,来干坏事!

文件提交后,在POST请求的视图函数中,通过request.files获取文件对象

这个request.files是一个字典,字典的键值就是之前模板中文件选择框的”name”属性的值,上例中是”file”;键值所对应的内容就是上传过来的文件对象。

检查文件对象的合法性后,通过文件对象的save()方法将文件保存在本地

import os
from flask import flask, render_template
from werkzeug import secure_filename

app = Flask(__name__)
# 设置请求内容的大小限制,即限制了上传文件的大小
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024

# 设置上传文件存放的目录
UPLOAD_FOLDER = './uploads'

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        # 获取上传过来的文件对象
        file = request.files['file']
        # 检查文件对象是否存在,且文件名合法
        if file and allowed_file(file.filename):
            # 去除文件名中不合法的内容
            filename = secure_filename(file.filename)
            # 将文件保存在本地UPLOAD_FOLDER目录下
            file.save(os.path.join(UPLOAD_FOLDER, filename))
            return 'Upload Successfully'
        else:    # 文件不合法
            return 'Upload Failed'
    else:    # GET方法
        return render_template('upload.html')

Flask的MAX_CONTENT_LENGTH配置项可以限制请求内容的大小默认是没有限制,上例中我们设为5M。
必须调用werkzeug.secure_filename()来使文件名安全比如用户上传的文件名为../../../../home/username/.bashrcsecure_filename()方法可以将其转为home_username_.bashrc。还是用来避免用户干坏事!
Flask处理文件上传的方式如果上传的文件很小,那么会把它直接存在内存中。否则就会把它保存到一个临时目录下,通过tempfile.gettempdir()方法可以获取这个临时目录的位置。

另外Flask从0.5版本开始提供了一个简便的方法来让用户获取已上传的文件:

from flask import send_from_directory

@app.route('/uploads/<filename>')
def uploaded_file(filename):
    return send_from_directory(UPLOAD_FOLDER, filename)

这个帮助方法send_from_directory()可以安全地将文件发送给客户端,它还可以接受一个参数mimetype来指定文件类型,和参数as_attachment=True来添加响应头Content-Disposition: attachment

Flask Blueprint 蓝图

我们的应用经常会区分用户站点和管理员后台,两者虽然都在同一个应用中,但是风格迥异。把它们分成两个应用的话,总有些代码我们想重用;放在一起,耦合度太高,代码不便于管理。所以Flask提供了蓝图(Blueprint)功能。蓝图使用起来就像应用当中的子应用一样,可以有自己的模板,静态目录,有自己的视图函数和URL规则,蓝图之间互相不影响。但是它们又属于应用中,可以共享应用的配置。对于大型应用来说,我们可以通过添加蓝图来扩展应用功能,而不至于影响原来的程序。不过有一点要注意,目前Flask蓝图的注册是静态的,不支持可插拔。

创建一个蓝图

比较好的习惯是将蓝图放在一个单独的包里,所以让我们先创建一个”admin”子目录,并创建一个空的__init__.py表示它是一个Python的包。现在我们来编写蓝图,将其存在”admin/admin_module.py”文件里:

from flask import Blueprint

admin_bp = Blueprint('admin', __name__)

@admin_bp.route('/')
def index(name):
    return '<h1>Hello, this is admin blueprint</h1>'

初始化Blueprint对象的第一个参数’admin’指定了这个蓝图的名称,第二个参数指定了该蓝图所在的模块名。

接下来,我们在应用中注册该蓝图。在Flask应用主程序中,使用app.register_blueprint()方法即可:

from flask import Flask
from admin.admin_module import admin_bp

app = Flask(__name__)
app.register_blueprint(admin_bp, url_prefix='/admin')

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

app.register_blueprint()方法的”url_prefix”指定了这个蓝图的URL前缀。现在,访问http://localhost:5000/admin/就可以加载蓝图的index视图了。
也可以在创建蓝图对象时指定其URL前缀:

admin_bp = Blueprint('admin', __name__, url_prefix='/admin')

这样注册时就无需指定:

app.register_blueprint(admin_bp)

蓝图资源

蓝图有自己的目录,它的所有资源都在其目录下。蓝图的资源目录是由创建Blueprint对象时传入的模块名__name__所在的位置决定的。

同时,我们可以指定蓝图自己的模板目录和静态目录。比如我们创建蓝图时传入

admin_bp = Blueprint('admin', __name__,
                     template_folder='templates',
                     static_folder='static')

这样,该蓝图的模板目录就在admin/templates下,而静态目录就在admin/static下。默认值就是这两个位置,不指定也没关系。

构建URL

from flask import url_for

url_for('admin.index')                          # return /admin/
url_for('admin.static', filename='style.css')   # return /admin/static/style.css

如果,url_for()函数的调用就在本蓝图下,蓝图名可以省略,但必须留下.表示当前蓝图:

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

部署和分发

到目前为止,我们启动Flask应用都是通过”app.run()”方法,在开发环境中,这样固然可行,不过到了生产环境上,势必需要采用一个健壮的,功能强大的Web应用服务器来处理各种复杂情形。同时,由于开发过程中,应用变化频繁,手动将每次改动部署到生产环境上很是繁琐,最好有一个自动化的工具来简化持续集成的工作。接下来讲如何将Flask的应用程序自动打包,分发,并部署到像Apache, Nginx等服务器中去。

使用setuptools打包Flask应用

作为Python标准的打包及分发工具,setuptools可以说相当地简单易用。它会随着Python一起安装在你的机器上。你只需写一个简短的setup.py安装文件,就可以将你的Python应用打包。

第一个安装文件

假设我们的项目名为setup-demo,包名为myapp,目录结构如下:

setup-demo/
  ├ setup.py         # 安装文件
  └ myapp/           # 源代码
      ├ __init__.py    
      ...

一个最基本的setup.py文件如下:

#coding:utf8
from setuptools import setup

setup(
    name='MyApp',         # 应用名
    version='1.0',        # 版本号
    packages=['myapp']    # 包括在安装包内的Python包
)
执行安装文件

有了上面的setup.py文件,我们就可以打各种包,也可以将应用安装在本地Python环境中。
创建egg包

$ python setup.py bdist_egg

该命令会在当前目录下的”dist”目录内创建一个egg文件,名为”MyApp-1.0-py2.7.egg”。文件名格式就是”应用名-版本号-Python版本.egg”,我本地Python版本是2.7。同时你会注意到,当前目录多了”build”和”MyApp.egg-info”子目录来存放打包的中间结果。

创建tar.gz包

$ python setup.py sdist --formats=gztar

同上例类似,只不过创建的文件类型是tar.gz,文件名为”MyApp-1.0.tar.gz”。

安装应用

$ python setup.py install

该命令会将当前的Python应用安装到当前Python环境的”site-packages”目录下,这样其他程序就可以像导入标准库一样导入该应用的代码了。

开发方式安装

$ python setup.py develop

如果应用在开发过程中会频繁变更,每次安装还需要先将原来的版本卸掉,很麻烦。使用”develop”开发方式安装的话,应用代码不会真的被拷贝到本地Python环境的”site-packages”目录下,而是在”site-packages”目录里创建一个指向当前应用位置的链接。这样如果当前位置的源码被改动,就会马上反映到”site-packages”里。

引入非Python文件

上例中,我们只会将”myapp”包下的源码打包,如果我们还想将其他非Python文件也打包,比如静态文件(JS,CSS,图片)。
这时我们要在项目目录下添加一个”MANIFEST.in”文件夹。假设我们把所有静态文件都放在”static”子目录下,现在的项目结构如下:

setup-demo/
  ├ setup.py         # 安装文件
  ├ MANIFEST.in      # 清单文件
  └ myapp/           # 源代码
      ├ static/      # 静态文件目录    
      ├ __init__.py    
      ...

我们在清单文件”MANIFEST.in”中,列出想要在包内引入的目录路径:

recursive-include myapp/static *
recursive-include myapp/xxx *

“recursive-include”表明包含子目录。还要在”setup.py”中将” include_package_data”参数设为True:

#coding:utf8
from setuptools import setup

setup(
    name='MyApp',         # 应用名
    version='1.0',        # 版本号
    packages=['myapp'],   # 包括在安装包内的Python包
    include_package_data=True    # 启用清单文件MANIFEST.in
)

之后再次打包或者安装,”myapp/static”目录下的所有文件都会被包含在内。如果你想排除一部分文件,可以在setup.py中使用”exclude_package_date”参数,比如:

setup(
    ...
    include_package_data=True,    # 启用清单文件MANIFEST.in
    exclude_package_date={'':['.gitignore']}
)

上面的代码会将所有”.gitignore”文件排除在包外。如果上述”exclude_package_date”对象属性不为空,比如”{‘myapp’:[‘.gitignore’]}”,就表明只排除”myapp”包下的所有”.gitignore”文件。

自动安装依赖

我们的应用会依赖于第三方的Python包,虽然可以在说明文件中要求用户提前安装依赖包,但毕竟很麻烦,用户还有可能装错版本。其实我们可以在setup.py文件中指定依赖包,然后在使用setuptools安装应用时,依赖包的相应版本就会被自动安装。让我们来修改上例中的setup.py文件,加入”install_requires”参数:

#coding:utf8
from setuptools import setup

setup(
    name='MyApp',         # 应用名
    version='1.0',        # 版本号
    packages=['myapp'],   # 包括在安装包内的Python包
    include_package_data=True,    # 启用清单文件MANIFEST.in
    exclude_package_date={'':['.gitignore']},
    install_requires=[    # 依赖列表
        'Flask>=0.10',
        'Flask-SQLAlchemy>=1.5,<=2.1'
    ]
)

setuptools会先检查本地有没有符合要求的依赖包,如果没有的话,就会从PyPI中获得一个符合条件的最新的包安装到本地。

如果应用依赖的包无法从PyPI中获取,我们需要指定其下载路径:

setup(
    ...
    install_requires=[    # 依赖列表
        'Flask>=0.10',
        'Flask-SQLAlchemy>=1.5,<=2.1'
    ],
    dependency_links=[    # 依赖包下载路径
        'http://example.com/dependency.tar.gz'
    ]
)

路径应指向一个egg包或tar.gz包,也可以是个包含下载地址(一个egg包或tar.gz包)的页面。

自动搜索Python包

之前我们在setup.py中指定了”packages=[‘myapp’]”,说明将Python包”myapp”下的源码打包。如果我们的应用很大,Python包很多。setuptools提供了”find_packages()”方法来自动搜索可以引入的Python包:

#coding:utf8
from setuptools import setup, find_packages

setup(
    name='MyApp',               # 应用名
    version='1.0',              # 版本号
    packages=find_packages(),   # 包括在安装包内的Python包
    include_package_data=True,   # 启用清单文件MANIFEST.in
    exclude_package_date={'':['.gitignore']},
    install_requires=[          # 依赖列表
        'Flask>=0.10',
        'Flask-SQLAlchemy>=1.5,<=2.1'
    ]
)

这样当前项目内所有的Python包都会自动被搜索到并引入到打好的包内。”find_packages()”方法可以限定你要搜索的路径,比如使用”find_packages(‘src’)”就表明只在”src”子目录下搜索所有的Python包。

使用setuptools打包Flask应用

setup.py文件

from setuptools import setup

setup(
    name='MyApp',
    version='1.0',
    long_description=__doc__,
    packages=['myapp','myapp.main','myapp.admin'],
    include_package_data=True,
    zip_safe=False,
    install_requires=[
        'Flask>=0.10',
        'Flask-Mail>=0.9',
        'Flask-SQLAlchemy>=2.1'
    ]
)

把文件放在项目的根目录下,还要写一个MANIFEST.in文件:

recursive-include myapp/templates *
recursive-include myapp/static *

编写完毕后,你可以创建一个干净的虚拟环境,然后运行安装命令

$ python setup.py install

使用Fabric远程部署Flask应用

Fabric是一个Python的库,它提供了丰富的同SSH交互的接口,可以用来在本地或远程机器上自动化、流水化地执行Shell命令。因此它非常适合用来做应用的远程部署及系统维护。

安装Fabric

首先Python的版本必须是2.7以上,可以通过下面的命令查看当前Python的版本:

$ python -V

Fabric的官网是www.fabfile.org,源码托管在Github上。你可以clone源码到本地,然后通过下面的命令来安装。

$ python setup.py develop

在执行源码安装前,你必须先将Fabric的依赖包Paramiko装上。推荐使用pip安装,只需一条命令即可:

$ pip install fabric
第一个例子

我们创建一个”fabfile.py”文件,然后写个hello函数:

def hello():
    print "Hello Fabric!"

现在,让我们在”fabfile.py”的目录下执行命令:

$ fab hello

你可以在终端看到”Hello Fabric!”字样。
fabfile.py文件中每个函数就是一个任务,任务名即函数名,上例中是hellofab命令就是用来执行fabfile.py中定义的任务,它必须显式地指定任务名。你可以使用参数-l来列出当前fabfile.py文件中定义了哪些任务:

$ fab -l

任务可以带参数,比如我们将hello函数改为:

def hello(name, value):
    print "Hello Fabric! %s=%s" % (name,value)

此时执行hello任务时,就要传入参数值:

$ fab hello:name=Year,value=2016

Fabric的脚本建议写在fabfile.py文件中,如果你想换文件名,那就要在”fab”命令中用”-f”指定。比如我们将脚本放在script.py中,就要执行:

$ fab -f script.py hello
执行本地命令

fabric.api包里的local()方法可以用来执行本地Shell命令,比如让我们列出本地/home/bjhee目录下的所有文件及目录:

from fabric.api import local

def hello():
    local('ls -l /home/bjhee/')

local()方法有一个capture参数用来捕获标准输出,比如:

def hello():
    output = local('echo Hello', capture=True)

这样,Hello字样不会输出到屏幕上,而是保存在变量output里。capture参数的默认值是False。

执行远程命令

Fabric真正强大之处不是在执行本地命令,而是可以方便的执行远程机器上的Shell命令。它通过SSH实现,你需要的是在脚本中配置远程机器地址及登录信息:

from fabric.api import run, env

env.hosts = ['example1.com', 'example2.com']
env.user = 'bjhee'
env.password = '111111'

def hello():
    run('ls -l /home/bjhee/')

fabric.api包里的run()方法可以用来执行远程Shell命令。上面的任务会分别到两台服务器example1.comexample2.com上执行ls -l /home/bjhee/命令。这里假设两台服务器的用户名都是bjhee,密码都是6个1。你也可以把用户直接写在hosts里,比如:

env.hosts = ['bjhee@example1.com', 'bjhee@example2.com']

如果你的env.hosts里没有配置某个服务器,但是你又想在这个服务器上执行任务,你可以在命令行中通过-H指定远程服务器地址,多个服务器地址用逗号分隔:

$ fab -H bjhee@example3.com,bjhee@example4.com hello

另外,多台机器的任务是串行执行的。

如果对于不同的服务器,我们想执行不同的任务,上面的方法似乎做不到,4我们要对服务器定义角色:

from fabric.api import env, roles, run, execute, cd

env.roledefs = {
    'staging': ['bjhee@example1.com','bjhee@example2.com'],
    'build': ['build@example3.com']
}

env.passwords = {
    'staging': '11111',
    'build': '123456'
}

@roles('build')
def build():
    with cd('/home/build/myapp/'):
        run('git pull')
        run('python setup.py')

@roles('staging')
def deploy():
    run('tar xfz /tmp/myapp.tar.gz')
    run('cp /tmp/myapp /home/bjhee/www/')

def task():
    execute(build)
    execute(deploy)

执行

$ fab task

这时Fabric会先在一台build服务器上执行build任务,然后在两台staging服务器上分别执行deploy任务。@roles装饰器指定了它所装饰的任务会被哪个角色的服务器执行。

如果某一任务上没有指定某个角色,但是你又想让这个角色的服务器也能运行该任务,你可以通过-R来指定角色名,多个角色用逗号分隔:

$ fab -R build deploy

这样buildstaging角色的服务器都会运行deploy任务了。注:staging是装饰器默认的,因此不用通过-R指定。

此外,上面的例子中,服务器的登录密码都是明文写在脚本里的。这样做不安全,推荐的方式是设置SSH自动登录。

其余操作可以去http://www.bjhee.com/fabric.html查看

使用Fabric远程部署Flask应用

编写fabfile.py文件:

from fabric.api import *

env.hosts = ['example1.com', 'example2.com']
env.user = 'bjhee'

def package():
    local('python setup.py sdist --formats=gztar', capture=False)

def deploy():
    dist = local('python setup.py --fullname', capture=True).strip()
    put('dist/%s.tar.gz' % dist, '/tmp/myapp.tar.gz')
    run('mkdir /tmp/myapp')
    with cd('/tmp/myapp'):
        run('tar xzf /tmp/myapp.tar.gz')
        run('/home/bjhee/virtualenv/bin/python setup.py install')
    run('rm -rf /tmp/myapp /tmp/myapp.tar.gz')
    run('touch /var/www/myapp.wsgi')

上例中,package任务是用来将应用程序打包,而deploy”任务是用来将Python包安装到远程服务器的虚拟环境中,这里假设虚拟环境在/home/bjhee/virtualenv下。安装完后,我们将/var/www/myapp.wsgi文件的修改时间更新,以通知WSGI服务器(如Apache)重新加载它。对于非WSGI服务器,比如uWSGI,这条语句可以省去。

编写完后,运行部署脚本测试下:

$ fab package deploy

使用Apache+mod_wsgi运行Flask应用

Flask应用是基于WSGI规范的,所以它可以运行在任何一个支持WSGI协议的Web应用服务器中,最常用的就是Apache+mod_wsgi的方式。上面的Fabric脚本已经完成了将Flask应用部署到远程服务器上,接下来要做的就是编写WSGI的入口文件”myapp.wsgi”,我们假设将其放在Apache的文档根目录在”/var/www”下。

activate_this = '/home/bjhee/virtualenv/bin/activate_this.py'
execfile(activate_this, dict(__file__=activate_this))

import os
os.environ['PYTHON_EGG_CACHE'] = '/home/bjhee/.python-eggs'

import sys;
sys.path.append("/var/www")

from myapp import create_app
import config
application = create_app('config')

你需要预先创建配置文件”config.py”,并将其放在远程服务器的Python模块导入路径中。上例中,我们将”/var/www”加入到了Python的模块导入路径,因此可以将”config.py”放在其中。另外,记得用setuptools打包时不能包括”config.py”,以免在部署过程中将开发环境中的配置覆盖了生产环境。

在Apache的”httpd.conf”中加上脚本更新自动重载和URL路径映射:

WSGIScriptReloading On
WSGIScriptAlias /myapp /var/www/myapp.wsgi

重启Apache服务器后,就可以通过”http://example1.com/myapp”来访问应用了。

使用Nginx+uWSGI运行Flask应用

需要先准备好Nginx+uWSGI的环境
uWSGI是一个Web应用服务器,它具有应用服务器,代理,进程管理及应用监控等功能。它支持WSGI协议,同时它也支持自有的uWSGI协议,该协议据说性能非常高,而且内存占用率低,为mod_wsgi的一半左右,我没有实测过。它还支持多应用的管理及应用的性能监控。虽然uWSGI本身就可以直接用来当Web服务器,但一般建议将其作为应用服务器配合Nginx一起使用,这样可以更好的发挥Nginx在Web端的强大功能。

安装uWSGI
$ pip install uwsgi

查看当前的uwsgi的版本:

$ uwsgi --version

让我们来写个Hello World的WSGI应用,并保存在”server.py”文件中:

def application(environ, start_response):
    status = '200 OK'
    output = 'Hello World!'

    response_headers = [('Content-type', 'text/plain'),
                        ('Content-Length', str(len(output)))]
    start_response(status, response_headers)

    return [output]

在uWSGI中运行它,执行命令:

$ uwsgi --http :9090 --wsgi-file server.py

然后打开浏览器,访问”http://localhost:9090″,你就可以看到”Hello World!”字样了。

上面的命令中”- -http”参数指定了HTTP监听地址和端口,”- -wsgi-file”参数指定了WSGI应用程序入口,uWSGI会自动搜寻名为”application”的应用对象并调用它。

更进一步,uWSGI可以支持多进程和多线程的方式启动应用,也可以监控应用的运行状态。我们将启动的命令改为:

$ uwsgi --http :9090 --wsgi-file server.py --master --processes 4 --threads 2 --stats 127.0.0.1:9191

执行它后,uWSGI将启动4个应用进程,每个进程有2个线程,和一个master主进程(监控其他进程状态,如果有进程死了,则重启)。同时,你可以访问”127.0.0.1:9191″来获取JSON格式的应用运行信息,uWSGI还提供了工具命令”uwsgitop”来像top一样监控应用运行状态,你可以用pip来安装它。

我们可以将参数写在配置文件里,启动uWSGI时指定配置文件即可。配置文件可以是键值对的格式,也可以是XML,YAML格式,这里我们使用键值对的格式。让我们创建一个配置文件”myapp.ini”:

[uwsgi]
http=:9090
wsgi-file=server.py
master=true
processes=4
threads=2
stats=127.0.0.1:9191

然后就可以将启动命令简化为:

$ uwsgi myapp.ini

配置Nginx

首先,我们将uWSGI的HTTP端口监听改为socket端口监听,即将配置文件中的”http”项去掉,改为”socket”项:

[uwsgi]
socket=127.0.0.1:3031
wsgi-file=server.py
master=true
processes=4
threads=2
stats=127.0.0.1:9191

然后,打开Nginx的配置文件,Ubuntu上默认是”/etc/nginx/sites-enabled/default”文件,将其中的根路径部分配置为:

location / {
    include uwsgi_params;
    uwsgi_pass 127.0.0.1:3031;
}

这段配置表明Nginx会将收到的所有请求都转发到”127.0.0.1:3031″端口上,即uWSGI服务器上。现在让我们重启Nginx,并启动uWSGI服务器:

$ sudo service nginx restart
$ uwsgi myapp.ini

访问”http://localhost”,我们会再次看到”Hello World!”。

部署多个应用

一个Nginx中,可以同时运行多个应用,不一定是Python的应用。我们期望通过不同的路径来路由不同的应用,因此就不能像上例那样直接修改根目录的配置。假设我们希望通过”http://localhost/myapp”来访问我们的应用,首先要在Nginx的配置文件中,加入下面的内容:

location /myapp {
    include uwsgi_params;
    uwsgi_param SCRIPT_NAME /myapp;
    uwsgi_pass 127.0.0.1:3031;
}

这里我们定义了一个uWSGI参数”SCRIPT_NAME”,值为应用的路径”/myapp”。接下来,在uWSGI的启动配置中,去掉”wsgi-file”项,并加上:

[uwsgi]
...
mount=/myapp=server.py
manage-script-name=true

“mount”参数表示将”/myapp”地址路由到”server.py”中,”manage-script-name”参数表示启用之前在Nginx里配置的”SCRIPT_NAME”参数。再次重启Nginx和uWSGI,你就可以通过”http://localhost/myapp”来访问应用了。

使用Nginx+uWSGI运行Flask应用

编写uWSGI的启动文件”myapp.ini”:

[uwsgi]
socket=127.0.0.1:3031
callable=app
mount=/myapp=run.py
manage-script-name=true
master=true
processes=4
threads=2
stats=127.0.0.1:9191
virtualenv=/home/bjhee/virtualenv

再修改Nginx的配置文件,Linux上默认是”/etc/nginx/sites-enabled/default”,加上目录配置:

location /myapp {
    include uwsgi_params;
    uwsgi_param SCRIPT_NAME /myapp;
    uwsgi_pass 127.0.0.1:3031;
}

重启Nginx和uWSGI后,就可以通过”http://example1.com/myapp”来访问应用了。

使用Tornado运行Flask应用

Tornado的强大之处在于它是非阻塞式异步IO及Epoll模型,采用Tornado的可以支持数以万计的并发连接,对于高并发的应用有着很好的性能。本文不会展开Tornado的介绍,感兴趣的朋友们可以参阅其官方文档。使用Tornado来运行Flask应用很简单,只要编写下面的运行程序,并执行它即可:

from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from myapp import create_app
import config

app = create_app('config')

http_server = HTTPServer(WSGIContainer(app))
http_server.listen(5000)
IOLoop.instance().start()

之后你就可以通过”http://example1.com:5000″来访问应用了。

使用Gunicorn运行Flask应用

Gunicorn是一个Python的WSGI Web应用服务器,是从Ruby的Unicorn移植过来的。它基于”pre-fork worker”模型,即预先开启大量的进程,等待并处理收到的请求,每个单独的进程可以同时处理各自的请求,又避免进程启动及销毁的开销。不过Gunicorn是基于阻塞式IO,并发性能无法同Tornado比。更多内容可以参阅其官方网站。另外,Gunicorn同uWSGI一样,一般都是配合着Nginx等Web服务器一同使用。

让我们先将应用安装到远程服务器上,然后采用Gunicorn启动应用,使用下面的命令即可:

$ gunicorn run:app

解释下,因为我们的应用使用了工厂方法,所以只在run.py文件中创建了应用对象app,gunicorn命令的参数必须是应用对象,所以这里是”run:app”。现在你就可以通过”http://example1.com:8000″来访问应用了。默认监听端口是8000。

假设我们想预先开启4个工作进程,并监听本地的5000端口,我们可以将启动命令改为:

$ gunicorn -w 4 -b 127.0.0.1:5000 run:app

来源

Flask入门系列(一)–Hello World


https://blog.csdn.net/u011054333/article/details/70151857/

Flask进阶系列(六)–蓝图(Blueprint)

  1. http://www.cmspojie.com – CMS破解站提供世界顶级设计师的无限网站模板,Wordpress主题,Opencart主题,Magento主题,Prestashop主题,Shopify主题,HTML网站模板,各种CMS模板资源尽在CMS破解站!
    外贸营销型
    插件开发
    opencart主题
    prestashop主题
    shopify

    Bob Pinkley     回复
  2. 可以把toc+插件的侧边栏功能给开了,这样滑到下面的时候就有侧边栏了

    shaobao     回复
 

发表评论

 
发表评论