點(diǎn)我查看本文集的說明及目錄。
本文是《 django by example 》第十三章 上線運(yùn)行 的內(nèi)容。
CH13 上線運(yùn)行
上一章,我們?yōu)轫?xiàng)目創(chuàng)建了 RESTful API 。本章,我們將學(xué)習(xí)如何實(shí)現(xiàn)以下功能:
配置生產(chǎn)環(huán)境
創(chuàng)建自定義中間件
實(shí)現(xiàn)自定義管理命令
生產(chǎn)環(huán)境上線運(yùn)行
現(xiàn)在是時(shí)候?qū)?Django 項(xiàng)目部署到生產(chǎn)環(huán)境了。我們將根據(jù)下面的步驟實(shí)現(xiàn)項(xiàng)目上線:
- 為生產(chǎn)環(huán)境配置項(xiàng)目;
- 使用 PostgreSQL 數(shù)據(jù)庫;
- 提供靜態(tài)資源;
- 使用 SSL 加密。
配置多種環(huán)境
實(shí)際項(xiàng)目需要處理多種環(huán)境。我們至少需要本地和生產(chǎn)環(huán)境,還可能包括其它環(huán)境。項(xiàng)目的大部分設(shè)置在多種環(huán)境中通用,但有些設(shè)置需要根據(jù)環(huán)境進(jìn)行更改。下面我們來為多種環(huán)境配置項(xiàng)目,使一切有條不紊。
在 educa 項(xiàng)目目錄下創(chuàng)建 settings 目錄。將項(xiàng)目的 settings.py 文件重命名為 settings/base.py,并創(chuàng)建下圖中的其它文件,settings 目錄結(jié)構(gòu)為:
這些文件包括:
base.py : 包含通用設(shè)置和缺省設(shè)置的基礎(chǔ)設(shè)置文件;
local.py :本地環(huán)境使用的自定義設(shè)置;
pro.py : 生產(chǎn)環(huán)境使用的自定義設(shè)置。
編輯 settings/base.py 文件,找到下面一行:
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
替換為:
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(os.path.join(__file__,os.pardir))))
這里將設(shè)置文件移動(dòng)到下一級(jí),所以需要 BASE_DIR 通過 os.pardir 指向父路徑。
編輯 settings/local.py 文件,并添加下面的代碼:
from .base import *
DEBUG = True
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
這是本地環(huán)境的設(shè)置文件。文件導(dǎo)入 base.py 定義的所有設(shè)置并為這個(gè)環(huán)境配置特定設(shè)置。由于 DEBUG 和 DATABASE 在每種環(huán)境中的值不同,這里從 base.py 文件中復(fù)制 DEBUG 和 DATABASE 設(shè)置,我們可以從 base.py 中移除這兩項(xiàng)設(shè)置。
編輯 settings/pro.py 文件并讓其看起來是這樣的:
from .base import *
DEBUG = False
ADMINS = (
('Antonio M', 'email@mydomain.com'),
)
ALLOWED_HOSTS = ['educaproject.com', 'www.educaproject.com']
DATABASES = {
'default': {
}
}
這是生產(chǎn)環(huán)境的設(shè)置。我們來看一下這些選項(xiàng):
- DEBUG : 生產(chǎn)環(huán)境必須將 DEBUG 設(shè)為 False 。不這樣做可能導(dǎo)致每個(gè)人都能看到調(diào)試信息及敏感配置。
- ADMINS:DEBUG 設(shè)置為 False 并且視圖出現(xiàn)異常時(shí),異常信息會(huì)通過郵件上報(bào)給 ADMINS 設(shè)置的用戶。這里需要將 name/e-mail 對改為自己的信息。
- ALLOWED_HOSTS :由于 DEBUG 設(shè)置為 False ,Django 只允許這個(gè)列表中的主機(jī)提供服務(wù)。這是一個(gè)安全機(jī)制,這里已經(jīng)包含了網(wǎng)站使用的域名 educaproject.com 和 www.educaproject.com。
- DATABASES : 保留了空的設(shè)置,下一步將實(shí)現(xiàn)生產(chǎn)環(huán)境的數(shù)據(jù)庫設(shè)置。
注意:
處理多種環(huán)境時(shí),創(chuàng)建一個(gè)基礎(chǔ)設(shè)置文件,并為每種環(huán)境配置一個(gè)設(shè)置文件。環(huán)境設(shè)置文件應(yīng)該繼承通用設(shè)置并重寫環(huán)境特定配置。
我們已經(jīng)將配置文件從默認(rèn)的 settings.py 文件更改到另一個(gè)文件。在指定要使用的配置模塊之前,manage.py 工具不能執(zhí)行任何命令。在 shell 中運(yùn)行管理命令時(shí),需要添加一個(gè) —settings 設(shè)置或設(shè)置 DJANGO_SETTINGS_MODULE 環(huán)境變量。打開 shell并運(yùn)行下面的命令:
export DJANGO_SETTINGS_MODULE=educa.settings.pro
這將為當(dāng)前 shell 會(huì)話設(shè)置 DJANGO_SETTINGS_MODULE 環(huán)境變量。如果你不想每次啟動(dòng) shell 執(zhí)行上面的命令,請將這個(gè)命令配置到 .bashrc 或者 .bash_profile 文件中。如果不想在 .bashrc 或 .bash_profile 中設(shè)置這個(gè)變量,運(yùn)行命令是需要包含 —settings ,比如:
python manage.py migrate –-settings=educa.settings.pro
現(xiàn)在,我們已經(jīng)為多種環(huán)境組織好設(shè)置了。
安裝 PostgreSQL
我們整本書使用的都是 SQLite 數(shù)據(jù)庫。這樣做很簡單并且容易啟動(dòng),但是生產(chǎn)環(huán)境需要像 PostgreSQL、MySQL、Oricle 等更加強(qiáng)大的數(shù)據(jù)庫。我們將為我們的項(xiàng)目使用 PostGreSQL 數(shù)據(jù)庫。Django 推薦使用 PostGreSQL 數(shù)據(jù)庫。Django 內(nèi)置 django.contrib.postgres 模塊便于我們使用特定的 PostGreSQL 功能。https://docs.djangoproject.com/en/1.11/ref/contrib/postgres/ 有這個(gè)模塊的詳細(xì)介紹。
如果使用 Linux,這樣安裝 Python 操作 PostGreSQL 的依賴關(guān)系:
sudo apt-get install libpq-dev python-dev
然后使用以下命令安裝 PostgreSQL:
sudo apt-get install postgresql postgresql-contrib
如果使用 mac OS X 或者 Windows ,可以從 https://www.postgresql.org/download/ 下載 PostgreSQL 。
我們來創(chuàng)建 PostGreSQL 用戶,打開 shell 并運(yùn)行以下命令:
su postgres
createuser -dP educa
將需要輸入密碼及這個(gè)用戶的權(quán)限。輸入密碼和權(quán)限后使用下面的命令創(chuàng)建一個(gè)新的數(shù)據(jù)庫:
createdb -E urf8 -U educa educa
然后,編輯 settings/pro.py 文件并修改 DATABASES 設(shè)置:
DATABASES = {'default': {'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'educa',
'USER': 'educa',
'PASSWORD': '******',
},
}
將上述數(shù)據(jù)替換為自己創(chuàng)建的用戶的數(shù)據(jù)庫名稱和憑據(jù)。 新的數(shù)據(jù)庫是空的, 運(yùn)行以下命令來實(shí)現(xiàn)所有數(shù)據(jù)庫遷移:
python manage.py migrate –-settings=educa.settings.pro
最后,使用以下命令創(chuàng)建 superuser:
python manage.py createsuperuser –-settings=educa.settings.pro
檢查你的項(xiàng)目
Django 內(nèi)置可以在任何時(shí)刻檢查項(xiàng)目的 check 管理命令。這個(gè)命令檢查 Django 項(xiàng)目中的應(yīng)用程序并輸出錯(cuò)誤或警告。如何添加 —deploy 選項(xiàng),則僅僅進(jìn)行生產(chǎn)相關(guān)的檢查。打開 shell 運(yùn)行以下命令來執(zhí)行檢查:
python manage.py check --deploy –-settings=educa.settings.pro
你將會(huì)看到輸出中沒有錯(cuò)誤但是又幾個(gè)警告。這意味著檢查是成功地,但是應(yīng)該逐條檢查警告使項(xiàng)目在生產(chǎn)環(huán)境中更加安全的運(yùn)行。這里我們不再深入討論這個(gè)問題,但是一定要記住在上線之前檢查項(xiàng)目來識(shí)別任何相關(guān)問題。
使用 WSGI 提供 Django 服務(wù)
Django 的主要部署平臺(tái)是 WSGI 。WSGI 是 Web Server Gateway Interface 的縮寫,它是 Python 語言中 Web服務(wù)器和 Web應(yīng)用程序之間或框架之間的通用接口標(biāo)準(zhǔn)。
我們使用 startproject 命令創(chuàng)建新項(xiàng)目時(shí), Django 在項(xiàng)目目錄中生成一個(gè) wsgi.py 文件。這個(gè)文件包括可調(diào)用 WSGI 配置(可以訪問應(yīng)用)。 使用 Django 開發(fā)服務(wù)器運(yùn)行項(xiàng)目和在生產(chǎn)環(huán)境運(yùn)行項(xiàng)目都需要使用 WSGI 。
我們可以從http://wsgi.readthedocs.io/en/latest/ 了解到更多 WSGI 相關(guān)細(xì)節(jié)。
安裝 uWSGI
整本書我們都在使用 Django 開發(fā)服務(wù)器在本地運(yùn)行項(xiàng)目。然而,生產(chǎn)環(huán)境需要一個(gè)真正的 web 服務(wù)器來部署我們的項(xiàng)目。
uWSGI 是一個(gè)非常快的 Python 應(yīng)用服務(wù)器。它使用 WSGI 配置與我們的 Python 應(yīng)用通信。uWSGI 將 web 請求轉(zhuǎn)化為 Django 可以處理的格式。
使用以下命令安裝 uWSGI 服務(wù)器:
pip install uwsgi==2.0.11.1
如果使用 Mac OS X,我們可以通過 brew install uwsgi 命令使用 Homebrew 安裝 uWSGI 。如果希望在 Windows 中安裝 uWSGI,則需要使用 Cygwin 。然而,我們推薦在 UNIX 環(huán)境中使用 uWSGI。
配置 uWSGI
我們可以從命令行運(yùn)行 uWSGI 。打開 shell 并從 educa 項(xiàng)目目錄運(yùn)行以下命令:
uwsgi --module=educa.wsgi:application \
--env=DJANGO_SETTINGS_MODULE=educa.settings.pro \
--http=127.0.0.1:80 \
--uid=1000 \
--virtualenv=/home/zenx/env/educa/
如果使用的賬戶沒有足夠權(quán)限,這些命令需要添加 sudo 前綴。
我們使用這個(gè)命令在本地運(yùn)行 uWSGI ,并設(shè)置了以下參數(shù):
- 使用 educa.wsgi:application WSGI;
- 從生產(chǎn)環(huán)境加載設(shè)置;
- 使用虛擬環(huán)境,將 virtualenv 選項(xiàng)設(shè)置為你的虛擬環(huán)境目錄。如果沒有使用虛擬環(huán)境,則可以跳過這一步。
如果沒有在項(xiàng)目目錄運(yùn)行這個(gè)命令,使用項(xiàng)目的路徑設(shè)置 —chdir=/path/to/educa/ 。
在瀏覽器中打開 http://127.0.0.1/,你將看到生成的 HTML , 但是并沒有加載任何 CSS 或者圖片。由于我們沒有配置 uWSGI 提供靜態(tài)文件,所以這很好理解。
我們可以使用 .ini. 文件定義 uWSGI 自定義配置。這比向命令行添加選項(xiàng)更加方便。在 educa 目錄下創(chuàng)建如下文件結(jié)構(gòu):
編輯 uwsgi.ini 文件并添加以下代碼:
# variables
projectname = educa
base = /home/zenx/educa
# configuration
master = true
virtualenv = /home/zenx/env/%(projectname)
pythonpath = %(base)
chdir = %(base)
env = DJANGO_SETTINGS_MODULE=%(projectname).settings.pro
module = educa.wsgi:application
socket = /tmp/%(projectname).sock
我們定義了如下變量:
- projectname : Django 項(xiàng)目的名稱,這里使用 educa 。
- base : educa 項(xiàng)目的絕對路徑。使用你的絕對路徑進(jìn)行設(shè)置。
下面是可能會(huì)使用的 uWSGI 選項(xiàng)。我們可以定義任意其它與 uWSGI 選項(xiàng)不同的變量,這里設(shè)置了這些選項(xiàng):
- master :啟動(dòng) master 處理;
- virtualenv : 虛擬環(huán)境路徑,設(shè)置為你的虛擬環(huán)境目錄。
- pythonpath :添加你的 Python 路徑的路徑。
- chdir : 項(xiàng)目目錄的路徑,這樣 uWSGI 在加載應(yīng)用之前可以更改路徑。
- env : 環(huán)境變量。這里包含指向生產(chǎn)環(huán)境配置的 DJANGO_SETTINGS_MODULE 變量。
- module :使用的 WSGI 模塊。我們將其設(shè)置為項(xiàng)目 wsgi 模塊中包括的可調(diào)用 application 。
- socket : 綁定到服務(wù)器的 UNIX/TCP socket 。
socket 選項(xiàng)是為了便于與第三方 router (比如 Nginx ) 通信,http 選項(xiàng)用于 uWSGI 接收 HTTP請求并自己實(shí)現(xiàn)路由。我們將使用 socket 運(yùn)行 uWSGI 。由于我們將使用 Nginx 作為我們的 web服務(wù)器,所以使用 socket 與 uWSGI 進(jìn)行通信。
我們可以從 http://uwsgi-docs.readthedocs.io/en/latest/Options.html 了解所有 uWSGI 選項(xiàng)。
現(xiàn)在,我們可以使用以下命令運(yùn)行自定義配置的 uWSGI :
uwsgi --ini config/uwsgi.ini
由于通過 socket 運(yùn)行,現(xiàn)在還不能通過瀏覽器訪問 uWSGI 實(shí)例。下一步,我們來完成生產(chǎn)環(huán)境。
安裝 Nginx
網(wǎng)站上線時(shí),我們需要提供動(dòng)態(tài)內(nèi)容,還需要提供靜態(tài)文件(比如 CSS、JavaScript 文件和圖像)。 盡管 uWSGI 能夠提供靜態(tài)文件,但這將為 HTTP請求增加不必要的開銷。 因此,建議在 uWSGI 之前設(shè)置一個(gè)類似 Nginx 的 Web服務(wù)器來提供靜態(tài)文件。
Nginx 是一款專注于高并發(fā)性、高性能和低內(nèi)存使用率的 Web服務(wù)器。 Nginx 還充當(dāng)反向代理,接收 HTTP請求并將它們路由到不同的后端。 一般而言,需要在前面使用諸如 Nginx 之類的Web服務(wù)器,以便快速有效的提供靜態(tài)文件,并將動(dòng)態(tài)請求轉(zhuǎn)發(fā)到 uWSGI 。 通過使用 Nginx ,我們還可以使用規(guī)則及反向代理功能。
使用以下命令安裝 Nginx :
sudo apt-get install nginx
Mac OS X 系統(tǒng)可以使用 brew install nginx 安裝 Nginx 。 http://nginx.org/en/download.html 網(wǎng)站有 Windows 系統(tǒng)安裝 Nginx 使用的二進(jìn)制文件。
生產(chǎn)環(huán)境
下圖表示生產(chǎn)環(huán)境最終的流程:
Client browser (客戶端瀏覽器) 發(fā)出 HTTP 請求后將進(jìn)行以下工作:
- Nginx 接收 HTTP 請求;
- 如果請求靜態(tài)文件, Nginx 直接提供靜態(tài)文件。如果請求動(dòng)態(tài)頁面, Nginx 通過 socket 將請求發(fā)送到 uWSGI 。
- uWSGI 將請求發(fā)送到 Django ,Django 的 HTTP 響應(yīng)傳回 Nginx ,Nginx 將其發(fā)送到 Client browser 。
配置 Nginx
在 config/ 目錄下新建一個(gè) nginx.conf 文件,添加以下代碼:
# the upstream component nginx needs to connect to
upstream educa {
server unix:///tmp/educa.sock;
}
server {
listen 80;
server_name www.educaproject.com educaproject.com;
location / {
include /etc/nginx/uwsgi_params;
uwsgi_pass educa;
} }
這是 Nginx 基礎(chǔ)配置,我們設(shè)置了一個(gè)名為 educa 的 upstream 來指向 uWSGI 創(chuàng)建的 socket 。我們使用服務(wù)器指令并添加以下配置:
- 告訴 Nginx 監(jiān)聽 80 端口;
- 將服務(wù)器名稱設(shè)置為 www.educaproject.com 和 educaproject.com 。Nginx將為這兩個(gè)域的請求提供服務(wù)。
- 最后,指定所有 / 路徑的內(nèi)容必須路由到 educa socket( uWSGI )。并包含 Nginx 內(nèi)置的默認(rèn) uWSGI 配置。
我們可以從 http://nginx.org/en/docs/ 找到 Nginx 文檔。 Nginx 主配置文件位于 /etc/nginx/nginx.conf ,它包含 /etc/nginx/sites-enabled/ 中找到的所有配置文件。為了讓 Nginx 加載自定義配置文件,這樣創(chuàng)建一個(gè)鏈接:
sudo ln -s /home/zenx/educa/config/nginx.conf /etc/nginx/sites-enabled/
educa.conf
將 /home/zenx/educa/ 替換為自己的絕對路徑。 如果 uWSGI 還沒有運(yùn)行,打開一個(gè) shell 并運(yùn)行 uWSGI :
uwsgi --ini config/uwsgi.ini
打開第二個(gè) shell 使用以下命令運(yùn)行 Nginx :
server nginx start
由于這里使用的是示例域名,因此需要將其重定向到本地主機(jī)。 編輯 /etc/hosts 文件,并添加下列內(nèi)容:
127.0.0.1 educaproject.com
127.0.0.1 www.educaproject.com
這樣,兩個(gè)名稱都路由到了我們的本地服務(wù)器。在生產(chǎn)服務(wù)器中,我們不需要這樣做。在生產(chǎn)服務(wù)器中,我們將在 域名 DNS 配置中主機(jī)指向我們的服務(wù)器。
在瀏覽器中打開 http://educaproject.com/ 。應(yīng)該可以能夠看到?jīng)]有加載任何靜態(tài)資源的網(wǎng)站了。我們的生產(chǎn)環(huán)境快要部署成功了。
提供靜態(tài)和文件資源
為了實(shí)現(xiàn)最佳性能,我們將使用 Nginx 直接提供靜態(tài)資源。
編輯 settings/base.py 文件并添加下面的代碼:
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR,'static/')
這里需要使用 Django 輸出靜態(tài)資源。 collect static 命令可以將所有應(yīng)用的靜態(tài)文件拷貝到 STATIC_ROOT 目錄。
打開 shell 并運(yùn)行下面的命令:
python manage.py collectstatic
你將看到下面的輸出:
You have requested to collect static files at the destination
location as specified in your settings.
This will overwrite existing files!
Are you sure you want to do this?
輸入 yes 以便 Django 拷貝所有文件。你將看到下面的輸出。
96 static files copied to '/Users/apple/profile/django_by_example/educa/educa3/static'.
現(xiàn)在,編輯 config/nginx.conf 文件并在 server 中添加下面的代碼:
location /static/ {
alias /home/zenx/educa/static/;
}
location /media/ {
alias /home/zenx/educa/media/;
}
記得使用項(xiàng)目路徑替換 /home/zenx/educa/ 路徑。Nginx 可以提供 /static/ 或 /media/ 路徑下的靜態(tài)文件了。 這些命令告訴 Nginx 直接提供 /static/ 或 /media/ 路徑下的靜態(tài)資源。
使用以下命令重新加載 Nginx 配置文件:
service nginx reload
在瀏覽器中打開 http://educaproject.com/ 。現(xiàn)在應(yīng)該可以看到靜態(tài)文件了。我們已經(jīng)成功配置 Nginx 來提供靜態(tài)資源了。
使用 SSL 進(jìn)行安全連接
SSL 協(xié)議( Secure Sockets Layer ) 正在成為為網(wǎng)站提供安全連接服務(wù)的標(biāo)準(zhǔn)。 強(qiáng)烈建議您的網(wǎng)站使用 HTTPS 提供服務(wù)。 我們將在 Nginx 中配置 SSL 證書來安全地為網(wǎng)站提供服務(wù)。
創(chuàng)建 SSL 證書
在 educa 項(xiàng)目中新建一個(gè)名為 ssl 的目錄。然后在命令行使用以下命令生成一個(gè) SSL 證書:
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ssl/
educa.key -out ssl/educa.crt
你正在生成一個(gè)私鑰和一個(gè)有效期為一年的 2048-bit 的 SSL 證書。你將需要輸入以下信息:
Country Name (2 letter code) [AU]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []: Madrid
Organization Name (eg, company) [Internet Widgits Pty Ltd]: Zenx IT
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []: educaproject.com
Email Address []: email@domain.com
你可以使用自己的數(shù)據(jù)填寫需要的內(nèi)容。其中最重要的字段為 Common Name ,這個(gè)字段指定需要驗(yàn)證的域名,我們這里使用 educaproject.com。
通常會(huì)在 ssl 目錄下生成一個(gè) educa.key 的私鑰文件和一個(gè) educa.crt 證書。
使用 SSL 驗(yàn)證 Nginx
編輯 nginx.conf 文件并修改服務(wù)器指令以包含以下SSL指令:
server { listen listen
80;
443 ssl;
ssl_certificate
ssl_certificate_key /home/zenx/educa/ssl/educa.key; server_name www.educaproject.com educaproject.com; # ...
}
現(xiàn)在,服務(wù)器通過 80 端口監(jiān)聽 HTTP ,并通過 443 端口監(jiān)聽 HTTPS 。我們使用 ssl_certificate 指定 SSL 證書的路徑,并使用 ssl_certificate_key 指定證書秘鑰。
使用以下密鑰重啟 Nginx :
sudo service nginx restart
Nginx 將加載新的配置。在瀏覽器中打開 https://educaproject.com ,你應(yīng)該可以看到下圖所在的警告信息:
由于瀏覽器的不同,上面的信息可能會(huì)有所不同。信息提醒我們網(wǎng)站沒有使用可信任的證書。瀏覽器無法驗(yàn)證網(wǎng)站的身份。這是因?yàn)槲覀冏约汉炇鹆俗C書,而沒有從可信任的證書頒發(fā)機(jī)構(gòu)獲得證書。當(dāng)你擁有一個(gè)真實(shí)的域名時(shí),可以申請一個(gè)可信的 CA 來處理 SSL 證書,以便瀏覽器驗(yàn)證其身份。
如果要為真實(shí)域名獲取一個(gè)可信的證書,可以參考 Linux Foundation 創(chuàng)建的 Let's Encrypt 項(xiàng)目。 這是一個(gè)旨在簡化獲取和更新可信 SSL 證書的協(xié)作項(xiàng)目。 https://letsencrypt.org/ 網(wǎng)站有更多相關(guān)信息。
點(diǎn)擊 Add Exception 按鈕告訴瀏覽器我們信任這個(gè)證書。 您將看到瀏覽器的 URL 旁邊顯示一個(gè)鎖形圖標(biāo),如以下屏幕截圖所示:
如果點(diǎn)擊鎖形圖標(biāo),將會(huì)顯示 SSL 證書詳情。
為項(xiàng)目配置 SSL
Django 內(nèi)置一些 SSL 配置。編輯 settings/pro.py 設(shè)置文件并添加下面的代碼:
SECURE_SSL_REDIRECT = True
CSRF_COOKIE_SECURE = True
這些設(shè)置為:
- SECURE_SSL_REDIRECT : HTTP 請求是否需要重定向到 HTTPS 請求。
- CSRF_COOKIE_SECURE : 為跨網(wǎng)站請求偽造防御建立安全 cookie 。
太棒了,我們已經(jīng)配置了一個(gè)生產(chǎn)環(huán)境,可以為項(xiàng)目提供卓越的性能。
創(chuàng)建自定義中間件
我們已經(jīng)了解了包含項(xiàng)目中間件的 MIDDLEWARE_CLASSES 設(shè)置。中間件是一個(gè)類,它內(nèi)置可以全局執(zhí)行的特定方法,我們可以把它想象成低級(jí)別的插件系統(tǒng),在請求或響應(yīng)過程中執(zhí)行的 Hook 。 每個(gè)中間件負(fù)責(zé)為所有請求或響應(yīng)執(zhí)行特定操作。
注意:
由于中間件為每個(gè)請求執(zhí)行特定方法,請避免為中間件添加昂貴的過程。
接收到 HTTP 請求后,中間件按照 MIDDLEWARE_CLASSES 設(shè)置中設(shè)定的順序執(zhí)行。Django 生成一個(gè) HTTP 響應(yīng)后,中間件方法將按照反向順序執(zhí)行。
下圖展示了在請求階段和響應(yīng)階段中間件方法的執(zhí)行順序。 它還顯示了(可能)調(diào)用的中間件方法:
請求階段將執(zhí)行這些中間件方法:
- process_request(request) : 在 Django 決定請求執(zhí)行哪個(gè)視圖之前調(diào)用。request 是一個(gè) HttpRequest 實(shí)例;
- process_view (request, view_func, view_args, view_kwargs ) :在 Django 調(diào)用視圖之前調(diào)用。它可以訪問視圖函數(shù)及接收的參數(shù)。
響應(yīng)階段將執(zhí)行這些中間件方法:
- process_exception( request, exception ) :視圖函數(shù)引發(fā) Exception 異常時(shí)調(diào)用。
- process_template_response(request, response) :視圖執(zhí)行完后返回的響應(yīng)對象有一個(gè) render() 方法時(shí)調(diào)用(如果一個(gè) TemplateResponse 或者等效的對象)。
- process_response(request, response) : 所有響應(yīng)返回瀏覽器之前都要調(diào)用。
由于中間件可能需要使用前面中間件方法設(shè)置到請求中的數(shù)據(jù)集,MIDDLEWARE_CLASSES 設(shè)置中中間件的順序非常重要。注意,即使由于前面的中間件返回了 HTTP 響應(yīng)而跳過了 process_request() 或者 process_view() ,也將調(diào)用中間件的 process_response() 方法。這意味著,process_response() 不能依賴請求階段的數(shù)據(jù)集。如果中間件處理異常并返回響應(yīng),則不會(huì)執(zhí)行前面的中間件類。
注意:
向 MIDDLEWARE_CLASSES 添加新的中間件時(shí),確保把它放在正確的位置。中間件方法在請求階段按照設(shè)置中的順序執(zhí)行,在響應(yīng)階段按照反向順序執(zhí)行。
我們可以從 https://docs.djangoproject.com/en/1.11/topics/http/middleware/ 了解更多中間件的信息。
我們將創(chuàng)建自定義中間件來實(shí)現(xiàn)通過自定義子域訪問課程。每個(gè)課程細(xì)節(jié)視圖的 URL (看起來像 https://educaproject.com/course/django/)也可以通過使用課程內(nèi)容的子域訪問,例如 https://django.educaproject.com/ 。
創(chuàng)建子域中間件
中間件可以位于項(xiàng)目的任何位置。然而,推薦在應(yīng)用目錄下創(chuàng)建一個(gè) middleware.py 文件。
在 courses 應(yīng)用目錄下新建一個(gè) middleware.py 文件并添加以下代碼:
from django.core.urlresolvers import reverse
from django.shortcuts import get_object_or_404, redirect
from .models import Course
class SubdomainCourseMiddleware(object):
"""
Provides subdomains for courses
"""
def process_request(self, request):
host_parts = request.get_host().split('.')
if len(host_parts) > 2 and host_parts[0] != 'www':
# get course for the given subdomain
course = get_object_or_404(Course, slug=host_parts[0])
course_url = reverse('course_detail', args=[course.slug])
# redirect current request to the course_detail view
url = '{}://{}{}'.format(request.scheme, '.'.join(host_parts[1:]),
course_url)
return redirect(url)
我們創(chuàng)建了一個(gè)執(zhí)行 process_request() 的中間件。接收到 HTTP 請求后,我們完成以下任務(wù):
獲取請求使用的主機(jī)名并對其進(jìn)行拆分。例如,如果用戶訪問 mycourse.educaproject.com,這里將生成 ['mycourse', 'educaproject', 'com'] 。
通過檢查主機(jī)名拆分是否生成2個(gè)以下元素確定主機(jī)名是否含有子域。如果主機(jī)名包含子域,并且子域不是 'www',則嘗試使用子域提供的 slug 查找對應(yīng)課程。
-
如果沒有找到課程,引發(fā)一個(gè) Http404 異常。使用主域?qū)g覽器重定向到課程詳細(xì)信息 URL 。
?
編輯項(xiàng)目的 settings/base.py 文件并將 'courses.middleware.SubdomainCourseMiddleware' 添加到 MIDDLEWARE_CLASSES 列表的底部。如下所示:
MIDDLEWARE = ['django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
# 'django.middleware.cache.UpdateCacheMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.moddleware.cache.FetchFromCacheMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'course.middleware.SubdomainCourseMiddleware' ]
現(xiàn)在,我們剛剛創(chuàng)建的中間件將對每個(gè)請求進(jìn)行操作。
使用 Nginx 提供多個(gè)子域
Nginx 需要為任何可能的子域提供網(wǎng)站服務(wù)。編輯 config/nginx.conf 文件并找到以下行:
server_name www.educaproject.com educaproject.com;
將其替換為:
server_name *.educaproject.com educaproject.com;
通過使用星號(hào),規(guī)則將適用于 educaproject.com 的所有子域。 為了在本地測試中間件,我們將任何需要測試的子域添加到 /etc/hosts 。 要使用slug 為 django 的 Course 對象測試中間件,請將下面一行添加到 /etc/hosts 文件中:
127.0.0.1 django.educaproject.com
然后,在瀏覽器中打開 https://django.educaproject.com/ 。 中間件將通過子域找到課程并將瀏覽器重定向到https://educaproject.com/course/django/。
執(zhí)行自定義管理命令
Django 允許應(yīng)用注冊用于 manage.py 的自定義管理命令。例如,我們在第九章使用了管理命令 makemessages 和 compilemessages 來創(chuàng)建和編譯翻譯文件。
管理命令由繼承 django.core.management.BaseCommand 的 Command 類組成的 Python 模塊實(shí)現(xiàn)。我們可以創(chuàng)建簡單命令,或者接收位置和可選參數(shù)作為輸入。
Django 從 INSTALLED_APPS 設(shè)置中每個(gè)激活的應(yīng)用的 management/commands/ 目錄中查找管理命令。找到的每個(gè)模塊都將注冊為管理命令(命令名稱為模塊名稱)。
https://docs.djangoproject.com/en/1.11/howto/custom-management-commands/ 包含更多自定義管理命令的介紹。
我們將創(chuàng)建一個(gè)自定義管理命令來提醒學(xué)生至少注冊一門課程。命令將向注冊了一段時(shí)間但是沒有報(bào)讀任何課程的用戶發(fā)送郵件提醒。
在 students 應(yīng)用目錄下創(chuàng)建以下文件結(jié)構(gòu):
編輯 enroll_reminder.py 文件并添加以下代碼:
import datetime
from django.conf import settings
from django.contrib.auth.models import User
from django.core.mail import send_mass_mail
from django.core.management.base import BaseCommand
from django.db.models import Count
class Command(BaseCommand):
help = 'Sends an e-mail reminder to users registered more \
than N days that are not enrolled into any courses yet'
def add_arguments(self, parser):
parser.add_argument('--days', dest='days', type=int)
def handle(self, *args, **options):
emails = []
subject = 'Enroll in a course'
date_joined = datetime.date.today() - datetime.timedelta(
days=options['days'])
users = User.objects.annotate(
course_count=Count('courses_enrolled')).filter(course_count=0,
date_joined__lte=date_joined)
for user in users:
message = 'Dear {},\n\nWe noticed that you didn\'t enroll in any courses yet.What are you waiting for ?'.format(
user.first_name)
emails.append(
(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email]))
send_mass_mail(emails)
self.stdout.write('Sent {} reminders'.format(len(emails)))
這就是我們的 enroll_reminder 命令,上面的代碼如下所述:
Command 命令繼承 BaseCommand。
添加 help 屬性。這個(gè)屬性提供運(yùn)行 python manage.py help enroll_reminder 時(shí)打印的簡單描述。
使用 add_arguments() 方法添加 —days 名稱參數(shù),這個(gè)參數(shù)用于指定用戶注冊但是沒有報(bào)讀任何課程的最小日期以便于提供提醒。
handle() 方法包含實(shí)際命令。獲取命令行解析到的 days 屬性,然后查詢注冊超過特定日期但是沒有報(bào)讀任何課程的用戶,查詢通過聚合計(jì)算每個(gè)用戶報(bào)讀的課程總數(shù)得到;為每個(gè)符合條件的用戶生成提醒郵件并放入 email 列表中。最后,使用 send_mass_mail() 函數(shù)發(fā)送郵件,send_mass_mail() 函數(shù)可以打開一次 SMTP 鏈接發(fā)送多封郵件。
我們已經(jīng)創(chuàng)建了第一個(gè)管理命令,打開 shell 并運(yùn)行命令:
python manage.py enroll_reminder --days=20
如果您沒有本地 STMP 服務(wù)器,查看第二章中關(guān)于為 Django 項(xiàng)目配置 STMP 設(shè)置的內(nèi)容。我們還可以將以下內(nèi)容添加到 settings/base.py 文件中以便在開發(fā)過程中將郵件打印到標(biāo)準(zhǔn)輸出:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
我們來安排運(yùn)行管理命令,以便服務(wù)器每天早上8點(diǎn)運(yùn)行該命令。如果您使用的是基于 UNIX 的系統(tǒng),例如 Linux 或Mac OS X ,請打開 shell 并運(yùn)行 crontab -e 編輯 cron ,添加以下行:
0 8 * * * python /path/to/educa/manage.py enroll_reminder --days=20
--settings=educa.settings.pro
如果你不熟悉 cron ,可以在 https://en/wikipedia.org/wiki/Cron 找到更多資料。
如果您使用 Windows,可以使用任務(wù)管理器安全任務(wù)。 http://windows.microsoft.com/en-au/windows/schedule-task#1TC=windows-7 包含更多相關(guān)內(nèi)容。
定期執(zhí)行任務(wù)的另一個(gè)選擇是使用 Celery 創(chuàng)建任何和日程。我們在第七章使用過 Celery 來執(zhí)行異步任務(wù)。這種情況不需要?jiǎng)?chuàng)建管理命令以及使用 Cron 創(chuàng)建日程,我們可以創(chuàng)建異步任務(wù)并使用 Celery 心跳日程來執(zhí)行任務(wù)即可。使用 Celery 調(diào)度定期任務(wù)的更多信息見 http://celery.readthedocs.org/en/latest/userguide/periodic-tasks.html 。
注意:
使用 Cron 或 Windows 計(jì)劃任務(wù)控制面板時(shí),使用獨(dú)立腳本實(shí)現(xiàn)管理命令。
Django 內(nèi)置 Python 調(diào)用管理命令的方法。我們可以使用代碼運(yùn)行管理命令:
from django.core import management
management.call_command('enroll_reminder',days=20)
恭喜你!現(xiàn)在可以為應(yīng)用創(chuàng)建自定義管理命令并定期執(zhí)行了。
總結(jié)
本章,我們使用 uWSGI 和 Nginx 配置了生成環(huán)境,并且學(xué)習(xí)了如何創(chuàng)建自定義中間件以及如何創(chuàng)建自定義管理命令。