博客開發簡記(3):從頁面到後端結構的部署

既然已經可以訪問django,那我們就可以搞點事情了哦,至少來個helloworld吧。

(一)helloworld

有兩個基本的知識點:

  1. 在瀏覽器發起一個請求(get),去到django,django調用urls.py來解析地址或參數,所以你可以改成urls.py,來決定對不同的參數作出不同的函數處理。
  2. 如果返回一個HttpResponse給瀏覽器,那瀏覽器就可以看到展示,所以HttpResponse可以是一個頁面。

基於這兩個知識點,我們就改一下urls.py,在請求主頁面時,調用一個函數,並且讓這個函數返回helloworld的內容。

urls.py的主要內容如下:

from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from django.http import HttpResponse

def dosomething(request):
    return HttpResponse('<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title>你好</title><body>hello world 喂世界 nihao</body><head></html>')

urlpatterns = [
    #path('admin/', admin.site.urls),
    url(r'^$', dosomething)
]

這裏定義一個dosomething的函數,注意這個函數是帶一個參數的,因爲django會傳遞request給它。然後dosomething返回了一個HttpResponse對象,裏面就是一些簡單的文字了。

./manage.py runserver啓動django後,在瀏覽器請求一下,可以看到這樣的效果:
helloworld的展示

這就是一個簡單的helloworld程序,太好改了吧。但這裏有個問題,上面那段python代碼,裏面出現一段html,有什麼問題嗎?html標識的是一個界面,比如用什麼標題、用什麼字體、用幾號大小,等等,這是界面設計的東西,而把界面設計跟代碼邏輯或業務邏輯混在一起,除非你能容忍混亂而且不影響開發效率,否則界面跟代碼邏輯就要分開。那界面提到哪裏去呢,就是模板了,就是一個html文件,這裏演示一下。

先mkdir創建一個templates文件夾,以後就放html文件了,然後創建一個main.html,這樣設計這個界面(好簡單!):

<h1>hello,{{yourname}}</h1>

{{yourname}}表示引用一個變量,變量名叫yourname,注意要用雙符號來引用變量。在界面中使用變量,這太尋常了,那變量的定義還有值從哪裏來的?就是業務邏輯給的呀,這就是界面與邏輯分離的表現,即界面使用邏輯給的變量值,邏輯給界面提供數據。

好了,然後,自然是要在代碼中定義這個yourname變量了,而且代碼中要展示這個html,代碼如下:

from django.contrib import admin
from django.urls import path
from django.conf.urls import url
from django.shortcuts import render

def dosomething(request):
    dict = {}
    dict['yourname'] = '廣州小程'
    return render(request, 'main.html', dict)

urlpatterns = [
    #path('admin/', admin.site.urls),
    url(r'^$', dosomething)
]

在接到請求時,還是調用dosomething函數,然後返回一個render,看名字就知道是要渲染繪製一個界面出來,這個界面是什麼呢?就是第二個參數指定的main.html,然後第三個參數就是傳遞給界面的數據,它是一個鍵值對集,明顯,一定要包括yourname這個變量,而且事先給了變量值。

最後要注意一個路徑的問題,就是render怎麼找到main.html的問題,這個要在settings.py中進行設置,然後來看一下現在的目錄結構:
增加main.html的目錄結構

按這個結構,要指定的路徑就是:項目目錄+/myblog/templates

在settings.py中找到 TEMPLATES 這個dict,然後把DIRS的值改成這樣:

‘DIRS’: [BASE_DIR+’/myblog/templates’,],

ok,一切就緒,瀏覽器請求一下,看到這樣的效果:
業務與界面發離的展示效果

好了,界面你也知道怎麼寫了,不就是寫html嘛。但是,我要的博客可沒有這麼簡單,需要一步步來開發嗎?

在你做一個事情,特別是一個常見的事情之前,看看別人是怎麼做的,或許能大大節省你的時間成本。於是,你會發現,博客這種日常操作,在github上有大量的項目。

(二)博客項目

於是,這裏我直接使用這個項目:https://github.com/liangliangyy/DjangoBlog

按這個地址的介紹來部署即可:https://www.lylinux.net/article/2019/8/5/58.html

這裏有個知識點,爲什麼有了djangoblog,還要用到nginx跟gunicorn呢?它們是什麼關係呢?

