雖然好久之前就據說「早期的網站不少經過cgi形式實現」、「C++可經過CGI形式編寫網頁」,日積月累對CGI也有了一些概念,但一直沒真正見過一個實際運行的CGI網站,總歸仍是有些底氣不足。html
上週在菜鳥教程上看到有CGI的編程實現因此就模仿實現一下,而過程當中發現不能成功運行(實際上是本身的未指定腳本處理shell的問題),而後又百度其餘資料看到實現方式五花八門不是人云亦云就是實現很不規範,因此本身記錄一下。前端
本文實現環境:Ubuntu 16.04 + Python 3.7 + apache2python
Ubuntu(嚴謹點應該說Dibian)上apache的配置文件在/etc/apache2的目錄,咱們先來看其目錄結構,以下:ios
xxx-available文件夾表示當前apache2支持的功能,對應的xxx-enabled表示啓用的功能;xxx-enabled下的文件通常是xxx-available下的文件的軟連接。c++
其中magic定義的是一些MIME的東西(?),envvars定義一些供其餘配置文件使用的變量,這兩個通常不用咱們修改來看咱們須要修改的:web
/etc/apache2/ |-- apache2.conf # 該文件經過IncludeOptional包含如下(各目錄中的)配置文件 | `-- ports.conf # 該文件主要配置監聽端口 |-- mods-enabled # 該目錄下的文件是針對某個模塊的配置 | |-- *.load # .load文件通常是用LoadModule命令加載模塊對應的.so文件 | `-- *.conf # .conf文件通常是同名.load文件加載的模塊的自己須要的配置,通常不用咱們管 |-- conf-enabled # 該目錄下的文件通常是針對某項功能的配置 | `-- *.conf `-- sites-enabled # 該目錄下的文件用於配置VirtualHost `-- *.conf
apache被分紅這麼多配置文件,是爲了讓咱們方便地知道某項功能對應的配置功是什麼;固然對於不熟悉的人則可能感受到的不是方便而是複雜,此時就要知道這只是一種約定的組織形式而並非語法上的強制,因此你要全部配置全都一把寫在apache2.conf這文件中也是能夠的(但注意apache2爲先配置先生效後配置不生效原則,若是IncludeOptional在前且已配置某項的值那你在apache2.conf追加的該項值會不生效)。shell
從上節的介紹中,咱們能夠總結出想啓用某項功能就在xxx-enabled目錄下,建立指向該功能在xxx-available目錄下相應文件的軟連接;要停用某項功能就刪除該功能在xxx-enable目錄下相應的軟連接。apache
這操做是可行的,但Ubuntu還直接提供了命令來實現該功能,這能夠免除咱們切換目錄、忘記軟連接命令書寫格式、遺漏連接等困撓。編程
mod對應的啓停用使令是a2enmod/a2dismod,conf對應的啓停用命令是a2enconf/a2disconf,site對應的啓停用命令是a2ensite/a2dissite。vim
好比咱們這裏想啓用停用cgi支持功能對應的命令就是:
# cgi和cgid這兩個模塊的區別是什麼我還不太懂,彷佛其實只用cgid就能夠了(?) # 啓用,cgi、cgid這兩個名字是mods-available目錄下的文件的名字 sudo a2enmod cgi cgid # 停用,cgi、cgid這兩個名字是mods-enabled目錄下要刪除掉的軟連接的名字 sudo a2dismod cgi cgid
可能有小夥伴仍是感受沒有很方便啊,我仍是獲得mods-available或mods-enabled下看文件的名字;那其實還有另外的寫法,你能夠先不帶要啓用/停用的功能的名字,而後該命令就會列出當前全部可啓用/停用的功能的名字,此時你再輸入想要啓用/停用的功能的名字便可,以下圖:
總結第二大節知識,使用如下命令啓用:
# 啓用模塊支持。仍是那句話我暫時沒懂cgi和cgid的區別,因此索性兩個都啓用 sudo a2enmod cgi cgid # 啓用cgi的默認配置。 sudo a2enconf serve-cgi-bin # 重啓apache使用置生效 sudo systemctl restart apache2
這裏的針對性改造指兩點,一是咱們前面只是啓動了cgi咱們還沒給apache指出要處理什麼語言的cgi,二是咱們想指定cgi文件的目錄爲/var/www/cgi-bin。
咱們先到/etc/apache2/conf-enabled目錄下查看serve-cgi-bin.conf的配置,默認以下:
<IfModule mod_alias.c> <IfModule mod_cgi.c> Define ENABLE_USR_LIB_CGI_BIN </IfModule> <IfModule mod_cgid.c> Define ENABLE_USR_LIB_CGI_BIN </IfModule> <IfDefine ENABLE_USR_LIB_CGI_BIN> ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ <Directory "/usr/lib/cgi-bin"> AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Require all granted </Directory> </IfDefine> </IfModule> # vim: syntax=apache ts=4 sw=4 sts=4 sr noet
能夠看到一是沒有配置處理文件,二是設置的文件夾是/usr/lib/cgi-bin/。咱們使用"AddHandler cgi-script .cgi .py"指定cgi指定擴展名爲.cgi和.py的文件(通常無論什麼語言寫的都應保存成.cgi但對於pytho也容許保存成習慣的.py),另使用「ScriptAlias /cgi-bin/ /var/www/cgi-bin/」指定遇到路徑爲「/cgi-bin/」的url就轉向「/var/www/cgi-bin/」目錄下找文件,最終修改以下:
<IfModule mod_alias.c> <IfModule mod_cgi.c> Define ENABLE_USR_LIB_CGI_BIN </IfModule> <IfModule mod_cgid.c> Define ENABLE_USR_LIB_CGI_BIN </IfModule> <IfDefine ENABLE_USR_LIB_CGI_BIN> ScriptAlias /cgi-bin/ /var/www/cgi-bin/ <Directory "/var/www/cgi-bin/"> AllowOverride None Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch Require all granted AddHandler cgi-script .cgi .py </Directory> </IfDefine> </IfModule> # vim: syntax=apache ts=4 sw=4 sts=4 sr noet
注意兩點:
一是開頭的#!指定文件解析shell不可少且shell要改爲本身python所在路徑(不然報錯「End of script output before headers:」),由於咱們雖然指定了cgi處理.py文件,但本質上apache仍是不懂.py應該使用哪一個shell去處理.py文件。
二是大多數教程都是上來直接寫print(),我這裏特意寫成了一個方法的形式,主要是爲了強調cgi最終就是python helloworld.py這麼運行的,你最終輸出一個html響應就行,並不須要你上來就print()。
# 建立/var/www/cgi-bin/目錄 sudo mkdir /var/www/cgi-bin/ # 建立helloworld.py文件並寫入內容 # 我不清楚爲何sudo直接cat寫入會提示Permission denied因此搞這麼麻煩 sudo touch /var/www/cgi-bin/helloworld.py sudo chmod 777 /var/www/cgi-bin/helloworld.py sudo cat > /var/www/cgi-bin/helloworld.py << EOF #!/opt/miniconda3/bin/python def main(): print ("Content-type:text/html") print () # 空行,告訴服務器結束頭部 print ('<html>') print ('<head>') print ('<meta charset="utf-8">') print ('<title>Hello Word - My First CGI Programe !</title>') print ('</head>') print ('<body>') print ('<h2>Hello Word! This is my first CGI programe !</h2>') print ('</body>') print ('</html>') main() EOF # 若是自使使用vi寫入的文件那麼寫完後記得給文件添加可執行權限
因爲在前邊作了不少配置,爲了以防萬一建議在訪問前重啓一把apache:
sudo systemctl restart apache2
運行效果以下:
請求-響應數據包過程以下:
咱們在/var/www/cgi-bin目錄下新建一個error.py文件,寫入如下內容並保存(主要是沒有指定shell那一行運行會報錯):
def main(): print("Content-type:text/html") print() # 空行,告訴服務器結束頭部 print('<html>') print('<head>') print('<meta charset="utf-8">') print('<title>Hello Word - My First CGI Programe !</title>') print('</head>') print('<body>') print('<h2>Hello Word! This is my first CGI programe !</h2>') print('</body>') print('</html>') main()
重啓apache後訪問兩次error.py頁面兩次,而後查看錯誤日誌(默認是/var/log/apache2/error.log)末尾的內容,若是沒有意外應該是以下的兩個報錯;報錯沒有什麼問題,問題是咱們能夠看到兩次訪問報錯的進程id是不同的,這就認證了cgi每次都從新啓動一個進程的說法(本質差很少和本身手動運行python文件差很少)。
應該來講到上一大節咱們的內容已經都講完了,但一方面python是一種解析型語言到C++等編譯型語言是否是同樣總仍是不敢肯定,另外一方面按網上的說法C++對apache的配置徹底和python同樣,在已有環境仍是比較容易實現因此咱們來看一下。
第一步,在/var/www/cgi-bin目錄建立cplusplus.cpp文件、添加可執行權限、寫入如下內容:
#include <iostream> using namespace std; int main () { cout << "Content-type:text/html\r\n\r\n"; cout << "<html>\n"; cout << "<head>\n"; cout << "<title>Hello World - First CGI Program</title>\n"; cout << "</head>\n"; cout << "<body>\n"; cout << "<h2>Hello World! This is my first CGI program</h2>\n"; cout << "</body>\n"; cout << "</html>\n"; return 0; }
第二步,使用g++進行編譯(不能使用gcc否則會因gcc不會自動連接c++庫而報錯「cplusplus.cpp:(.text+0xa): undefined reference to `std::cout'」)並把輸出文件後輟名指定爲.cgi。
sudo g++ -o cplusplus.cgi cplusplus.cpp
因爲編譯成了可執行文件因此cpp文件不用指定解析器語句也就很好理解了,直接執行輸出以下(因此咱們是否能夠認爲web的本質就是中間件把程序輸出返回到前端?):
第三步,訪問頁面確認效果。以下圖:
參考:
https://www.runoob.com/python3/python3-cgi-programming.html
https://www.tutorialspoint.com/cplusplus/cpp_web_programming