Flask 入門(第二篇)

1. 數據庫

Flask 沒有限定使用哪一種數據庫,無論是 SQL 仍是 NoSQL。若是你不想本身編寫負責的 SQL語句的話,你也可使用 ORM,經過 SQLALchemy 便可實現。css

1.1 SQLALchemy 使用

安裝html

pip install flask-sqlalchemy

# 要鏈接mysql數據庫,仍須要安裝flask-mysqldb
pip install flask-mysqldb

# 或者你還還需 pymyql
pip3 install pymyql

相關網站:python

Flask-SQLAlchemy 數據庫 URLmysql

# hostname:主機、database:數據庫,username、password 數據庫用戶名和密碼
# mysql
mysql://username:password@hostname/database

# postgres
postgresql://username:password@hostname/database
        
# SQLite  unix
sqlite:////absolute/path/to/database
    
# sqlite  windows
sqlite:///c:/absolute/path/to/database

鏈接 MySQLjquery

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

# 設置鏈接數據庫的URL
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:xxx@localhost/t1'

#設置每次請求結束後會自動提交數據庫中的改動
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SQLALCHEMY_ECHO'] = True    # 查詢時會顯示原始SQL語句
db = SQLAlchemy(app)

經常使用 sqlalchemy 列類型nginx

經常使用 sqlalchemy 列選項web

經常使用 SQLAlchemy 關係選項sql

1.2 基本操做

無論是插入、修改、仍是刪除操做,均由數據庫會話管理,會話用 db.session 表示。數據庫會話是爲了保證數據的一致性,避免因部分更新致使數據不一致。提交操做把會話對象所有寫入數據庫,若是寫入過程發生錯誤,整個會話都會失效。數據庫會話也能夠回滾,經過 db.session.rollback() 方法,實現會話提交數據前的狀態。shell

建立、刪除、插入數據庫

# 建立表
# 若是表存在,則幫助從新建立或更新
# 若是要修改模型後要把應用到現有數據庫中,這個操做不行,更新現有數據庫的粗暴方式是先刪除再建立
db.create_all()

# 刪除表
db.drop_all()

# 插入一條數據
ro1 = Role(name='admin')
db.session.add(ro1)
db.session.commit()

# 一次插入多條數據
us1 = User(name='wang',email='xxx@163.com',pswd='123456',role_id=ro1.id)
us2 = User(name='zhang',email='xxx@189.com',pswd='201512',role_id=ro2.id)
db.session.add_all([us1,us2])
db.session.commit()

查詢

# 查詢:filter_by精確查詢
User.query.filter_by(name='wang').all()

# first()返回查詢到的第一個對象
User.query.first()

# all()返回查詢到的全部對象
User.query.all()

# filter模糊查詢,返回名字結尾字符爲g的全部數據。
User.query.filter(User.name.endswith('g')).all()

# get(),參數爲主鍵,若是主鍵不存在沒有返回內容
User.query.get()

# 邏輯非,返回名字不等於wang的全部數據。
User.query.filter(User.name!='wang').all()

# 邏輯與,須要導入and,返回and()條件知足的全部數據。
from sqlalchemy import and_
User.query.filter(and_(User.name!='wang',User.email.endswith('163.com'))).all()

# 邏輯或,須要導入or_
from sqlalchemy import or_
User.query.filter(or_(User.name!='wang',User.email.endswith('163.com'))).all()

# not_ 至關於取反
from sqlalchemy import not_
User.query.filter(not_(User.name=='chen')).all()

# 查詢數據後刪除
user = User.query.first()
db.session.delete(user)
db.session.commit()
User.query.all()

# 關聯查詢示例:角色和用戶的關係是一對多的關係,一個角色能夠有多個用戶,一個用戶只能屬於一個角色。
查詢角色的全部用戶:
#查詢roles表id爲1的角色
ro1 = Role.query.get(1)
#查詢該角色的全部用戶
ro1.us

# 查詢用戶所屬角色:
#查詢users表id爲3的用戶
us1 = User.query.get(3)
#查詢用戶屬於什麼角色
us1.role