djangoblog,簡單來說就是一個web應用,也可以說是一個web框架。作爲一個應用,djangoblog當然可以runserver起來並且佔用80端口等,然後瀏覽器就可以訪問到他。但是,在實際數據交互的場景中,讓web應用把負載均衡、高併發之類的事情也做了,是不合適的,應用只應該做自己的業務。所以,還需要一個重要的角色,這個角色就是web服務器,而nginx就是一個web服務器,瀏覽器的所有請求先到達nginx,nginx先做一些前置的處理(比如靜態頁面攔截、負載平衡之類),但是,由於nginx不懂業務啊,所以它還是要調用到djangoblog(web框架或應用),但這個調用不是直接的調用,而是經過中間件,比如gunicorn。gunicorn或wsgi(網關接口),解決了nginx與django交互的問題,因爲web服務器與web框架的通信,要遵守一種協議,而gunicorn正是實現了這種協議。實際上,gunicorn本身也能做爲web服務器(類似於nginx),但因能力有限,一般會把這個角色讓位於nginx等。所以,簡單來說,一種常見的後端結構就是:nginx+gunicorn+django。

另外,由於gunicorn支持nginx與django的通信,不可或缺,如果它退出了,有必要即刻啓動起來,於是引入supervisor。supervisor監控gunicorn,保證後者的拉起,而且後者以子進程掛在supervisor進程內。於是這個結構也可以說是:nginx+supervisor+gunicorn+django。

最終用瀏覽器訪問,一個網站就出來了:
djangoblog的樣子

(三)後端知識

補充一些知識點。

(a)supervisor與gunicorn
supervisor是監控並管理gunicorn的,如果你想停止gunicorn,你只需要把對應的supervisor服務給stop掉就可以了,gunicorn進程會自動停止。

用這個命令先看一下supervisor監控了哪些服務:

supervisorctl status

然後就可以停止這個服務:

supervisorctl stop 服務名

這時服務對應的gunicorn會自動停止,用lsof -i:8000不再看到進程。 對應於stop,還有start、reload。

當然,如果想直接kill掉gunicorn進程,也可以,先找到它的根pid:

pstree -ap|grep gunicorn

再kill掉:

kill -9 pid

但是,如果supervisor是運行狀態,kill掉的gunicorn即刻就會被拉起,一個新的pid的gunicorn又會出現。

(b)supervisor的配置
諸多key-value的配置選項,請自行搜索瞭解,這裏簡單說兩個。

command,就是supervisor要執行的命令,比如執行某個程序或腳本,比如執行一句python語句等等,涉及到的文件,你可以寫完整路徑,如果寫相對路徑則要組合directory這個選項。

directory,在執行command之前,先cd到這個目錄,看情況使用(也可不用)。

(c)supervisor的日誌
如果遇到supervisor有什麼異常而你一時想不出原因,這時看一下它的日誌輸出(包括你故意print出來的日誌–因爲supervisor是python程序所以你當然可以print),也許能幫到你,那日誌在哪裏呢? 默認在/var/log/supervisor目錄下面。如果在配置supervisor時指定了標準輸出路徑,比如stdout_logfile字段,那就多了一個日誌。對於分析問題,這兩個日誌都應該關注。

(d)supervisor的進程關係
這樣查看supervisor的進程關係:

service supervisor status

如果已經設置好supervisor的配置,比如指定啓動gunicorn,那就可以看到在supervisor進程下面掛上了gunicorn進程(一般兩個,一主一從)。

(e)nginx的配置
nginx的所有配置都在:/etc/nginx 目錄,而你新增加的配置應該放在 /etc/nginx/sites-enabled 目錄下,這個目錄已經有一個叫default的默認配置文件,你的新配置可以任意命名,都將替代default配置,但你需要重新啓動nginx。

可以這樣重啓nginx:

service nginx reload/restart 或:
/etc/init.d/nginx reload

如果不放心,也可以先stop(瀏覽器請求一下)再start:

service nginx stop
service nginx start

注意,nginx明明stop掉了,用ps也看不到進程了,這時瀏覽器再請求可能還是看到頁面,這可能是瀏覽器端的緩存。

配置中的error_log字段,指定了nginx出錯時的記錄文件,這個文件可以幫助你分析nginx的錯誤。默認不設置這個字段的情況下,錯誤日誌文件是/var/log/nginx/error.log。

如果只是想驗證nginx是否可以正常使用,可以寫一個最簡單的配置,比如這樣:

