以下測試主要是參考下列兩本書 :
# 一次搞定所有 Python Web 框架開發百科全書 (佳魁, 劉長龍) 第 5.3 節
# 科班出身的MVC網頁開發 : 使用Python + Django (佳魁, 王友釗)
以及下面這幾篇文章 :
# 化整為零的次世代網頁開發標準: WSGI
# python中WSGI是什麼,Python應用WSGI詳解
# Python Web开发最难懂的WSGI协议,到底包含哪些内容?
# 做python Web开发你要理解:WSGI & uwsgi
# 理解Python WSGI
要在 Web 伺服器上跑 Python 應用程式必須先了解 WSGI 介面, 這是專為 Python 而定義的後端通訊協定, 相關知識摘要整理如下 :
一. WSGI 規範 (協定) :
WSGI (Web Server Gateway Interface) 是源自 CGI 的 Python 網頁介面標準, 問世於 2003 年, 它定義了 Python 網頁應用程式 (web application) 與 Web 伺服器 (web server) 的溝通方式, 讓 Python 網頁應用程式可以跟 Web 伺服器溝通. 對於 Client-Server 運作模式而言, 所謂的溝通其實就是伺服器如何傳遞請求與應用程式如何回應罷了.
WSGI 是在 CGI 的基礎上專門為 Python 語言制定的後端通訊標準, 功能上可說是 CGI 的延伸, 但 WSGI 為 Python 做了更多的定義, 其規格定義詳見 Python 文件 PEP333 :
# https://www.python.org/dev/peps/pep-0333/
WSGI 協定包括了 Server 與 Application 兩部分 :
- WSGI Server :
接收客戶端請求打包後轉發給 Application; 然後接收 Application 的回應, 將其回傳給客戶端. - WSGI Application :
接收 server 轉發的客戶端請求, 處理後將結果傳回 Server. 這部分可以是單獨的 Application, 也可以是包覆著 Application 的多個 middleware 堆疊.
注意, WSGI 只是一個分別定義了 Server 與 Application 介面的規範, 本身不是一個實作軟體. 關於 middleware 的妙用, 可參考下面這篇 :
# 化整為零的次世代網頁開發標準: WSGI
WSGI 的兩個部分可以分別實作, 實作 Application 部分的就是各式各樣的 Python 網頁框架, 例如 Django, Flask, Tornado 等都是. 而實作 Server 部分的稱為 WSGI Server, 例如 Python 3 內建的 wsgiref, 介接 Apache 的 mod_wsgi, 或移植自 Unix 的 Gunicorn 等等都是 WSGI server 的實作. 更多 WSGI Server 參考 :
# Servers which support WSGI
二. Python 3 內建的 WSGI 伺服器 wsgiref :
由上面的概念圖可知 WSGI Server 的介面有兩個 :
- 與 Web 伺服器的介面
- 與 Python 應用程式的介面
網頁程式開發者需要處理的是 WSGI 伺服器的第二個介面, 即面向網頁應用程式的介面. WSGI 標準定義了一個簡單且形式固定的函數做為 Web 伺服器與 Python 網頁應用程式的溝通介面, 開發者只要按此介面撰寫網頁應用程式即可 :
def app_name(environ, start_response):
函數名稱 app_name 是自訂的, 兩個傳入參數 environ 與 start_response 即應用程式與 Web 伺服器的單向雙工溝通管道, environ 表示來訊; 而 start_response() 表示回訊 :
- environ : (來訊用)
字典型態的環境變數, 儲存 Web 伺服器傳來的請求相關資訊 - start_response(status, response_headers) : (回訊用)
回呼函數, 可讓應用程式起始一個回應, 其第一參數為回應狀態 (字串), 第二參數是回應標頭 (串列), 回應的本體 (body) 則用 return 傳回 HTML 代碼.
例如 :
#myapp.py
def application(environ, start_response):
status='200 OK'
response_headers=[('Content-type','text/plain')]
start_response(status, response_headers)
return [b'Hello World!n']
將此函數存成 myapp.py 即成為一個會回應 "Hello World!" 的應用程式了.
另外是 WSGI 伺服器部分, Python 3 的內建模組 wsgiref 的 simple_server 類別可用來實作一個簡易的 WSGI+HTTP 伺服器, 在測試開發階段可以用它做為 Web 伺服器的代用品. 只要呼叫 simple_server 的 make_server() 方法並傳入 host, port, 以及處理函數即可建立一個 Web 伺服器 :
wsgiref.simple_server.make_server(host, port, app)
詳細文件參考 :
# https://docs.python.org/2/library/wsgiref.html#module-wsgiref.simple_server
例如下面程式就實作了一個 Web 伺服器 :
#wsgi_server.py
from wsgiref.simple_server import make_server
from myapp import application
http_server=make_server('', 8000, application)
http_server.serve_forever()
注意, 在 Python 3, 傳回的回應字串必須加 b 以轉成 byte string, 否則會出現 "AttributeError: 'NoneType' object has no attribute 'split'" 錯誤.
也可以將上面的 Web 伺服器程式 wsgi_server.py 與應用程式 myapp.py 合併寫在單一檔案裡面, 例如 :
#helloworld.py
from wsgiref.simple_server import make_server
def application(environ, start_response):
status='200 OK'
response_headers=[('Content-type','text/plain')]
start_response(status, response_headers)
return [b'Hello World!']
http_server=make_server('localhost', 8000, application)
http_server.serve_forever()
執行結果是一樣的 :
D:\Python\test>python helloworld.py
127.0.0.1 - - [04/Apr/2019 20:09:20] "GET / HTTP/1.1" 200 12
127.0.0.1 - - [04/Apr/2019 20:09:20] "GET /favicon.ico HTTP/1.1" 200 12
三. uWSGI 伺服器 :
上面提到有很多實作 WSGI Server 介面的 WSGI 伺服器, 在上線運營的 Python 網站中常見的配置是 :
- Nginx + uWSGI
- Nginx + Gunicorn
- Apache + mod_wsgi
# https://uwsgi-docs.readthedocs.io/en/latest/Protocol.html
我在 Win10 上用 pip3 安裝結果失敗 :
D:\Python\test>pip3 install uwsgi
Collecting uwsgi
Downloading https://files.pythonhosted.org/packages/e7/1e/3dcca007f974fe4eb369bf1b8629d5e342bb3055e2001b2e5340aaefae7a/uwsgi-2.0.18.tar.gz (801kB)
Complete output from command python setup.py egg_info:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "C:\Users\User\AppData\Local\Temp\pip-install-0i8q8nuo\uwsgi\setup.py", line 3, in <module>
import uwsgiconfig as uc
File "C:\Users\User\AppData\Local\Temp\pip-install-0i8q8nuo\uwsgi\uwsgiconfig.py", line 8, in <module>
uwsgi_os = os.uname()[0]
AttributeError: module 'os' has no attribute 'uname'
----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in C:\Users\User\AppData\Local\Temp\pip-install-0i8q8nuo\uwsgi\
查詢網路, 似乎 uWSGI 不支援 Windows :
# https://github.com/unbit/uwsgi/issues/1930
# Windows 10安装uWSGI:不可行、失败了
在樹莓派上就能安裝了 :
pi@raspberrypi:~ $ pip3 install uwsgi
Collecting uwsgi
Using cached https://files.pythonhosted.org/packages/e7/1e/3dcca007f974fe4eb369bf1b8629d5e342bb3055e2001b2e5340aaefae7a/uwsgi-2.0.18.tar.gz
Building wheels for collected packages: uwsgi
Running setup.py bdist_wheel for uwsgi ... done
Stored in directory: /home/pi/.cache/pip/wheels/2d/0c/b0/f3ba1bbce35c3766c9dac8c3d15d5431cac57e7a8c4111c268
Successfully built uwsgi
Installing collected packages: uwsgi
Successfully installed uwsgi-2.0.18
安裝完畢用 pip3 show 顯示版本訊息為 2.0.18 版 :
pi@raspberrypi:~ $ pip3 show uwsgi
Name: uWSGI
Version: 2.0.18
Summary: The uWSGI server
Home-page: https://uwsgi-docs.readthedocs.io/en/latest/
Author: Unbit
Author-email: info@unbit.it
License: GPLv2+
Location: /home/pi/.local/lib/python3.5/site-packages
Requires:
安裝好後, 我在 /home/pi 底下編輯了一個網頁檔 myapp.py :
pi@raspberrypi:~ $ nano myapp.py
pi@raspberrypi:~ $ cat myapp.py
def application(environ, start_response):
status='200 OK'
response_headers=[('Content-type','text/plain')]
start_response(status, response_headers)
return [b'Hello World!']
然後在命令列開啟 uWSGI 伺服器讀取網頁 :
pi@raspberrypi:~ $ uwsgi --http :8080 --wsgi-file myapp.py
卻出現 "bash : uwsgi command not found" 錯誤訊息, why?
pi@raspberrypi:~ $ sudo pip3 install uWSGI
Collecting uWSGI
Using cached https://files.pythonhosted.org/packages/e7/1e/3dcca007f974fe4eb369bf1b8629d5e342bb3055e2001b2e5340aaefae7a/uwsgi-2.0.18.tar.gz
Building wheels for collected packages: uWSGI
Running setup.py bdist_wheel for uWSGI ... done
Stored in directory: /root/.cache/pip/wheels/2d/0c/b0/f3ba1bbce35c3766c9dac8c3d15d5431cac57e7a8c4111c268
Successfully built uWSGI
Installing collected packages: uWSGI
Successfully installed uWSGI-2.0.18
用 pip3 show 顯示套件資訊 :
pi@raspberrypi:~ pip3 show uWSGI
Name: uWSGI
Version: 2.0.18
Summary: The uWSGI server
Home-page: https://uwsgi-docs.readthedocs.io/en/latest/
Author: Unbit
Author-email: info@unbit.it
License: GPLv2+
Location: /home/pi/.local/lib/python3.5/site-packages
Requires:
pi@raspberryp
看起來跟 uwsgi 的幾乎一樣, 就大小寫不同而已. 再試試看用 uwsgi 指令啟動伺服器, 結果沒再出現 "bash : uwsgi command not found" 錯誤訊息, 而是出現 "uwsgi: unrecognized option" 錯誤訊息 :
pi@raspberrypi:~ $ uwsgi --http:8000 --wsgi-file myapp.py
uwsgi: unrecognized option '--http:8000'
getopt_long() error
查詢官網文件, 應該加上 "--http-websockets", 參考 :
果然加上 proxy 後就可以順利啟動伺服器了 :
*** Starting uWSGI 2.0.18 (32bit) on [Fri Apr 5 11:30:48 2019] ***
compiled with version: 6.3.0 20170516 on 05 April 2019 02:47:26
os: Linux-4.14.79+ #1159 Sun Nov 4 17:28:08 GMT 2018
nodename: raspberrypi
machine: armv6l
clock source: unix
detected number of CPU cores: 1
current working directory: /home/pi
detected binary path: /usr/local/bin/uwsgi
!!! no internal routing support, rebuild with pcre support !!!
*** WARNING: you are running uWSGI without its master process manager ***
your processes number limit is 3400
your memory page size is 4096 bytes
detected max file descriptor number: 1024
lock engine: pthread robust mutexes
thunder lock: disabled (you can enable it with --thunder-lock)
uWSGI http bound on :8080 fd 4
spawned uWSGI http 1 (pid: 21648)
uwsgi socket 0 bound to TCP address 127.0.0.1:37339 (port auto-assigned) fd 3
Python version: 3.5.3 (default, Sep 27 2018, 17:25:39) [GCC 6.3.0 20170516]
*** Python threads support is disabled. You can enable it with --enable-threads ***
Python main interpreter initialized at 0x1766130
your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
mapped 64392 bytes (62 KB) for 1 cores
*** Operational MODE: single process ***
WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x1766130 pid: 21647 (default app)
*** uWSGI is running in multiple interpreter mode ***
spawned uWSGI worker 1 (and the only) (pid: 21647, cores: 1)
[pid: 21647|app: 0|req: 1/1] 127.0.0.1 () {36 vars in 665 bytes} [Fri Apr 5 11:31:18 2019] GET / => generated 12 bytes in 1 msecs (HTTP/1.1 200) 1 headers in 45 bytes (1 switches on core 0)
[pid: 21647|app: 0|req: 2/2] 127.0.0.1 () {36 vars in 644 bytes} [Fri Apr 5 11:31:21 2019] GET /favicon.ico => generated 12 bytes in 1 msecs (HTTP/1.1 200) 1 headers in 45 bytes (1 switches on core 0)
瀏覽 localhost:8080 成功顯示網頁 :
參考 :
# 葉難: 在Raspbian上安裝nginx、PHP、Django與uWSGI
# Setting up Nginx and uWSGI for CGI scripting
# NGINX and uWSGI work but don't begin at startup
# uWSGI Install help
# How to properly host Flask application with Nginx and Guincorn
# Raspberry Pi + Django Home Server (Youtube)
# Running Flask under NGINX on the Raspberry Pi
# Setting up Nginx and uWSGI for CGI scripting
# 在树莓派上部署Nginx+uWSGI+Flask
# Nginx、Gunicorn在服务器中分别起什么作用?
# 使用uWSGI和nginx来设置Django和你的web服务器
# Django Uwsgi Nginx 實現生產環境部署
# https://coder.tw/?p=6375
# How to properly host Flask application with Nginx and Guincorn
# Raspberry Pi + Django Home Server (Youtube)
# Running Flask under NGINX on the Raspberry Pi
# Setting up Nginx and uWSGI for CGI scripting
# 在树莓派上部署Nginx+uWSGI+Flask
# Nginx、Gunicorn在服务器中分别起什么作用?
# 使用uWSGI和nginx来设置Django和你的web服务器
# Django Uwsgi Nginx 實現生產環境部署
# https://coder.tw/?p=6375
沒有留言:
張貼留言