更新

# 更新數據
user = User.query.first()
user.name = 'dong'
db.session.commit()
User.query.first()

# 使用update
User.query.filter_by(name='zhang').update({'name':'li'})

查看原生 SQL

>>> str(User.query.filter_by(role=user_role)
... )
'SELECT users.id AS users_id, users.username AS users_username, users.role_id AS users_role_id \nFROM users \nWHERE %(
param_1)s = users.role_id'

經常使用 sqlAlchemy 查詢過濾器

經常使用 SQLAlchemy 查詢執行函數


示例:在視圖函數中定義模型類

from flask import Flask
from flask_sqlalchemy import SQLAlchemy


app = Flask(__name__)

#設置鏈接數據庫的URL
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:mysql@127.0.0.1:3306/Flask_test'

#設置每次請求結束後會自動提交數據庫中的改動
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
# 查詢時會顯示原始SQL語句
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)

class Role(db.Model):
    # 定義表名
    __tablename__ = 'roles'
    # 定義列對象
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True)
    us = db.relationship('User', backref='role')

    #repr()方法顯示一個可讀字符串
    def __repr__(self):
        return 'Role:%s'% self.name

class User(db.Model):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(64), unique=True, index=True)
    email = db.Column(db.String(64),unique=True)
    pswd = db.Column(db.String(64))
    role_id = db.Column(db.Integer, db.ForeignKey('roles.id'))

    def __repr__(self):
        return 'User:%s'%self.name
if __name__ == '__main__':
    db.drop_all()
    db.create_all()
    ro1 = Role(name='admin')
    ro2 = Role(name='user')
    db.session.add_all([ro1,ro2])
    db.session.commit()
    us1 = User(name='rose',email='rose@163.com',pswd='123',role_id=ro1.id)
    us2 = User(name='lila',email='lila@189.com',pswd='456',role_id=ro2.id)
    us3 = User(name='john',email='john@126.com',pswd='789',role_id=ro2.id)
    db.session.add_all([us1,us2,us3])
    db.session.commit()
    app.run(debug=True)

1.3 使用 Flask-Migrate 實現數據庫遷移

開發時,須要修改數據庫模型,修改以後要更新數據庫。表不存在時就建立,存在時,更新表的惟一方式是先刪除後建立,可是會丟失數據。

更新表的最好方式是使用 數據庫遷移框架,Flask 使用的是 Alembic (https://alembic.readthedocs.org/en/latest/index.html),集成在 Flask-Script 中:

# pip3 install flask-migrate
from flask_migrate import Migrate, MigrateCommand

app = Flask(__name__)     
manager = Manager(app)

app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:xxxx@localhost/t1'
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

db = SQLAlchemy(app)
migrate = Migrate(app, db)      # 第一個參數是Flask的實例,第二個參數是Sqlalchemy數據庫實例
manager.add_command('db', MigrateCommand)  # 這條語句在flask-Script中添加一個db命令

初始化數據庫

在維護數據庫遷移以前,使用 init 子命令建立遷移倉庫:

此時你會看到項目中生成一個 migrations 的文件夾,全部遷移腳本都存放其中。

建立遷移腳本

Alembic 中,遷移腳本中有兩個函數:upgrade() he downgrade():

  • upgrade():把遷移中改動應用到數據庫中
  • upgrade():將改動刪除

revision 命令手動建立 Alembic 遷移,migrate 自動建立(自動建立可能會有錯誤,必定要檢查下),自動建立遷移腳本:

發現出現:too many arguments 錯誤,用 python3 s1.py db migrate --help 命令查看 migrate 命令後面到底能夠接什麼參數,發現 -m 是能夠的,但爲何會報錯呢 ?

嘗試不輸入 'initial migration' 那麼長,而是隨便輸入了一個 's',發現居然成功了:


數據回滾

若是要回滾,那麼首先要指定版本號,查看版本號:

# 因爲版本號是隨機字符串,爲避免出錯,建議先使用 history 命令查看歷史版本的具體版本號,而後複製具體版本號執行回退。
python database.py db history

# 回滾
python database.py db downgrade 版本號

1.4 示例

在這裏咱們將建立一個簡單的可以添加書籍的小程序,整個項目結構以下圖所示:

一、定義模型,models.py

from flask_test.s4 import db


class Author(db.Model):
    """定義模型類-做者"""
    __tablename__ = 'author'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(32), unique=True)
    email = db.Column(db.String(64))
    au_book = db.relationship('Book', backref='author', lazy='dynamic')

    def __str__(self):
        return 'Author:%s' % self.name


