Flask学习3:表单

flask-表单

原生表单

1.添加模板文件login.html

<form method="post" action="{{ url_for('check') }}">
    用户名:<input type="text" name="username">
    <input type="submit" value="登陆">
</form>

2.视图函数

# 登陆
@app.route('/login/', methods=['GET', 'POST'])
def login():
    return render_template('login.html')

# 校验
@app.route('/check/', methods=['GET', 'POST'])
def check():
    return '登陆成功, %s~' % request.form['username']

3.将登陆页面和校验的路由合并

@app.route('/login/', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    return '登陆成功, %s~' % request.form['username']

一个路由处理时,表单的action属性不用书写

flask-wtf

1.说明

​是一个用于表单处理的扩展库,提供了CSRF、校验等功能,使用非常方便。

2.安装

pip install flask-wtf

3.使用

# 原生渲染
# 导入表单基类
from flask_wtf import FlaskForm
# 导入相关类型
from wtforms import StringField, SubmitField
# 导入验证器
from wtforms.validators import DataRequired

# CSRF需要
app.config['SECRET_KEY'] = 'idandan'

# 定义表单类
class NameForm(FlaskForm):
    name = StringField('账号:', validators=[DataRequired()])
    submit = SubmitField('登陆')


@app.route('/formTest/')
def formTest():
    # 创建表单对象
    form = NameForm()
    return render_template('formTest.html', form = form)


# html
<form>
    {{ form.hidden_tag() }}
    {{ form.name .label()}} {{ form.name(id = 'username', class  = 'username') }}
    {{ form.submit }}
</form>



# bootstrap渲染
# 设置bootstrap本地库中的文件
app.config['BOOTSTRAP_SERVER_LOCAL'] = True

# html
{% extends 'bootstrap/base.html' %}
{# 导入 #}
{% import 'bootstrap/wtf.html' as wtf %}

{% block title %}
    表单渲染
{% endblock %}

{% block content %}
    <div class="container">
        {{ wtf.quick_form(form) }}
    </div>
{% endblock %}

4.表单校验

@app.route('/formTest/', methods=['GET', 'POST'])
def formTest():
    # 创建表单对象
    form = NameForm()
    # return render_template('formTest.html', form = form)
    # return render_template('bootForm.html', form = form)

    # 表单校验
    name = None
    if form.validate_on_submit():
        name = form.name.data
        form.name.data = ''
    return render_template('bootForm.html', form = form, name = name)


# html
{% extends 'bootstrap/base.html' %}

{# 导入 #}
{% import 'bootstrap/wtf.html' as wtf %}

{% block title %}
    表单渲染
{% endblock %}

{% block content %}
    <h1>
        Hi,
        {% if name %}
            {{ name }}!
        {% else %}
            Stranger!
        {% endif %}
    </h1>

    <div class="container">
        {{ wtf.quick_form(form) }}
    </div>
{% endblock %}

5.POST重定向GET

@app.route('/formTest/', methods=['GET', 'POST'])
def formTest():
    # 创建表单对象
    form = NameForm()

    # 表单校验
    name = None
    if form.validate_on_submit():
        session['name'] = form.name.data
        form.name.data = ''
        return redirect(url_for('formTest'))
    name = session.get('name')
    return render_template('bootForm.html', form = form, name = name)

6.常见的字段类型

未分类

7.常见验证器类

未分类

8.自定义字段验证

# 定义表单类
class NameForm(FlaskForm):
    name = StringField('账号:', validators=[DataRequired()])
    submit = SubmitField('登陆')

    # 自定义字段验证函数,格式是写一个‘valudate_字段名’函数
    def validate_name(self, field):
        if len(field.data) < 6:
            raise ValidationError('账号长度不能小于6个字符')

flash消息

1.说明

​ 当用户请求发出后,用户状态发生了改变,需要给出提示、警告等信息时,通常可以通过弹窗的形式给出指示,用户可以跟据提示进行下一步操作,也可手动取消显示。

2.使用

​在合适的时候书写flash消息,调用flash函数

@app.route('/formTest/', methods=['GET', 'POST'])
def formTest():
    # 创建表单对象
    form = NameForm()
    # return render_template('formTest.html', form = form)
    # return render_template('bootForm.html', form = form)

    # 表单校验
    name = None
    if form.validate_on_submit():
        oldName = session.get('name')
        if oldName is not None and oldName != form.name.data:
            flash('Change your name!')
        session['name'] = form.name.data
        return redirect(url_for('formTest'))
    name = session.get('name')
    return render_template('bootForm.html', form = form, name = name)


# html
{% for message in get_flashed_messages() %}
    <h3>{{ message }}</h3>
{% endfor %}

若好多页面都有弹出消息,可以将flash消息放在基础模板中展示。

flask-moment

1.说明

​专门负责时间本地化显示的扩展库,使用非常方便 。

2.安装

pip install flask-moment

3.使用

# 导入类库
from flask_moment import Moment
# 创建对象
moment = Moment(app)

from datetime import datetime, timedelta
@app.route('/moment/')
def moment():
    current_time = datetime.utcnow() + timedelta(seconds=-3600)  # 伪造时间
    return render_template('moment.html', current_time=current_time)

# HTML
{#{% extends 'bootstrap/base.html' %}#}
{% block content %}
    <div class="time">
        <h1>时间本地化显示</h1>
        <h3>距离:{{ moment(current_time).fromNow() }}</h3>
        <h3>时间:{{ moment(current_time).format('LLLL') }}</h3>
    </div>
{% endblock %}

{#加载jquery和moment,因为moment.js需要依赖,使用bootstrap时就不需要再加载jquery#}
{{ moment.include_jquery() }}
{{ moment.include_moment() }}

Flask学习2:模板引擎

模板引擎

模板引擎

说明:

模板文件就是按照特定规则书写的一个负责展示效果的html文件;模板引擎就是提供了特定规则的解释和替换的功能。

Jinja2:

在Flask中使用的就是该模板引擎,由Flask核心开发组人员开发。

jinja2使用

1.准备工作,目录结构

project/
manage.py  # 项目的启动控制文件
    templates/  # 所有的模板文件

2.渲染模板文件

在templates下创建一个模板文件(hello.html),内容如下:

<h1>渲染字符串</h1>

​模板渲染

@app.route('/')
def index():
    # return '模板引擎测试'

    # 渲染模板文件
    # return render_template('hello.html')

    # 渲染字符串
    return render_template_string('<h1>渲染字符串</h1>')

3.使用变量

​在templates下创建一个模板文件(hello.html),内容如下:

<h1>Nice,{{ g.name }}</h1>


模板渲染

@app.route('/var/')
def va():
    # g对象,在模板使用中不需要分配
    g.name = 'Nian'
    # return render_template('hello.html', name='dandan')
    # return render_template_string('<h1>{{ name }}</h1>', name='dandan')
    return render_template_string('<h1>{{ g.name }}</h1>')

4.使用函数

​在模板文件中可以使用特定的函数,对指定的变量处理后再显示,用法如下:

<h1>Nice,{{ g.name | upper }}</h1>

​常用函数

未分类

5.流程控制

​分支语句(if-else)

    {% if name %}
        <h1>{{ name }}</h1>
    {% else %}
        <h1>Stranger</h1>
    {% endif %}


@app.route('/control/')
def control():
    # return render_template('if-else.html', name = 'Nian')
    return render_template('if-else.html')

​循环语句

    <ol>
        {% for i in range(1, 5) %}
            <li>{{ i }}</li>
        {% endfor %}
    </ol>

6.文件包含

​include1.html

# include.html
<h1>Include 1</h1>

{% include 'include2.html' %}


# include2.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>被包含</title>
</head>
<body>
    include 2
</body>
</html>

7.宏的使用

    {# 定义宏 #}
    {% macro show_name(name) %}
        <h1>Hello, {{ name }}</h1>
    {% endmacro %}


    {# 调用宏 #}
    {{ show_name(name) }}


    # 宏的使用
    @app.route('/macro/')
    def macro():
        # return render_template('macro.html', name = 'Nian')
        return render_template('macro2.html', name = 'dandan')

  说明:
      采用类似Python中的函数的形式进行定义和调用,可以把特定功能的内容定义为一个宏,哪里使用哪里调用即可。

8.模板继承

# parents.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}父级标题{% endblock %}</title>
</head>
<body>
    {% block body %}
        默认内容
    {% endblock %}
</body>
</html>


# children.html
{% extends 'parents.html ' %}

{% block title %}
    丹丹测试
{% endblock %}

{% block body %}
    这是模板继承   这是=》{{ super() }}
{% endblock %}

flask-bootstrap

1.安装

pip install flask-bootstrap

2.使用:

from flask_bootstrap import Bootstrap

# 创建实例
app = Flask(__name__)
bootstrap = Bootstrap(app)

3.测试模板文件boot.html

{# 继承自bootstrap模板 #}
{% extends 'bootstrap/base.html' %}

{% block title %}
    用户注册
{% endblock %}

{% block content %}
    <div class="container">
        欢迎丹丹来注册
    </div>
{% endblock %}

4.bootstrap继承模板base.html中定义的block

未分类

使用bootstrap时,发现重写block后原来的显示效果消失,可能是忘了调用super

定义项目基础模板

1.说明:

一个项目中,很多页面都很相似,只有细微的差别,如果每个页面都定制,势必会有大量的重复代码。为了简化这种重复工作,通常我们会为项目定制一个基础模板,让它继承自bootstrap,其他页面继承自该基础模板,只需稍微定制即可。

2.步骤

1.从bootcss.com复制一个导航条
2.将container-fluid改为container
3.根据需要,定制显示内容

3.基础模板文件base.html

{% extends 'bootstrap/base.html' %}

{% block title %}
    丹丹的标题
{% endblock %}

{% block navbar %}
    <nav class="navbar navbar-inverse">
  <div class="container">
    <!-- Brand and toggle get grouped for better mobile display -->
    <div class="navbar-header">
      <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
        <span class="sr-only">Toggle navigation</span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
      <a class="navbar-brand" href="#">首页</a>
    </div>

    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
      <ul class="nav navbar-nav">
        <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li>
        <li><a href="#">Link</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">One more separated link</a></li>
          </ul>
        </li>
      </ul>
      <form class="navbar-form navbar-left">
        <div class="form-group">
          <input type="text" class="form-control" placeholder="Search">
        </div>
        <button type="submit" class="btn btn-default">Submit</button>
      </form>
      <ul class="nav navbar-nav navbar-right">
        <li><a href="#">Link</a></li>
        <li class="dropdown">
          <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a>
          <ul class="dropdown-menu">
            <li><a href="#">Action</a></li>
            <li><a href="#">Another action</a></li>
            <li><a href="#">Something else here</a></li>
            <li role="separator" class="divider"></li>
            <li><a href="#">Separated link</a></li>
          </ul>
        </li>
      </ul>
    </div><!-- /.navbar-collapse -->
  </div><!-- /.container-fluid -->
</nav>
{% endblock %}


{# 定制内容 #}
{% block content %}
    <div class="container">
        {# 这里可以为弹出内容显示预留位置 #}
        {% block page_content %}
            默认内容
        {% endblock %}
    </div>
{% endblock %}

错误页面定制

1.添加视图函数

# 错误页面定制
@app.errorhandler(404)
def error(e):
    return render_template('404.html')

2.定制404.html

{% extends 'base.html' %}

{% block title %}
    出错了
{% endblock %}

{% block content %}
    <h1>找不见了吧@_@</h1>
{% endblock %}

回顾url_for函数

1.根据视图函数名反向构造路由地址,路由需要的直接构造,多出来的参数以GET方式 传递

2.若需要构造完整的外部链接,需要添加_extenal=True参数

3.通常网站中的点击链接都是url_for构造的

<li><a href="{{ url_for('register') }}">注册</a></li>

加载静态资源

1.flask框架中静态资源的默认目录为static,项目目录如下:

project/
    manage.py  # 启动控制文件
    static/  # 存放静态资源
    templates/  # 存放模板文件

2.加载网站图标

{# 加载收藏夹的图标 #}
{% block head %}
    {{ super }}
    <link rel="icon" type="image/x-icon" href="{{ url_for('static', filename = 'bg.png') }}">
{% endblock %}

3.静态资源加载

# manage.py
# 加载静态文件
@app.route('/source/')
def source():
    return render_template('static.html')

# static.html
{% block styles %}
    {{ super() }}
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename = 'css.css') }}">
{% endblock %}

4.设置本地日期和时间

# base.html
{#引入moment.js类#}
{% block scripts %}
    {{ super() }}
    {{ moment.include_moment() }}
{% endblock %}



# manage.py
# 使用Flask-Monment本地化日期和时间
from datetime import datetime
@app.route('/testtime/')
def testtime():
    return render_template('testtime.html', current_time = datetime.utcnow())


# testtime.html
  {% block content %}
    <p>The local date and time is {{ moment(current_time).format('LLL') }}</p>
{% endblock %}

5.表单

# maange.py
from flask import Flask, render_template, session, redirect, url_for
from flask_script import Manager
from flask_bootstrap import Bootstrap
from wtforms import SubmitField, StringField, IntegerField
from wtforms.validators import Required
from flask_wtf import Form

# 实例化对象
app = Flask(__name__)
bootstrap = Bootstrap(app)
manager = Manager(app)
app.config['SECRET_KEY'] = 'idandan'

class PersonInfo(Form):
    name = StringField('Name: ', validators=[Required()])
    age = IntegerField('age: ', validators=[Required()])
    submit = SubmitField('Submit')

@app.route('/', methods=['GET', 'POST'])
def index():
    name = None
    age = None
    form = PersonInfo()

    if form.validate_on_submit():
        session['name'] = form.name.data
        session['age'] = form.age.data
        return redirect(url_for('index'))
        # name = form.name.data
        # age = form.age.data
        # form.name.data = ''
        # form.age.data = ''  # ? 不能清空数据???
    # return render_template('personInfo.html', form = form, name = session.get('name'), age = session.get('age'))
    return render_template('personInfo.html', form = form)

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



# personInfo.html
{% import 'bootstrap/wtf.html' as wtf %}

{% block title %}
    Form
{% endblock %}

{% block content %}
{#    <h1>#}
{#        Nice,#}
{#        {% if name %}#}
{#            {{ name }}, your age is {{ age }}?#}
{#        {% else %}#}
{#            Stranger!#}
{#        {% endif %}#}
{#    </h1>#}

    <h1>
        Nice,
        {% if session.get('name') %}
            {{ session.get('name') }}, your age is {{ session.get('age') }}?
        {% else %}
            Stranger!
        {% endif %}
    </h1>

    {{ wtf.quick_form(form) }}

{% endblock %}

Flask学习1:简介

Flask入门

web工作原理

1、C/S和B/S架构

​ C/S:客户端/服务器

​ B/S:浏览器/服务器

2、B/S架构工作原理

​ 客户端(浏览器) <=> WEB服务器(apache/nginx) <=> WSGI <=> Python(Flask)

Flask框架

1、简介

​ flask是一个非常小的Python Web框架,被称为微型框架,只提供了一个强健的核心,其他功能都是通过扩展来实现的,意味着可以根据项目的需求量身打造。

2、组成

  1. 测试、路由、WSGI系统
  2. 模板引擎(jinja2, 是flask核心开发组人员开发)

3、安装

pip install flask

4、完整程序

# 导入类库
from flask import Flask

# 创建实例
app = Flask(__name__)

# 视图函数
@app.route('/')
def index():
    return '<h1>Nice dandan</h1>'

# 启用实例
if __name__ == '__main__':
    app.run()

# 浏览器测试:http://127.0.0.1:5000/

5、启动参数

未分类

app.run(debug=True, threaded=True, port=8001, host=’0.0.0.0’)

6、请求与响应

变量或对象

未分类

请求钩子函数

未分类

7、视图函数

说明:

无参路由

# 无参路由

@app.route('/test2/')
def test2():
   return 'This is test2 page!'

带参路由

# 带参路由,参数写在<>中

@app.route('/welcome/<name>/')
def welcome(name):
   return 'Hello, %s~' % name

指定参数类型,如string(默认)、int、float、path(参数中的/不再作为分隔符)
@app.route('/user/int:uid')
  def show(uid):
      return '%d 号,Nice' % uid

测试path

 @app.route('/path/path:p')
 def path(p):
   return p

'''
说明:
   1.路由末尾的'/'建议加上,需要的时候,浏览器会自动加上,输入时加不加都可以
   2.若需要指定参数,将参数写在<>中,视图函数参数要与路由参数一致
   3.若需要知道参数类型,如int/float/path等,写在参数前,用‘:’与参数隔开
   4.不指定类型,参数默认是string,path其实也是字符串,只是'/'不再作为分隔符
'''  

8、请求(request)

python

请求

@app.route(‘/request/’) 
def req(): 
# 完整的url请求地址 
# return request.url
# 基础的url(除了参数以外的url,即不包括get参数)
# return request.base_url

# 主机地址和端口号
# return request.host_url

# 只有路由信息(只包含装饰器中的路由地址)
# return request.path

# 请求方式
# return request.method

# 客户端的ip地址
# return request.remote_addr

# 获取get参数
# return request.args['name']

# 获取请求头
return request.headers['User-Agent']

9、响应

# 响应
@app.route('/response/')
def response():
    # 默认状态码200
    # return 'Nice'

    # 在响应的后面指定状态码
    # return 'Page not found', 404

    # 先构造一个响应(也可以指定状态吗),然后返回
    resp = make_response('这是通过函数构造的响应', 404)
    return resp

10、重定向(redirect)

# 重定向
@app.route('/redirect/')
def new():
    # return '新的内容'
    # 重定向,需要指定一个路由地址
    # return redirect('/response/')

    # 根据视图函数,反向的构造出来路由地址,传递的参数是视图函数
    # return url_for('req')

    # 构造带参数的路由,直接写在后面即可
    # return url_for('welcome', name='Nian')
    return redirect(url_for('welcome', name='Nian'))

11、终止(abort)

# 抛出指定错误
@app.route('/abort/')
def err():
    # 使用abort函数,不是说控制权就归你了,
    # 他只是向系统抛出了指定的异常,
    # 异常的处理仍然会按照框架中写好的方式进行
    abort(404)
    return 'abort测试'

# 定制错误页面
@app.errorhandler(404)
def page_not_found(e):
    return 'Not found'

12、会话控制(cookie/session)

# 设置cookie
@app.route('/set_cookie/')
def set_cookie():
    resp = make_response('cookie已设置')

    # 设置cookie
    resp.set_cookie('name', '小啊念')
    return resp

# 获取cookie
@app.route('/get_cookie/')
def get_cookie():
    return request.cookies.get('name') or 'Stranges'


# 设置session
# 设置密钥,用于加密解密的字符串,不只是用于session
app.config['SECRET_KEY'] = '123456'

# 设置session
@app.route('/set_session/')
def set_session():
    session['user'] = 'Likesea'
    return 'session已设置'

# 获取session
@app.route('/get_session/')
def get_session():
    return session.get('user', 'Strangers')

flask-script扩展

1、简介

​ 简单来说就是一个flask终端运行的解析器;因为在测试完项目后,最好不要改动任何代码,

否则可能会带来风险,所以借助扩展库实现启动,通过传递参数完成不同的启动。

2、安装

pip install flask-script

3、使用

# 导入类库
from flask_script import Manager

# 创建对象
manager = Manager(app)

# 启用实例
if __name__ == '__main__':
    manager.run()

# 启动:python manage.py runserver

4、启动参数

-? / --help :查看启动设置帮助
-h / --host :指定主机
-p / --port :指定端口
--threaded  :启动多线程
-d  :开启调试模式
-r  :代码修改后自动加载

# 实例:python manage.py runserver -d -r

蓝本(blueprint)

1、说明:

​ 当代码越来越复杂时,将所有的视图函数放在一个文件中很明显是不合适的,如果能够根据功能模块进行划分,存储在不同的文件中,蓝本就是为解决此问题而生的。

# 导入类库
from flask import Blueprint

# 创建蓝本对象
user = Blueprint('user', __name__)

# 添加路由(视图函数)
@user.route('/login/')
def login():
    return '登陆'


# 注册蓝本,未注册的蓝本处于休眠状态(无法使用)
from user import user

# 可以通过url_prefixl指定路由前缀
app.register_blueprint(user, url_prefix='/user')

扩展

1、MVC

​M:model, 模型,数据模型

V:view, 视图,负责数据展示

​C:controller, 控制器,负责业务逻辑的扩展

2、MTV

​M:model,模型,数据模型

​T:templates, 模板,负责数据展示

V:view function,视图函数,负责业务逻辑的处理

flask 更新数据库失败

在使用SQLALchemy更新数据的时候,一直不成功。
数据更新的方式采用ajax,具体代码如下:

$(function () {
    $('#user_profile').click(function (event) {
        event.preventDefault();  //prevent the actual form post
        user_profile();

    });

    // Inject our CSRF token into our AJAX request.
    $.ajaxSetup({
        beforeSend: function (xhr, settings) {
            if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", $('#csrf_token').val())
            }
        }
    })
})

function user_profile() {
    $.ajax({
        type: $('#tab_1_1 form').attr("method"),
        url: $('#tab_1_1 form').attr("action"),
        data: $('#tab_1_1 form').serialize(),
        contentType: "application/x-www-form-urlencoded",
        success: function (data) {
            console.log(data);
        }
    });
}

但是提交到后台无法获取数据,提交的数据结构如下所示:

csrf_token=IjkwNjQ5OTE2MTJjMGMwZDk4OTc5ZmQxOTZjOTgxMmU4MmU0ZDRmMzAi.DjHgiQ.hDgD-Yz89RkJZPL-YorWJdG7Z7c&fullname=%E9%BB%84%E5%85%B5&nick_name=%E7%8B%AC%E8%A1%8C%E4%BE%A0&phone_number=13429950072&about_me=%E6%88%91%E6%98%AF%E4%B8%80%E4%B8%AA%E5%AE%85%E7%94%B7%EF%BC%8C%E8%AF%B7%E5%A4%A7%E5%AE%B6%E4%B8%8D%E8%A6%81%E6%89%93%E6%89%B0%E6%88%91%E7%BB%A7%E7%BB%AD%E5%AE%85%E3%80%82&website_url=https%3A%2F%2Fpdf-lib.org

之后看一下后台如何接收数据:

# 验证提交
if form.validate_on_submit():
    current_user.fullname = form.fullname.data
    current_user.phone_number = form.phone_number.data
    current_user.nick_name = form.nick_name.data
    current_user.website_url = form.website_url.data
    try:
        sql_query=str(db.session.add(current_user._get_current_object()))
        print(sql_query)
        print(current_user._get_current_object())
        db.session.commit()
    except:
        flash('账户更新失败!')
    flash('账户资料已更新!')

使用这种方式无法获取数据,数据一直没有更新,搞不清是什么情况。之后不断调试,参考了别人文章之后终于把问题解决了。
需要修改源代码如下:

# 验证提交
    if form.validate_on_submit():
        current_user.fullname = request.values.get('fullname', 0)
        current_user.phone_number = request.values.get('phone_number', 0)
        current_user.nick_name = request.values.get('nick_name', 0)
        current_user.about_me = request.values.get('about_me', 0)
        current_user.website_url = request.values.get('website_url', 0)
        try:
            db.session.add(current_user._get_current_object())
            db.session.commit()
        except:
            flash('账户更新失败!')
        flash('账户资料已更新!')

这个是flask获取post参数的方式,具体可以参考这里: https://pdf-lib.org/Home/Details/5201

这样再次获取数据就没有什么问题了。
再次使用SQLALchemy保存数据,数据已经成功更新了。

flask – 生成 secret key

在flask项目中,Session, Cookies以及一些第三方扩展都会用到SECRET_KEY值,这是一个比较重要的配置值。

在使用flask时,我产生了这个错误:the session is unavailable because no secret key was set. Set the secret_key on the application to something unique and secret

解决方法是在flask项目开头加入设置SECRET_KEY。

app = Flask(__name__)
app.config['SECRET_KEY'] = '123456'
# or
app.secret_key = '123456'
# or
app.config.update(SECRET_KEY='123456')

如果需要设置一个随机的SECRET_KEY值。我们可以使用os模块的urandom函数来获得随机值。

>>> import os
>>> os.urandom(24)
'xeewxe4xc0xeexb1]x9bxa0x9e)x15Qhemxe5xf17xd6xceBxb7xb4'

Flask 运行性能调优

概述

目前使用的平台在使用的过程中发现性能比较低,所以需要想办法进行性能调优。

使用的工具

Siege是一个http负载测试和基准测试工具。 它旨在让网络开发者在胁迫下测量他们的代码,看看它将如何站起来加载到互联网上。 Siege支持基本认证,cookies,HTTP,HTTPS和FTP协议。 它允许用户使用可配置数量的模拟客户端访问服务器。 这些客户将服务器置于“under siege”。

说白了Siege 就是一个多线程的http服务器压力测试工具,官网在这里,最近版本3.1.4。怎么安装可以在官网上查看。这个官网好像已经很长时间没更新了,我在mac下安装的siege已经到了4.0.4版本。Mac下直接使用brew安装就可以了。

brew install siege

siege
SIEGE 4.0.4
Usage: siege [options]
       siege [options] URL
       siege -g URL
Options:
  -V, --version             VERSION, prints the version number.
  -h, --help                HELP, prints this section.
  -C, --config              CONFIGURATION, show the current config.
  -v, --verbose             VERBOSE, prints notification to screen.
  -q, --quiet               QUIET turns verbose off and suppresses output.
  -g, --get                 GET, pull down HTTP headers and display the
                            transaction. Great for application debugging.
  -p, --print               PRINT, like GET only it prints the entire page.
  -c, --concurrent=NUM      CONCURRENT users, default is 10
  -r, --reps=NUM            REPS, number of times to run the test.
  -t, --time=NUMm           TIMED testing where "m" is modifier S, M, or H
                            ex: --time=1H, one hour test.
  -d, --delay=NUM           Time DELAY, random delay before each requst
  -b, --benchmark           BENCHMARK: no delays between requests.
  -i, --internet            INTERNET user simulation, hits URLs randomly.
  -f, --file=FILE           FILE, select a specific URLS FILE.
  -R, --rc=FILE             RC, specify an siegerc file
  -l, --log[=FILE]          LOG to FILE. If FILE is not specified, the
                            default is used: PREFIX/var/siege.log
  -m, --mark="text"         MARK, mark the log file with a string.
                            between .001 and NUM. (NOT COUNTED IN STATS)
  -H, --header="text"       Add a header to request (can be many)
  -A, --user-agent="text"   Sets User-Agent in request
  -T, --content-type="text" Sets Content-Type in request
      --no-parser           NO PARSER, turn off the HTML page parser
      --no-follow           NO FOLLOW, do not follow HTTP redirects

Copyright (C) 2017 by Jeffrey Fulmer, et al.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.

直接给出几个常用命令。

# get 请求
siege -c 1000 -r 100 -b url
# post 请求
siege -c 1000 -r 100 -b url POST {"accountId":"123","platform":"ios"}"

测试

测试代码

看一下文件树结构,tree

➜  flask tree
.
├── hello1.py
├── hello1.pyc
├── hello2.py
├── hello2.pyc
├── hello3.py
└── templates
    └── hello.html

下面是一段没有使用模板,只返回字符串的Flask代码。

# file hello1.py
from flask import Flask

app = Flask(__name__)

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

app.run(debug=False, threaded=True, host="127.0.0.1", port=5000)

下面是一段使用模板文件的Flask代码。

# file hello2.py
from flask import Flask,render_template

app = Flask(__name__)

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

app.run(debug=False, threaded=True, host="127.0.0.1", port=5000)

hello.html文件

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

flask 直接运行

首先看hello1.py的测试结果

# 100 并发
siege -c 100 -r 10 -b http://127.0.0.1:5000

Transactions:               1000 hits
Availability:             100.00 %
Elapsed time:               1.17 secs
Data transferred:           0.01 MB
Response time:              0.11 secs
Transaction rate:         854.70 trans/sec
Throughput:             0.01 MB/sec
Concurrency:               92.12
Successful transactions:        1000
Failed transactions:               0
Longest transaction:            0.14
Shortest transaction:           0.01

# 200并发
# siege -c 200 -r 10 -b http://127.0.0.1:5000

Transactions:               1789 hits
Availability:              89.45 %
Elapsed time:               2.26 secs
Data transferred:           0.02 MB
Response time:              0.17 secs
Transaction rate:         791.59 trans/sec
Throughput:             0.01 MB/sec
Concurrency:              134.37
Successful transactions:        1789
Failed transactions:             211
Longest transaction:            2.09
Shortest transaction:           0.00

# 1000 并发
siege -c 1000 -r 10 -b http://127.0.0.1:5000

Transactions:              10000 hits
Availability:             100.00 %
Elapsed time:              16.29 secs
Data transferred:           0.12 MB
Response time:              0.00 secs
Transaction rate:         613.87 trans/sec
Throughput:             0.01 MB/sec
Concurrency:                2.13
Successful transactions:       10000
Failed transactions:               0
Longest transaction:            0.08
Shortest transaction:           0.00

不知道为什么200的时候可用率会有一个下降,但是从大趋势可以看出来,访问速率是一直再降的,1000并发的时候已经到613/s了。

在看看第二段代码

# 100 并发
siege -c 100 -r 10 -b http://127.0.0.1:5000/hello/libai

Transactions:               1000 hits
Availability:             100.00 %
Elapsed time:               1.26 secs
Data transferred:           0.07 MB
Response time:              0.12 secs
Transaction rate:         793.65 trans/sec
Throughput:             0.06 MB/sec
Concurrency:               93.97
Successful transactions:        1000
Failed transactions:               0
Longest transaction:            0.14
Shortest transaction:           0.04

# 200并发
siege -c 200 -r 10 -b http://127.0.0.1:5000/hello/libai
Transactions:               1837 hits
Availability:              91.85 %
Elapsed time:               2.52 secs
Data transferred:           0.13 MB
Response time:              0.18 secs
Transaction rate:         728.97 trans/sec
Throughput:             0.05 MB/sec
Concurrency:              134.77
Successful transactions:        1837
Failed transactions:             163
Longest transaction:            2.18
Shortest transaction:           0.00

# 1000 并发
siege -c 1000 -r 10 -b http://127.0.0.1:5000/hello/libai
Transactions:              10000 hits
Availability:             100.00 %
Elapsed time:              17.22 secs
Data transferred:           0.70 MB
Response time:              0.01 secs
Transaction rate:         580.72 trans/sec
Throughput:             0.04 MB/sec
Concurrency:                7.51
Successful transactions:       10000
Failed transactions:               0
Longest transaction:            0.09
Shortest transaction:           0.00

其他方式

参考Flask官方文档推荐的部署方式进行测试。

虽然轻便且易于使用,但是 Flask 的内建服务器不适用于生产 ,它也不能很好 的扩展。本文主要说明在生产环境下正确使用 Flask 的一些方法。

如果想要把 Flask 应用部署到这里没有列出的 WSGI 服务器,请查询其文档中关于 如何使用 WSGI 的部分,只要记住: Flask 应用对象实质上是一个 WSGI 应用。
下面从官方的方式中挑选几种进行性能测试。

Gunicorn

Gunicorn ‘Green Unicorn’ 是一个 UNIX 下的 WSGI HTTP 服务器,它是一个 移植自 Ruby 的 Unicorn 项目的 pre-fork worker 模型。它既支持 eventlet , 也支持 greenlet 。在 Gunicorn 上运行 Flask 应用非常简单:

gunicorn myproject:app

当然,为了使用gunicorn,我们首先得pip install gunicorn来进行gunicorn的安装。要使用gunicorn启动hello1.py,需要将里面的代码

app.run(debug=False, threaded=True, host="127.0.0.1", port=5000)

删掉。然后执行命令

# 其中 -w 为开启n个进程 -b 为绑定ip和端口
gunicorn hello1:app -w 4 -b 127.0.0.1:4000

gunicorn 默认使用同步阻塞的网络模型(-k sync),对于大并发的访问可能表现不够好, 它还支持其它更好的模式,比如:gevent或meinheld。所以,我们可以将阻塞模型替换为gevent。

# 其中 -w 为开启n个进程 -b 为绑定ip和端口 -k 为替换阻塞模型为gevent
gunicorn hello1:app -w 4 -b 127.0.0.1:4000  -k gevent

下面我分别测试1000并发10次访问的四种情况,1个进程、4个进程下gevnent和非gevent模型,看看结果。

在测试前,一定要设置ulimit的值大一些,否者会报Too many open files错误,我设置到了65535

ulimit 65535

gunicorn hello1:app -w 1 -b 127.0.0.1:4000
siege -c 1000 -r 10 -b http://127.0.0.1:4000
Transactions:              10000 hits
Availability:             100.00 %
Elapsed time:              15.21 secs
Data transferred:           0.12 MB
Response time:              0.00 secs
Transaction rate:         657.46 trans/sec
Throughput:             0.01 MB/sec
Concurrency:                0.85
Successful transactions:       10000
Failed transactions:               0
Longest transaction:            0.01
Shortest transaction:           0.00

可以看到,单进程比flask直接启动稍稍好一点。

gunicorn hello1:app -w 4 -b 127.0.0.1:4000
siege -c 1000 -r 10 -b http://127.0.0.1:4000

Transactions:              10000 hits
Availability:             100.00 %
Elapsed time:              15.19 secs
Data transferred:           0.12 MB
Response time:              0.00 secs
Transaction rate:         658.33 trans/sec
Throughput:             0.01 MB/sec
Concurrency:                0.75
Successful transactions:       10000
Failed transactions:               0
Longest transaction:            0.01
Shortest transaction:           0.00

# 使用gevent,记得 pip install gevent
gunicorn hello1:app -w 1 -b 127.0.0.1:4000  -k gevent
Transactions:              10000 hits
Availability:             100.00 %
Elapsed time:              15.20 secs
Data transferred:           0.12 MB
Response time:              0.00 secs
Transaction rate:         657.89 trans/sec
Throughput:             0.01 MB/sec
Concurrency:                1.33
Successful transactions:       10000
Failed transactions:               0
Longest transaction:            0.02
Shortest transaction:           0.00

gunicorn hello1:app -w 4 -b 127.0.0.1:4000  -k gevent

Transactions:              10000 hits
Availability:             100.00 %
Elapsed time:              15.51 secs
Data transferred:           0.12 MB
Response time:              0.00 secs
Transaction rate:         644.75 trans/sec
Throughput:             0.01 MB/sec
Concurrency:                1.06
Successful transactions:       10000
Failed transactions:               0
Longest transaction:            0.28
Shortest transaction:           0.00

可以看到,在并发数为1000的时候,使用gunicorn和genent并不明显,但是当我们修改并发数为100或200是进行测试

gunicorn hello1:app -w 1 -b 127.0.0.1:4000  -k gevent
siege -c 200 -r 10 -b http://127.0.0.1:4000
Transactions:               1991 hits
Availability:              99.55 %
Elapsed time:               1.62 secs
Data transferred:           0.02 MB
Response time:              0.14 secs
Transaction rate:        1229.01 trans/sec
Throughput:             0.02 MB/sec
Concurrency:              167.71
Successful transactions:        1991
Failed transactions:               9
Longest transaction:            0.34
Shortest transaction:           0.00

gunicorn hello1:app -w 4 -b 127.0.0.1:4000  -k gevent
siege -c 200 -r 10 -b http://127.0.0.1:4000
Transactions:               2000 hits
Availability:             100.00 %
Elapsed time:               0.71 secs
Data transferred:           0.02 MB
Response time:              0.04 secs
Transaction rate:        2816.90 trans/sec
Throughput:             0.03 MB/sec
Concurrency:              122.51
Successful transactions:        2000
Failed transactions:               0
Longest transaction:            0.17
Shortest transaction:           0.00

可以看到在4进程,使用gevent的时候已经达到了2816。

再测试一下200并发下hello2.py的效率。

gunicorn hello2:app -w 1 -b 127.0.0.1:4000  -k gevent
siege -c 200 -r 10 -b http://127.0.0.1:4000/hello/2
Transactions:               1998 hits
Availability:              99.90 %
Elapsed time:               1.72 secs
Data transferred:           0.13 MB
Response time:              0.14 secs
Transaction rate:        1161.63 trans/sec
Throughput:             0.08 MB/sec
Concurrency:              168.12
Successful transactions:        1998
Failed transactions:               2
Longest transaction:            0.35
Shortest transaction:           0.00

gunicorn hello2:app -w 4 -b 127.0.0.1:4000  -k gevent
siege -c 200 -r 10 -b http://127.0.0.1:4000/hello/2
Transactions:               2000 hits
Availability:             100.00 %
Elapsed time:               0.71 secs
Data transferred:           0.13 MB
Response time:              0.05 secs
Transaction rate:        2816.90 trans/sec
Throughput:             0.19 MB/sec
Concurrency:              128.59
Successful transactions:        2000
Failed transactions:               0
Longest transaction:            0.14
Shortest transaction:           0.0

可以看到和hello1.py的效率差不都,也达到了2800+,性能基本上算是提升了4倍。

uWSGI

官网链接uWSGI,安装请点开链接查看。Mac下直接使用brew install uWSGI就可以安装,安装好后,在网站目录下运行

uWSGI --http 127.0.0.1:4000 --module hello1:app

时间不够了,先写到这里

usWSGI和ngnix

uswgi 安装,使用pip install uswgi就可以了。

写配置文件uswgi.ini,这个文件uswgi的配置文件。

[uwsgi]
# 是否启用主进程
master = true
# 虚拟python环境的目录,即virtualenv生成虚拟环境的目录
home = venv
# wsgi启动文件
wsgi-file = manage.py
# wsgi启动文件中new出的app对象
callable = app
# 绑定端口
socket = 0.0.0.0:5000
# 开启几个进程
processes = 4
# 每个进程几个线程
threads = 2
# 允许的缓冲大小
buffer-size = 32768
# 接受的协议,这里要注意!!!!!!直接使用uwsgi启动时必须有这项,没有的话会造成服务可以启动,但是浏览器不能访问;在只是用nginx进行代理访问时,这项必须删除,否则nginx不能正常代理到uwsgi服务。
protocol=http

其中uwsgi的启动文件为manage.py,其中hello1为上面的hello1.py,注释掉其中的app.run(debug=False, threaded=True, host=”127.0.0.1″, port=5000)。

from flask import Flask
from hello1 import app

manager = Manager(app)

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

然后使用命令uswgi uswgi.ini启动程序,访问本地127.0.0.1:5000就可以看到helloworld了。然后就需要和nginx一起使用了,安装好nginx后,找到nginx的配置文件,如果使用的是apt或者yum安装nginx,则nginx的配置文件在/etc/nginx/nginx.conf中,为了不影响全局效果,这里修改/etc/nginx/sites-available/default文件,这个文件在/etc/nginx/nginx.conf中包含了,所以配置也是生效的。配置文件内容。

# nginx ip 访问次数限制,具体内容请查看参考6,7
limit_req_zone $binary_remote_addr zone=allips:100m rate=50r/s;  

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    # nginx ip 访问次数限制,具体内容请查看参考6,7
    limit_req   zone=allips  burst=20  nodelay; 
    root /var/www/html;
    # Add index.php to the list if you are using PHP
    index index.html index.htm index.nginx-debian.html;
    server_name _;
    # 静态文件代理,nginx的静态文件访问速度比其他容器快很多。
    location /themes/  {
        alias       /home/dc/CTFd_M/CTFd/themes/;
    }
    # uwsgi配置
    location / {
        include uwsgi_params;
        uwsgi_pass 127.0.0.1:5000; 
        # python virtualenv 路径
        uwsgi_param UWSGI_PYHOME /home/dc/CTFd_M/venv; 
        # 当前项目路径
        uwsgi_param UWSGI_CHDIR /home/dc/CTFd_M; 
        # 启动文件
        uwsgi_param UWSGI_SCRIPT manage:app; 
        # 超时
        uwsgi_read_timeout 100;
    }
}

然后启动nginx服务器,访问127.0.0.1就可以正常访问了,由于可能本机配置有问题,不能成功使用这种方式进行系统的访问,后面的对比结果是我新建虚拟机,Ubuntu Server 16.04,2核,2G内存的性能,并且这里访问的网页已经不是前面的hello1.py这种测试程序,而是一个完成的应用平台,可以从Throughput属性看到,已经达到了20+M/s的处理速度。

# 下面的两个测试均是物理机上访问虚机环境,虚机环境为Ubuntu Server 16.04
# 使用uswgi启动
siege -c 200 -r 10 -b http://192.168.2.151:5000/index.html
Transactions:              56681 hits
Availability:              99.90 %
Elapsed time:             163.48 secs
Data transferred:        3385.71 MB
Response time:              0.52 secs
Transaction rate:         346.72 trans/sec
Throughput:            20.71 MB/sec
Concurrency:              180.97
Successful transactions:       56681
Failed transactions:              59
Longest transaction:           32.23
Shortest transaction:           0.05

# 使用uswsgi和nginx做静态代理后
siege -c 200 -r 10 -b http://192.168.2.151/index.html

Transactions:              53708 hits
Availability:              99.73 %
Elapsed time:             122.13 secs
Data transferred:        3195.15 MB
Response time:              0.29 secs
Transaction rate:         439.76 trans/sec
Throughput:            26.16 MB/sec
Concurrency:              127.83
Successful transactions:       53708
Failed transactions:             148
Longest transaction:          103.07
Shortest transaction:           0.00

可以看到,uswsgi和nginx一起使用,能够提升一些效率,从346次/s提升到了439次/s。

flask多app应用(url进行处理和分发)

from flask import Flask
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple

app01 = Flask('app01')
app02 = Flask('app02')

@app01.route('/login')
def login():
    return 'app01.login'

@app02.route('/index')
def index():
    return 'app02.index'


dm = DispatcherMiddleware(app01,{
    '/app02':        app02,
})

if __name__ == '__main__':
    run_simple('localhost', 5000,dm)

我们可以创建多个app,并同时运行,访问app01时可以直接使用/login,访问app02时需要使用/app02/index

Python3 Flask+nginx+Gunicorn部署(下)

继续上一篇文章,在上一篇文章中,我们已经把gunicorn和nginx运行并且安装好了,在这一篇,我们需要进行nginx与gunicorn端口进行绑定

上一篇,我们已经将gunicorn运行起来了

gunicorn -w 4 -b 127.0.0.1:8000 入口文件名:app

nginx操作目录:

/usr/local/nginx/sbin

在/usr/local/nginx/sbin目录下可以进行nginx的重启、重载、停止与启动。

在这里我们进行通过nginx反向代理网站域名与服务器,这里可能说得有些绕口,

通俗点:
就是别人访问我们的网站,是通过nginx反向代理再把访问请求发送到flask服务器上的。

首先我们设置一下nginx的配置文件

cd /usr/local/nginx/conf
vi nginx.conf

然后打开配置文件:

    #gzip  on;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    server {
        listen       80;
        server_name  www.00reso.com;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

        location / {
           # root   html;
           # index  index.html index.htm;
            proxy_pass http://127.0.0.1:8000;
            access_log /home/admin/api_access.log;
            proxy_read_timeout 60;
            error_log  /home/admin/api_error.log;
        }

主要动的地方:

#gzip  on;
# 1代理的设置吧,这个都是这样设置的
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

# 2绑定的域名
server_name  www.00reso.com;

# 3 监听本服务器的端口,设置log,请求时间等
proxy_pass http://127.0.0.1:8000;
            access_log /home/admin/api_access.log;
            proxy_read_timeout 60;
            error_log  /home/admin/api_error.log;

然后进行保存。

接下来重载一下nginx文件,在/usr/local/nginx/sbin/nginx下
使用命令:

sudo nginx -t

如果看到

1 nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
2 nginx: configuration file /etc/nginx/nginx.conf test is successful

就是代表成功了。

重新启动下nginx

sudo service nginx restart

这里重启nginx的命令可能会行不通,这时候,你用
ps -e aux|grep nginx 把nginx的进程杀掉,

然后在 /usr/local/nginx/sbin目录下再次启动nginx

nginx

如果看到:

[admin@izbp11722iq94py8qx691sz conf]$ ps -e aux|grep nginx
root      2657  0.0  0.0  24952   784 ?        Ss   Jun25   0:00 nginx: master process nginx
nobody    2658  0.0  0.1  25388  1752 ?        S    Jun25   0:00 nginx: worker process
admin    13814  0.0  0.0 112664   968 pts/2    S+   20:59   0:00 grep --color=auto nginx

这个情况,就是启动nginx成功了,

验证

1 使用本地ping

curl localhost:80

如果出现flask index页面的内容,就表示nginx与gunicorn配置成功,
2 远程直接输入www.00reso.com 看是否能进行访问,如果没有出现失败的页面,则访问成功,如果出现失败的页面,则大多是防火墙或80端口关闭了,这里我们需要把防火墙打开。

编辑防火墙白名单

[root@localhost ~]# vim /etc/sysconfig/iptables
增加下面一行代码
-A INPUT -p tcp -m state -- state NEW -m tcp --dport 80 -j ACCEPT
保存退出,重启防火墙
[root@localhost ~]# service iptables restart

未分类

这样就可以进行访问了

目前自己还在探索中,现在在快马加鞭的优化中,

因为flask+nginx+gunicorn部署自己踩过了很多坑,但是从坑中踩过来,差不多就都懂了。还好没放弃~~

Python3 Flask+nginx+Gunicorn部署(上)

这次主要是记录flask在python3 环境结合nginx +gunicorn在服务器上进行项目的部署

(一)运行环境

  • 虚拟机centos7
  • python3 环境
  • nginx
  • gunicorn
  • virtualenv

难点:nginx gunicorn的安装配置

(二)nginx、gunicorn简介

Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等

gunicorn是一个python Wsgi http server,只支持在Unix系统上运行,来源于Ruby的unicorn项目。Gunicorn使用prefork master-worker模型(在gunicorn中,master被称为arbiter),能够与各种wsgi web框架协作。

(三)软件安装

(1)首先安装gunicorn

直接使用命令:pip install gunicorn

(2) 将gunicorn 加入到app.run()中,

这里我在路径为:/home/flaskproject/flaskweb 下新建一个myweb.py 作为入口函数
代码为:

from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
    return 'hello world'
if __name__ == '__main__':
    from werkzeug.contrib.fixers import ProxyFix
    app.wsgi_app = ProxyFix(app.wsgi_app)
    app.run(

未分类

(3)用命令启动gunicorn

在myweb.py路径下,一定要记住是当前路径下!!
方式一:

gunicorn myweb:app

未分类

python 虚拟环境的安装:

pip install virtualenv 

然后一顿骚操作:

mkdir flaskproject

cd flaskproject

virtualenv flaskprojectenv

然后进行激活:

source flaskprojectenv/bin/activate

正如下图一样的骚操作,之前我也是很少用虚拟环境的,现在感觉虚拟环境并没有那么神秘,因为以前是很拒绝,不会用,不过这次是会用了,

退出虚拟环境的命令是:deactivate(这里我只是说一下)

未分类

这时候我们本地服务器看一下是否运行起来(已经有hello world):

[root@localhost flaskproject]# curl http://127.0.0.1:8000
hello world[root@localhost flaskproject]# 

ctrl + c 停掉当前环境,我们使用第二种方式试一下
专门为处理高并发则要开多个进程和修改监听端口方式:

gunicorn -w 4 -b 127.0.0.1:8000 入口文件名:app

如下代码:

(flaskprojectenv) [admin@localhost flaskweb]$ gunicorn -w 4 -b 127.0.0.1:8000 myweb:app
[2018-05-28 10:57:11 -0400] [1813] [INFO] Starting gunicorn 19.8.1
[2018-05-28 10:57:11 -0400] [1813] [INFO] Listening at: http://127.0.0.1:8000 (1813)
[2018-05-28 10:57:11 -0400] [1813] [INFO] Using worker: sync
[2018-05-28 10:57:11 -0400] [1816] [INFO] Booting worker with pid: 1816
[2018-05-28 10:57:11 -0400] [1817] [INFO] Booting worker with pid: 1817
[2018-05-28 10:57:11 -0400] [1819] [INFO] Booting worker with pid: 1819
[2018-05-28 10:57:11 -0400] [1821] [INFO] Booting worker with pid: 1821

本地服务器已经运行了,但是我们远程并不能进行访问(win10访问虚拟机)

未分类

说明端口号没有打开,这时候我们需要把防火墙,端口号什么的都进行设置一下,可以看一下这篇博客:linux下nginx首次安装远程无法访问

主要是两个命令:

[root@localhost nginx-1.12.1] systemctl stop firewalld
[root@localhost nginx-1.12.1] systemctl stop iptalbes

接下来:我们进行nginx的配置安装与gunicorn的相结合并进行部署。

Python Web 腾讯云部署:flask+fabric+gunicorn+nginx+supervisor

最近看了《Flask Web开发–基于Python的Web应用开发实战》,还有廖雪峰老师的Python教程,前者是基于Web框架flask进行开发,后者是自己搭建了一个Web框架。Web框架致力于如何生成HTML代码,而Web服务器用于处理和响应HTTP请求。Web框架和Web服务器之间的通信,需要一套双方都遵守的接口协议。WSGI协议就是用来统一这两者的接口的。

为了部署一个完整的环境,本文将使用flask作为Web框架,Gunicorn作为WSGI容器,Nginx作为Web服务器,同时使用Supervisor进行进程管理,并使用Fabric实现自动化部署。

前期准备

申请一个腾讯云服务器Ubuntu Server 16.04.1 LTS 64位:

账户:ubuntu
密码:**********
内网ip:172.16.0.17
外网ip:139.199.184.183

使用《Flask Web开发》源码:

$ mkdir web
$ cd web
~/web$ git clone https://github.com/miguelgrinberg/flasky.git

设置并进入虚拟环境:

~/web$ cd flasky
~/web/flasky$ virtualenv venv
~/web/flasky$ source venv/bin/activate

安装对应的库:

(venv) ~/web/flasky$ cd requirements
(venv) ~/flasky/requirements$ pip3 install -r common.txt
(venv) ~/flasky/requirements$ pip3 install -r dev.txt
(venv) ~/flasky/requirements$ pip3 install -r heroku.txt
(venv) ~/flasky/requirements$ pip3 install -r prod.txt

创建数据库:

(venv) ~/flasky/requirements$ cd ..
(venv) ~/flasky/$ python3 manage.py deploy

Fabric

Fabric入门看这里https://www.jianshu.com/p/0be28f6c4607

新开一个终端,远程登陆服务器:

$ ssh [email protected]

在服务器端定义好部署结构:

ubuntu@VM $ mkdir web
ubuntu@VM $ mkdir web/flasky
ubuntu@VM $ mkdir web/log

即:

  • web/    Web App根目录
    • flasky/  存放python文件
    • log/    存放日志文件

我们使用Fabric将代码上传到服务器,首先,安装Fabric:

(venv) ~/web$ sudo apt-get install fabric

在web目录下创建fabfile.py(必须这个名字),写入:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os, re
from datetime import datetime

# 导入Fabric API:
from fabric.api import *

# 服务器登录用户名:
env.user = 'ubuntu'
# sudo用户为root:
env.sudo_user = 'root'
# 服务器地址,可以有多个,依次部署:
env.hosts = ['139.199.184.183']

_TAR_FILE = 'dist-flasky.tar.gz'

def build():
    includes = ['app', 'migrations', 'requirements', 'tests', 'venv', *.py', '*.sqlite']
    excludes = ['.*', '*.pyc', '*.pyo']
    local('rm -f dist/%s' % _TAR_FILE)
    with lcd(os.path.join(os.path.abspath('.'), 'flasky')):
        cmd = ['tar', '--dereference', '-czvf', '../dist/%s' % _TAR_FILE]
        cmd.extend(['--exclude='%s'' % ex for ex in excludes])
        cmd.extend(includes)
        local(' '.join(cmd))


_REMOTE_TMP_TAR = '/tmp/%s' % _TAR_FILE
_REMOTE_BASE_DIR = '/home/ubuntu/web/flasky'

def deploy():
    newdir = 'flask-%s' % datetime.now().strftime('%y-%m-%d_%H.%M.%S')
    # 删除已有的tar文件:
    run('rm -f %s' % _REMOTE_TMP_TAR)
    # 上传新的tar文件:
    put('dist/%s' % _TAR_FILE, _REMOTE_TMP_TAR)
    # 创建新目录:
    with cd(_REMOTE_BASE_DIR):
        sudo('mkdir %s' % newdir)
    # 解压到新目录:
    with cd('%s/%s' % (_REMOTE_BASE_DIR, newdir)):
        sudo('tar -xzvf %s' % _REMOTE_TMP_TAR)
    # 重置软链接:
    with cd(_REMOTE_BASE_DIR):
        sudo('rm -f flask')
        sudo('ln -s %s flask' % newdir)
        sudo('chown ubuntu:ubuntu flask')
        sudo('chown -R ubuntu:ubuntu %s' % newdir)
    # 重启Python服务和nginx服务器:
    #with settings(warn_only=True):
    #    sudo('supervisorctl stop flask')
    #    sudo('supervisorctl start flask')
    #    sudo('/etc/init.d/nginx reload')

使用fab命令将代码打包:

(venv) ~/web$ mkdir dist
(venv) ~/web$ fab build

将代码传上服务器:

(venv) ~/web$ fab deploy

Gunicorn部署

gunicorn入门看这里https://www.jianshu.com/p/260f18aa5462

代码上传给服务器后,可以直接运行manage.py文件,就可以访问网站了。可是实际情景中需要处理高并发访问的情况,我们使用Gunicorn来解决这个问题。

在服务器上进入flask所在目录,激活虚拟环境,并安装gunicorn:

ubuntu@VM ~/web/flasky/flask$ source venv/bin/activate
(venv) ubuntu@VM ~/web/flasky/flask$  sudo apt-get install gunicorn

使用gunicorn运行web应用(支持4个并发请求):

(venv) ubuntu@VM ~/web/flasky/flask$ gunicorn -w 4 -b 172.16.0.17:5000 manage:app

其中172.16.0.17是服务器内网ip,这样设置后就可以通过外网ip访问网站了。在浏览器地址栏中输入139.199.184.183:5000,就可以看到网站了。

Nginx

Nginx入门看这里

使用gunicorn的话,需要将端口暴露给外界,这不利于服务器的安全问题。Nginx是一个高性能的HTTP和反向代理服务器,通过配置一个代理服务器,可以实现反向代理的功能。所谓反向代理,是指一个请求,从互联网过来,先进入代理服务器,再由代理服务器转发给局域网的目标服务器。

先安装nginx:

(venv) ubuntu@VM ~$ sudo apt-get install nginx

设置反向代理服务器:在服务器,进入目录/etc/nginx/sites-available/,在目录下的default文件写入一个server。

首先,先备份default文件:

$ cd /etc/nginx/sites-available/
$ sudo cp default default-backup

在default文件中写入server:

server {
    listen 80;
    root /home/ubuntu/web/flasky/flask/;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Forward-For $proxy_add_x_forwarded_for;
    }
}

配置完成后,重启nginx:

$ sudo nginx -s reload

此时,该反向代理服务器就开始监听80端口。80端口是为HTTP开放的端口,浏览网页服务默认的端口号都是80 (8080端口跟80端口一样)。只要访问服务器,代理服务器就会将请求转发到127.0.0.1:5000.

回到目录/home/ubuntu/web/flasky/flask/下,再次使用gunicorn运行web app:

(venv) ubuntu@VM ~/web/flasky/flask$ gunicorn -w 4 -b 127.0.0.1:5000 manage:app

用的ip地址是127.0.0.1

而外界要访问网站只需要在浏览器地址栏输入139.199.184.183,使用默认端口80。这样web app实际使用的5000就不用暴露了。

Supervisor

supervisor入门看这里https://www.jianshu.com/p/0226b7c59ae2

如果你需要进程一直执行,若该进程因各种原因中断,也会自动重启的话,supervisor是一个很好的选择。

supervisor管理进程,是通过fork/exec的方式将这些被管理的进程当作supervisor的子进程来启动,所以我们只需要将要管理进程的可执行文件的路径添加到supervisor的配置文件中就好了。此时被管理进程被视为supervisor的子进程,若该子进程异常终端,则父进程可以准确的获取子进程异常终端的信息,通过在配置文件中设置autostart=true,可以实现对异常中断的子进程的自动重启。

安装supervisor:

(venv) ubuntu@VM ~$ sudo apt-get install supervisor

在目录/etc/supervisor/conf.d/下创建flask.conf,并加入:

;/etc/supervisor/conf.d/flask.conf

[program:flask]

command     = /home/ubuntu/.local/bin/gunicorn -w 4 -b 127.0.0.1:5000 manage:app
;这里/home/ubuntu/.local/bin/是我服务器上gunicorn的安装目录
directory   = /home/ubuntu/web/flasky/flask
user        = ubuntu
startsecs   = 3
autostart   = true

redirect_stderr         = true
stdout_logfile_maxbytes = 50MB
stdout_logfile_backups  = 10
stdout_logfile          = /home/ubuntu/web/flasky/log/app.log

配置完后,进入目录/home/ubuntu/web/flasky/, 创建log目录:

(venv) ubuntu@VM ~/web/flasky$ mkdir log

启动supervisor:

(venv) ubuntu@VM /etc/supervisor$ sudo supervisord -c supervisor.conf                 

执行服务(运行app.py):

(venv) ubuntu@VM $ sudo supervisorctl start flask

此时,进程flask运行,执行command后边的语句,即/home/ubuntu/.local/bin/gunicorn -w 4 -b 127.0.0.1:5000 manage:app。

在浏览器地址栏输入139.199.184.183,使用默认端口80,即可访问网站。

如果supervisor遇到错误,可以在/var/log/supervisor/supervisord.log中查看日志;
如果app运行出现问题,可以在 /home/ubuntu/web/flasky/log/app.log中查看日志。

自动化部署

为了实现自动化部署,将上边fabfile.py文件的最后四个语句的注释取消,就能在将修改后的代码传上服务器后,用supervisor自动重载配置并重启flask进程。之后便可以直接访问网站。
因此,每次更改代码后,只需要执行以下两个语句,无需登陆服务器,便可以启动网站:

$ fab build
$ fab deploy

gunicorn配置文件

gunicorn的配置除了可以在命令行中设置,也可以使用配置文件实现更复杂的配置:

$ gunicorn -c gunicorn.py manage:app

gunicorn.py文件如下:

# gunicorn.py
import logging
import logging.handlers
from logging.handlers import WatchedFileHandler
import os
import multiprocessing
bind = '127.0.0.1:5000'       #绑定ip和端口号
backlog = 512                 #监听队列
chdir = '/home/test/server/bin'   #gunicorn要切换到的目的工作目录
timeout = 30      #超时
worker_class = 'gevent' #使用gevent模式,还可以使用sync 模式,默认的是sync模式
workers = multiprocessing.cpu_count() * 2 + 1    #进程数

threads = 2 #指定每个进程开启的线程数

loglevel = 'info' #日志级别,这个日志级别指的是错误日志的级别,而访问日志的级别无法设置
access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s %(f)s" "%(a)s"'     #设置gunicorn访问日志格式,错误日志无法设置
"""
其每个选项的含义如下
        h           remote address
        l           '-'
        u           currently '-', may be user name in future releases
        t           date of the request
        r           status line (e.g. ``GET / HTTP/1.1``)
        s           status
        b           response length or '-'
        f           referer
        a           user agent
        T           request time in seconds
        D           request time in microseconds
        L           request time in decimal seconds
        p           process ID
        {Header}i   request header
        {Header}o   response header
"""
accesslog = "/home/ubutnu/web/flasky/log/gunicorn_access.log"      #访问日志文件
errorlog = "/home/ubutnu/web/flasky/log/gunicorn_error.log"        #错误日志文件

在本地将gunicorn.py放在/home/ubutnu/web/flasky/flask/目录下,在服务器端将/etc/supervisor/conf.d/flask.conf中的command改为:

command     = /home/ubuntu/.local/bin/gunicorn -c /home/ubutnu/web/flasky/flask/gunicorn.py manage:app

然后,在本地执行:

$ fab build
$ fab deploy

便可以访问网站了。

未分类