server {
        listen 80;
		server_name www.freep2p.cn;
		root /root/python;
    }

然後在/root/python目錄創建一個index.html文件,比如文件內容可以這麼簡單:

<html><p2>hello world</p2></html> 

重啓nginx後用 www.freep2p.cn 訪問它,就可以看到hello world。

對於nginx的運行情況,可以用下面的命令來查看:

systemctl status nginx.service

(f)定位問題的一個關鍵
層級一多,問題定位就變得複雜。除了考慮去除層級,比如直接手動調用gunicorn不經supervisor等,這個辦法有效外,還有一個關鍵的點,就是分析日誌。

有兩個日誌要上心,一個是nginx的輸出日誌(在配置中有寫,或者使用默認的路徑即/var/log/nginx/error.log),另一個是supervisor的輸出日誌(如上介紹,有兩個),supervisor的日誌,包括了supervisor跟gunicorn的表現,還有請求應答的情況。

(g)可能遇到的問題

(1)Internal Server Error
對於nginx,如果提示“Internal Server Error”,是什麼原因呢? 這個提示(對應錯誤碼是500),意思是服務內部出問題了,但至少說明,你訪問到nginx了,只是觸發了錯誤。對於這裏的結構,nginx是要調用supervisor+gunicorn的,而gunicorn要調用djangoblog框架,是哪一步出了問題呢?

用lsof -i:8000,可以看到gunicorn已經運行起來了,而nginx的配置相對是簡單的,不太像會出問題,那gunicorn到djangoblog那一步怎麼樣?gunicorn的配置是不是有問題呢?

先把supervisor停止掉,按上面介紹的命令即可做到,supervisor給stop掉後,gunicorn也會自動結束(lsof -i:8000看不到進程),如果這時用瀏覽器請求一下,會看到這樣的提示:

502 Bad Gateway

也就是網關接口出問題了,而實際就是gunicorn給停止了。

嘗試用最簡單的nginx的配置,能正常訪問,所以排除是nginx的問題。

先把supervisor給stop掉,也就是不使用supervisor來啓用gunicorn,而是直接執行gunicorn_start.sh腳本,再訪問nginx,發現,正常了!

所以,從gunicorn到djangoblog沒有問題,問題出在supervisor啓用gunicorn引入了問題。

從理論來看,按這裏使用的supervisor的配置,supervisor解釋執行腳本(不一定用bash),再以exec命令啓動gunicorn,而exec的意思就是不另起進程,而是使用當前進程,只要gunicorn不退出(本意就是不退出的),那麼當前進程聲明的變量以及用source切換至python虛擬環境就是生效的,不用懷疑source不生效。

這時,一定要分析日誌。 在supervisor的標準輸出/var/log/djangoblog.log中,可以看到這樣的提示:
內部錯誤時supervisor的日誌提示

也就是找不到HOST變量!

根據上面的提示,這個是database的設置,代碼如下圖:
supervisor要讀取的環境變量

也就是os.environ.get不到環境變量。

手動執行sh腳本,相當在shell中交互執行,實際是bash進程啓動了gunicorn,而supervisor是python程序,它解釋sh腳本啓動了gunicorn,這兩種方式是不同的,至少bash跟supervisor使用的環境變量的配置就不同,這個下面就來證明。

先來測試一下supervisor當前的環境變量吧。

把supervisor執行的命令設置一下:

command=python3 -c “import os; print(os.environ)”

然後:

tail -10 /var/log/djangoblog.log

你可以看到已經有一些變量了,但是,沒有代碼裏面的那三個變量,因爲還沒有設置(設置到~/.bashrc中是無效的)。這樣設置到supervisor,同樣是在supervisor的配置中修改:

environment = DJANGO_MYSQL_USER=‘root’,DJANGO_MYSQL_PASSWORD=‘xxx’,…

也就是增加environment字段,值就是新增的環境變量,以逗號分隔。

再啓supervisor,這個問題就得到解決。

(2)403 forbidden
如果error的log提示,“xxx” is forbidden,一個可能是在這個目錄下面找不到index.html。


總結一下,本文介紹了怎麼簡單寫helloworld界面,怎麼使用開源的博客項目,也重點介紹了後端的知識點。

至此,解決了後端結構的部署問題,網站框架也好了,接下來就是頁面個性化類的問題了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章