class Book(db.Model):
    """定義模型類-書名"""
    __tablename__ = 'books'
    id = db.Column(db.Integer, primary_key=True)
    info = db.Column(db.String(32),unique=True)
    leader = db.Column(db.String(32))
    au_book = db.Column(db.Integer, db.ForeignKey('author.id'))

    def __str__(self):
        return 'Book:%s' % self.info

二、定義 Form 表單,my_forms.py

from flask_wtf import FlaskForm
from wtforms.validators import DataRequired
from wtforms import StringField, SubmitField


class Append(FlaskForm):
    """添加信息"""
    auth_info = StringField(label='做者', validators=[DataRequired()])
    book_info = StringField(label='書名', validators=[DataRequired()])
    submit = SubmitField(u'添加')

三、主程序,app.py

from flask import Flask, render_template, redirect, url_for, request
from flask_test.my_extend import models
from flask_test.my_extend.my_forms import Append
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

manager = Manager(app)

#設置鏈接數據
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:password@localhost/t2'

#設置每次請求結束後會自動提交數據庫中的改動
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True

#設置成 True,SQLAlchemy 將會追蹤對象的修改而且發送信號。這須要額外的內存, 若是沒必要要的能夠禁用它。
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SECRET_KEY'] = 's'

#實例化SQLAlchemy對象
db = SQLAlchemy(app)

@app.route('/', methods=['GET', 'POST'])
def index():
    # 查詢全部做者和書名信息
    author_list = models.Author.query.all()
    book_list = models.Book.query.all()
    return render_template('info.html', author_list=author_list, book_list=book_list)


@app.route('/add_book', methods=['GET', 'POST'])
def add_book():
    """添加書籍"""

    form = Append()  # 建立表單對象
    if form.validate_on_submit():
        # 獲取表單輸入數據
        wtf_auth = form.auth_info.data
        wtf_book = form.book_info.data
        print(wtf_book, wtf_auth)

        # 將表單輸入的數據存入到模型中
        db_auth = models.Author(name=wtf_auth)
        db_book = models.Book(info=wtf_book)

        # 提交會話
        db.session.add_all([db_auth, db_book])
        db.session.commit()

        return redirect(url_for('index'))
    else:
        if request.method == 'GET':
            return render_template('add_book.html', form=form)


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

四、info.html

```python
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.css') }}">
</head>
<body>
   <div class="col-md-6" style="margin-left: 100px">
        <h1>玄幻系列</h1>
        <button class="btn pull-right" style="margin-bottom: 15px;"><a href="/add_book">添加</a> </button>
        <table class="table table-striped table-bordered">
            <thead>
                <tr>
                    <th>做者</th>
                    <th>書名</th>
                </tr>
            </thead>

            <tbody>
                    <tr>
                        {% for author in author_list %}
                            <td>{{ author.name }}</td>
                        {% endfor %}

                        {% for book in book_list %}
                            <td>{{ book.info }}</td>
                        {% endfor %}
                    </tr>
            </tbody>
        </table>
   </div>
    <script src="{{ url_for('static', filename='js/jquery-3.1.1.js') }}"></script>
    <script src="{{ url_for('static', filename='js/bootstrap.js') }}"></script>
</body>
</html>

五、add_book.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/add_book" method="post">
        {{ form.csrf_token }}
        <p>{{ form.auth_info.label }} {{ form.auth_info }}</p>
        <p>{{ form.book_info.label }} {{ form.book_info }}</p>
        <p>{{ form.submit }}</p>
    </form>
</body>
</html>

效果圖以下:

1.5 踩坑

flask 不能進入 shell

flask 進入 shell,命令:python 程序文件.py shell

要安裝:pip3 install flask-script,程序中使用:

from flask_script import Manager
from flask import Flask

app = Flask(__name__)
manager = Manager(app)
 
if __name__ == '__main__':
    manager.run()

flask 在鏈接MySQL出現ModuleNotFoundError: No module named 'MySQLdb'錯誤

解決辦法:只要在配置 SQLALCHEMY_DATABASE_URI時,加上一個 pymysql 就能夠了:

# 記得安裝 pymysql
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:dzd123@localhost/你的數據庫名'

參考文章:https://blog.csdn.net/qq_25046261/article/details/78991442


進入 shell 時出現錯誤:SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '

解決辦法:

# 修改 flask_sqlalchemy 的 __init__.py 
# 查找方法:在程序中導入 flask_sqlalchemy,按住 Ctrl 鍵,鼠標點擊 flask_sqlalchemy,就會定位到 __init__.py 中,而後 Ctrl + F 查找 track_modifications 便可
# 在init.py裏面有 init_app方法,修改下面的一行

track_modifications = app.config.setdefault('SQLALCHEMY_TRACK_MODIFICATIONS', True)

參考文章:https://blog.csdn.net/qq_25730711/article/details/53690687

2. 郵件拓展

Flask 的擴展包 Flask-Mail 經過包裝了 Python內置的 smtplib 包,能夠用在Flask程序中發送郵件。

Flask-Mail鏈接到簡單郵件協議(Simple Mail Transfer Protocol,SMTP)服務器,並把郵件交給服務器發送。

示例:下面以 qq 郵箱爲示例演示下如何在 Flask 程序中發送郵件

一、開啓 IMAP/SMTP 服務,生成受權碼:

二、app.py

pip3 install flask_mail
```

```python
from flask import Flask
from flask_mail import Mail, Message

app = Flask(__name__)
# 配置郵件:服務器/端口/傳輸層安全協議/郵箱名/密碼
app.config.update(
    DEBUG = True,
    MAIL_SERVER='smtp.qq.com',
    MAIL_PROT=465,
    MAIL_USE_TLS=True,
    MAIL_USERNAME='98xxx616@qq.com',
    MAIL_PASSWORD='vnpyuevrhqyybccd',   # 受權碼
)

mail = Mail(app)

@app.route('/')
def index():
    # sender 發送方,recipients 接收方列表,This is a test 爲郵件標題
    msg = Message("This is a test ", sender='9825xx616@qq.com', recipients=['146xx0@qq.com', 'junxx@outlook.com'])
    #郵件內容
    msg.body = "Flask 郵件測試"
    #發送郵件
    mail.send(msg)
    print("郵件發送中...")
    return "發生成功!"

if __name__ == "__main__":
    app.run()

運行程序,訪問:http://127.0.0.1:5000/

3. 藍圖 Blueprint

一個項目有不少的頁面(視圖),也就意味着會有不少路由,若是把全部代碼寫在一個文件中(啓動文件、路由、配置等等),顯然程序變得模塊化,耦合性太差,後期維護過於困難。

採用 Python 模塊導入的方式將程序拆分紅多個文件,雖然下降程序耦合,可是也不能解決路由映射問題。Flask 提倡使用藍圖解決此類問題。那麼什麼是藍圖呢?

藍圖是用於實現單個應用的視圖、模板、靜態文件的集合,是模塊化處理的類。簡單來講,藍圖就是一個存儲操做路由映射方法的容器,主要用來實現客戶端請求和URL相互關聯的功能。 在Flask中,使用藍圖能夠幫助咱們實現模塊化應用的功能。

藍圖使用

1、建立藍圖對象。

#Blueprint必須指定兩個參數,admin表示藍圖的名稱,__name__表示藍圖所在模塊
admin = Blueprint('admin',__name__)
2、註冊藍圖路由。

@admin.route('/')
def admin_index():
    return 'admin_index'
3、在程序實例中註冊該藍圖。

app.register_blueprint(admin,url_prefix='/admin')

示例:

三個程序文件:login.py、user.py、manage.py,至關於三個 app,其中 manage.py 做爲主要程序文件(啓動問)。

實現目標:在主程序與子程序中定義相同的路由 URL,可以同時訪問,互不影響。

項目結構:

user.py

from flask import Blueprint,render_template

users = Blueprint('user',__name__)

@users.route('/user')
def user():
    return render_template('user.html')

login.py

from flask import Blueprint,render_template
#建立藍圖
logins = Blueprint('login',__name__)

@logins.route('/login')
def login():
    return render_template('login.html')

manage.py

from flask import Flask
#導入藍圖對象
from login import logins
from user import users

app = Flask(__name__)

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


@app.route('/login')
def sss():
    return 'login'

#註冊藍圖,第一個參數logins是藍圖對象,url_prefix參數默認值是根路由,若是指定,會在藍圖註冊的路由url中添加前綴。
app.register_blueprint(logins, url_prefix='/ll')
app.register_blueprint(users, url_prefix='')

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

在藍圖 login.pylogin() 函數的路由咱們使用了:/login,主程序 manage.pysss() 函數也使用了 /login

若是藍圖 logins 沒有指定 url_prefix='' 路由前綴,那麼訪問:http://127.0.0.1:5000/login 獲得的會是函數 sss() 所渲染的值。

那麼訪問藍圖 logins,應該使用:http://127.0.0.1:5000/ll/login


動態路由

login.py

@logins.route('/login/<id>')
def login(id):
    print(id)
    return render_template('login.html')

訪問:http://127.0.0.1:5000/ll/login/2

4 單元測試

4.1 什麼是單元測試

Web程序開發過程通常包括如下幾個階段:需求分析,設計階段,實現階段,測試階段。其中測試階段經過人工或自動來運行測試某個系統的功能。目的是檢驗其是否知足需求,並得出特定的結果,以達到弄清楚預期結果和實際結果之間的差異的最終目的。

測試的分類:
測試從軟件開發過程能夠分爲:單元測試、集成測試、系統測試等。在衆多的測試中,與程序開發人員最密切的就是單元測試,由於單元測試是由開發人員進行的,而其餘測試都由專業的測試人員來完成。因此咱們主要學習單元測試。

什麼是單元測試?
程序開發過程當中,寫代碼是爲了實現需求。當咱們的代碼經過了編譯,只是說明它的語法正確,功能可否實現則不能保證。 所以,當咱們的某些功能代碼完成後,爲了檢驗其是否知足程序的需求。能夠經過編寫測試代碼,模擬程序運行的過程,檢驗功能代碼是否符合預期。

單元測試就是開發者編寫一小段代碼,檢驗目標代碼的功能是否符合預期。一般狀況下,單元測試主要面向一些功能單一的模塊進行。在Web開發過程當中,單元測試實際上就是一些「斷言」(assert)代碼。

斷言就是判斷一個函數或對象的一個方法所產生的結果是否符合你指望的那個結果。 python中assert斷言是聲明布爾值爲真的斷定,若是表達式爲假會發生異常。單元測試中,通常使用assert來斷言結果。

斷言使用:

>>> a = [1, 2, 3, 4]
>>> b = 2
>>> assert b in a   # 爲真則經過

>>> assert b not in a       # 爲假則報錯
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError

# 自定義異常
>>> assert b not in a, 'False'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: False

經常使用的斷言方法:

assertEqual         # 若是兩個值相等,則pass
assertNotEqual      # 若是兩個值不相等,則pass
assertTrue          # 判斷bool值爲True,則pass
assertFalse         # 判斷bool值爲False,則pass
assertIsNone        # 不存在,則pass
assertIsNotNone     # 存在,則pass

簡單測試用例:

>>> def add(x, y):
...     return x + y
...
>>> assert add(2, 3) == 5

>>> assert add(2, 3) == 6, 'False'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError: False

4.2 單元測試用例

單元測試的基本寫法:

  • 定義一個類,繼承自unittest.TestCase
  • 在測試類中,定義兩個測試方法
  • 在測試類中,編寫測試代碼

測試基本框架:

import unittest


class TestClass(unittest.TestCase):
    def setUp(self):
        """該方法會首先執行,方法名爲固定寫法"""
        pass

    def tearDown(self):
        """該方法會在測試代碼執行完後執行,方法名爲固定寫法"""
        pass

    def test_app_exists(self):
        """測試代碼"""
        pass

setUp() 方法的代碼建立了一個新的測試客戶端而且初始化了一個新的數據庫。這個函數將會在每次獨立的測試函數運行以前運行。要在測試以後刪除這個數據庫,咱們在 tearDown() 函數當中關閉這個文件,並將它從文件系統中刪除。同時,在初始化的時候 TESTING 配置標誌被激活,這將會使得處理請求時的錯誤捕捉失效,以便於您在進行對應用發出請求的測試時得到更好的錯誤反饋。


一、測試響應內容是否與預期一致

def test_index(self):
       resp = self.client.get('/')
       print(resp.data)

       self.assertEqual(resp.data, b'Hello World!')

二、測試登錄和登出

咱們應用的大部分功能只容許具備管理員資格的用戶訪問。因此咱們須要一種方法來幫助咱們的測試客戶端登錄和登出。爲此,咱們向登錄和登出頁面發送一些請求,這些請求都攜帶了表單數據(用戶名和密碼),由於登錄和登出頁面都會重定向,咱們將客戶端設置爲 follow_redirects

def login(self, username, password):
    return self.app.post('/login', data=dict(
        username=username,
        password=password
    ), follow_redirects=True)

def logout(self):
    return self.app.get('/logout', follow_redirects=True)

def test_login_logout(self):
    rv = self.login('admin', 'default')
    assert 'You were logged in' in rv.data
    rv = self.logout()
    assert 'You were logged out' in rv.data
    rv = self.login('adminx', 'default')
    assert 'Invalid username' in rv.data
    rv = self.login('admin', 'defaultx')
    assert 'Invalid password' in rv.data

三、數據庫測試

import unittest
from author_book import *

#自定義測試類,setUp方法和tearDown方法會分別在測試先後執行。以test_開頭的函數就是具體的測試代碼。

class DatabaseTest(unittest.TestCase):
    def setUp(self):
        app.config['TESTING'] = True
        app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://root:mysql@localhost/test0'
        self.app = app
        db.create_all()

    def tearDown(self):
        db.session.remove()
        db.drop_all()

    #測試代碼
    def test_append_data(self):
        au = Author(name='itcast')
        bk = Book(info='python')
        db.session.add_all([au,bk])
        db.session.commit()
        author = Author.query.filter_by(name='itcast').first()
        book = Book.query.filter_by(info='python').first()
        #斷言數據存在
        self.assertIsNotNone(author)
        self.assertIsNotNone(book)

5. 部署

一直以來咱們都是使用的 flask 自帶的服務器,完成了web服務的啓動。在生產環境中,flask 自帶的服務器,沒法知足性能要求。

這裏採用 Gunicornwsgi容器,來部署 flask 程序。Gunicorn(綠色獨角獸)是一個Python WSGIHTTP服務器。從Ruby的獨角獸(Unicorn )項目移植。該 Gunicorn 服務器與各類Web框架兼容,實現很是簡單,輕量級的資源消耗。Gunicorn直接用命令啓動,不須要編寫配置文件,相對 uWSGI要容易不少。

幾個概念:

  • WSGI:全稱是Web Server Gateway Interface(web服務器網關接口),它是一種規範,它是web服務器和 web 應用程序之間的接口。它的做用就像是橋樑,鏈接在 ` 服務器和web`應用框架之間。
  • uwsgi:是一種傳輸協議,用於定義傳輸信息的類型。
  • uWSGI:是實現了uwsgi協議WSGIweb服務器。

在這裏咱們採用 nginx + gunicorn + flask 方式部署 flask,你也能夠採用別的方式,好比:Django(uWSGI+Nginx)等等,只不過 gunicorn 相對來講更簡單操做一些。


正向代理及反向代理

代理種類 說明 特色
正向代理 請求通過代理服務器從局域網發出,而後到達互聯網上的服務器 服務端並不知道真正的客戶端是誰 客戶端
反向代理 請求從互聯網發出,先進入代理服務器,再轉發給局域網內的服務器 客戶端並不知道真正的服務端是誰 服務端

使用 Nginx 的優點:

  • 實現分流、轉發、負載均衡
  • 分擔服務器的壓力
  • 部署簡單,內存消耗少,成本低
  • 既能夠作正向代理,也能夠作反向代理

5.1 gunicorn

經常使用命令

# 安裝
$ pip3 install gunicorn

# 查看命令行選項: 
$ gunicorn -h

# 直接運行,默認啓動的 127.0.0.1::8000
gunicorn 運行文件名稱:Flask程序實例名
# 示例
gunicorn s2:app
    
# 指定進程和端口號: -w: 表示進程(worker)。 -b:表示綁定ip地址和端口號(bind)。
$ gunicorn -w 4 -b 127.0.0.1:5001 運行文件名稱:Flask程序實例名

踩坑

運行 gunicorn 時發生以下錯誤:

# 錯誤
gunicorn.errors.HaltSever:<HaltServer 'Worker faild to boot.' 3

使用 debug 模式查看更多信息--log-level=debug,也能夠在後邊加上參數 –preload,即可看到詳細的報錯信息:

gunicorn -w 4 -b 192.168.21.128:5001 s2:app  --log-level=debug

發現是 import error,錯誤提醒說 s2.py 不是個包文件,這是 Python import 的問題。

解決方法

一、給這個執行文件添加環境變量

# 添加環境變量
vim /etc/profile
export PATH="$PATH:/home/hj/桌面/fk"      # 將路徑設置你的執行文件路徑(在 profile 文件最後添加)

# 關閉保存 profile 文件
source /etc/profile

# 測試下有沒有添加成功
echo $PATH

二、將這個執行文件變爲包文件

# 在 fk 文件夾中新建一個 __init__.py 文件便可
vim __init__.py

hj@hj:~/桌面/fk$ ls
error.log  __init__.py  __pycache__  s  s2.py

三、再運行:gunicorn -w 4 -b 192.168.21.128:5001 s2:app,發現成功了:

參考文章:gunicorn啓動報錯gunicorn.errors.HaltServer


5.2 Nginx

安裝 Nginx

$ sudo apt-get install nginx

Nginx 經常使用命令

Ubuntu 中你能夠像任何其餘 systemd 單位同樣管理 Nginx 服務:

# 中止Nginx服務
sudo systemctl stop nginx

# 再次啓動
sudo systemctl start nginx

# 從新啓動Nginx服務:
sudo systemctl restart nginx

# 在進行一些配置更改後從新加載 Nginx 服務:
$sudo systemctl reload nginx

# 若是你想禁用Nginx服務在啓動時啓動:
$sudo systemctl disable nginx

# 並從新啓用它:
$sudo systemctl enable nginx

# 查看
ps -ef | grep nginx

Nginx 配置

一、爲 Nginx 添加配置文件,Ngnix 默認配置文件加載是在 /etc/nginx/conf.d/ 目錄下,新建一個配置文件(名字隨意),編輯以下:

server {
    # 監聽80端口
    listen 80;
    # 本機
    server_name 192.168.21.128; 
    # 默認請求的url
    location / {
        # 請求轉發到gunicorn服務器
        proxy_pass http://127.0.0.1:5001; 
        # 設置請求頭,並將頭信息傳遞給服務器端 
        proxy_set_header Host $host; 
    }
}

二、啓動 Nginx 服務 /etc/init.d/nginx start,訪問:http://192.168.21.128:5001/,效果以下圖:

總結

  • 客戶端向 Nginx 發起請求,Nginx 將請求轉發給 gunicorn 服務器
  • Nginx 的server_name 要和 gunicorn 啓動的 -b 參數地址一致
相關文章
相關標籤/搜索