Python學(xué)習(xí)教程:WEB開發(fā)——Python WSGI協(xié)議詳解

Web應(yīng)用程序開發(fā)
Web應(yīng)用程序的本質(zhì)是什么

簡單描述Web應(yīng)用程序的本質(zhì)紧唱,就是我們通過瀏覽器訪問互聯(lián)網(wǎng)上指定的網(wǎng)頁文件展示到瀏覽器上哥牍。

流程如下圖:


在這里插入圖片描述

從更深層次一點(diǎn)的技術(shù)角度來看荣恐,由以下幾個(gè)步驟:

  • 瀏覽器潭苞,將要請(qǐng)求的內(nèi)容按照HTTP協(xié)議發(fā)送服務(wù)端
  • 服務(wù)端庶诡,根據(jù)請(qǐng)求內(nèi)容找到指定的HTML頁面
  • 瀏覽器唬血,解析請(qǐng)求到的HTML內(nèi)容展示出來

HTTP協(xié)議的全稱是HyperText Transfer Protocol(超文本傳輸協(xié)議)

HTTP協(xié)議是我們常用的五層協(xié)議中的應(yīng)用層(5層從上到下是應(yīng)用層响逢,傳輸層绒窑,網(wǎng)絡(luò)層,數(shù)據(jù)鏈路層舔亭,物理層)些膨,HTTP協(xié)議中協(xié)定的內(nèi)容稱之為消息,消息主要包括消息頭——Header和消息體——Body钦铺。

客戶端請(qǐng)求時(shí)的消息稱為Request订雾,服務(wù)端響應(yīng)時(shí)的消息稱為Response.

Header:包括請(qǐng)求方法,HTTP版本矛洞,URI洼哎,狀態(tài)碼,COOKIE等

Body:是響應(yīng)或者請(qǐng)求時(shí)的內(nèi)容沼本,包含HTML,CSS,JS等

HTTP協(xié)議這里就不做過多的描述噩峦,可以到點(diǎn)擊這里深入了解HTTP協(xié)議

HTML的全稱是Hyper Text Markup Language(超文本標(biāo)記語言)

簡單點(diǎn)說,HTML 是一種由不同元素組成的標(biāo)記語言抽兆,它定義了網(wǎng)頁內(nèi)容的含義和結(jié)構(gòu)识补,所有我們?cè)跒g覽器中看到的內(nèi)容都是由一個(gè)一個(gè)的元素組成。除 HTML 以外的其它技術(shù)則通常用來描述一個(gè)網(wǎng)頁的表現(xiàn)與展示效果(如 CSS)辫红,或功能與行為(如 JavaScript)凭涂。

WEB開發(fā)的歷程

靜態(tài)開發(fā)

直接將寫好的HTML頁面放在服務(wù)器上,然后直接通過瀏覽器訪問指定服務(wù)器的文件厉熟。

動(dòng)態(tài)開發(fā)

隨著我們的需求變化單獨(dú)使用靜態(tài)開發(fā)已經(jīng)不能完全滿足我們导盅。
例如我們查看的頁面只有部分內(nèi)容會(huì)變化较幌,那我們?cè)偃ラ_發(fā)相同的頁面揍瑟。
一是開發(fā)上是一種重復(fù)工作,完全是一種浪費(fèi)乍炉。
二是數(shù)據(jù)量變化巨大時(shí)绢片,完全是跟不上速度滤馍,并且數(shù)據(jù)變化也不是定時(shí)更新。
為了應(yīng)對(duì)這種問題底循,動(dòng)態(tài)網(wǎng)頁技術(shù)也就誕生了巢株。早期的動(dòng)態(tài)網(wǎng)頁開發(fā)技術(shù)是CGI
CGI全稱:Common Gateway Interface,通用網(wǎng)關(guān)接口熙涤,它是一段程序阁苞,運(yùn)行在服務(wù)器上如:HTTP 服務(wù)器,
提供同客戶端 HTML 頁面的接口祠挫。
CGI 程序可以是 Python 腳本那槽,PERL 腳本,SHELL 腳本等舔,C 或者 C++ 程序等骚灸。
各種編程語言也針對(duì)動(dòng)態(tài)網(wǎng)頁開發(fā)給出不同的解決方案,JAVA的servlet慌植,Python的WSGI協(xié)議等

Python的WSGI協(xié)議也是我們本章要講的內(nèi)容

CGI流程

在這里插入圖片描述

WSGI的流程

在這里插入圖片描述

什么是WSGI

WSGI全稱是Web Server Gateway Interface甚牲,其主要作用是Web服務(wù)器與Python Web應(yīng)用程序或框架之間的建議標(biāo)準(zhǔn)接口,以促進(jìn)跨各種Web服務(wù)器的Web應(yīng)用程序可移植性蝶柿。

WSGI并不是框架而只是一種協(xié)議丈钙,我們可以將WSGI協(xié)議分成三個(gè)組件Application,Server只锭,Middleware和協(xié)議中傳輸?shù)膬?nèi)容著恩。

將這三個(gè)組件對(duì)映射到我們具體使用的組件是:

  • Server:常用的有uWSGI,gunicorn等

  • Application:Django蜻展,F(xiàn)lask等

  • Middleware: Flask等框架中的裝飾器

點(diǎn)擊這里查看官方關(guān)于WSGI協(xié)議的定義

組件Application

應(yīng)用程序喉誊,是一個(gè)可重復(fù)調(diào)用的可調(diào)用對(duì)象,在Python中可以是一個(gè)函數(shù)纵顾,也可以是一個(gè)類伍茄,如果是類的話要實(shí)現(xiàn)call方法,要求這個(gè)可調(diào)用對(duì)象接收2個(gè)參數(shù)施逾,返回一個(gè)內(nèi)容結(jié)果

接收的2個(gè)參數(shù)分別是environ和start_response敷矫。

  • environ是web服務(wù)器解析HTTP協(xié)議的一些信息,例如請(qǐng)求方法汉额,請(qǐng)求URI等信息構(gòu)成的一個(gè)Dict對(duì)象曹仗。
  • start_response是一個(gè)函數(shù),接收2個(gè)參數(shù)蠕搜,一個(gè)是HTTP狀態(tài)碼怎茫,一個(gè)HTTP消息中的響應(yīng)頭。

依照官方提供的示例用函數(shù)實(shí)現(xiàn)應(yīng)用程序

def simple_app(environ, start_response):
 """Simplest possible application object"""
 status = '200 OK'
 response_headers = [('Content-type', 'text/plain; charset=utf-8')]
 start_response(status, response_headers)
 
 return_body = []
 
 for key, value in environ.items():
 return_body.append("{} : {}".format(key, value))
 
 return_body.append("\nHello WSGI!")
 # 返回結(jié)果必須是bytes
 return ["\n".join(return_body).encode("utf-8")]

如果你依然在編程的世界里迷茫,
不知道自己的未來規(guī)劃轨蛤,
對(duì)python感興趣蜜宪,
這里推薦一下我的學(xué)習(xí)交流圈QQ群:895 797 751,
里面都是學(xué)習(xí)python的祥山,

組件Server

Web服務(wù)器圃验,主要是實(shí)現(xiàn)相應(yīng)的信息轉(zhuǎn)換,將網(wǎng)絡(luò)請(qǐng)求中的信息缝呕,按照HTTP協(xié)議將內(nèi)容拿出澳窑,同時(shí)按照WSGI協(xié)議組裝成新的數(shù)據(jù),同時(shí)將提供的start_response傳遞給Application供常。最后接收Application返回的內(nèi)容照捡,按照WSGI協(xié)議解析出。最終按照HTTP協(xié)議組織好內(nèi)容返回就完成了一次請(qǐng)求话侧。

Server操作的步驟如下

  1. 根據(jù)HTTP協(xié)議內(nèi)容構(gòu)建envrion
  2. 提供一個(gè)start_response函數(shù)栗精,接收HTTP STATU 和 HTTP HEADER
  3. 將envrion和start_response作為參數(shù)調(diào)用Application
  4. 接收Application返回的結(jié)果
  5. 按照HTTP協(xié)議,順序?qū)懭際TTP響應(yīng)頭(start_response接收)瞻鹏,HTTP響應(yīng)體(Application返回結(jié)果)

下面這個(gè)是pep3333協(xié)議中的一個(gè)server例子悲立,按照CGI請(qǐng)求的方式來實(shí)現(xiàn)。

import os, sys
enc, esc = sys.getfilesystemencoding(), 'surrogateescape'
def unicode_to_wsgi(u):
 # Convert an environment variable to a WSGI "bytes-as-unicode" string
 return u.encode(enc, esc).decode('iso-8859-1')
def wsgi_to_bytes(s):
 return s.encode('iso-8859-1')
def run_with_cgi(application):
    # 按照WSGI協(xié)議新博,構(gòu)建environ內(nèi)容
    # 1類 CGI相關(guān)的變量薪夕,此腳本就是用于cgi執(zhí)行赫悄,所以前面的web服務(wù)器已經(jīng)將CGI變量封裝好原献,這里直接使用
 environ = {k: unicode_to_wsgi(v) for k,v in os.environ.items()}
 # 2類 wsgi定義的變量
 environ['wsgi.input'] = sys.stdin.buffer
 environ['wsgi.errors'] = sys.stderr
 environ['wsgi.version'] = (1, 0)
 environ['wsgi.multithread'] = False
 environ['wsgi.multiprocess'] = True
 environ['wsgi.run_once'] = True
 if environ.get('HTTPS', 'off') in ('on', '1'):
 environ['wsgi.url_scheme'] = 'https'
 else:
 environ['wsgi.url_scheme'] = 'http'
 headers_set = []
 headers_sent = []
 def write(data):
     # 將內(nèi)容返回
 out = sys.stdout.buffer
 if not headers_set:
 raise AssertionError("write() before start_response()")
 elif not headers_sent:
 # Before the first output, send the stored headers
 status, response_headers = headers_sent[:] = headers_set
 out.write(wsgi_to_bytes('Status: %s\r\n' % status))
 for header in response_headers:
 out.write(wsgi_to_bytes('%s: %s\r\n' % header))
 out.write(wsgi_to_bytes('\r\n'))
 out.write(data)
 out.flush()
    
    
 def start_response(status, response_headers, exc_info=None):
 if exc_info:
 try:
 if headers_sent:
 # Re-raise original exception if headers sent
 raise exc_info[1].with_traceback(exc_info[2])
 finally:
 exc_info = None # avoid dangling circular ref
 elif headers_set:
 raise AssertionError("Headers already set!")
 headers_set[:] = [status, response_headers]
 # Note: error checking on the headers should happen here,
 # *after* the headers are set. That way, if an error
 # occurs, start_response can only be re-called with
 # exc_info set.
 return write
    
    # 將上面處理的參數(shù)交給應(yīng)用程序
 result = application(environ, start_response)
 try:
     # 將請(qǐng)求到的結(jié)果寫回埂淮。
 for data in result:
 if data: # don't send headers until body appears
 write(data)
 if not headers_sent:
 write('') # send headers now if body was empty
 finally:
 if hasattr(result, 'close'):
 result.close()

組件Middleware

中間件姑隅,可以理解為對(duì)應(yīng)用程序的一組裝飾器。

在應(yīng)用程序端看來倔撞,它可以提供一個(gè)類start_response函數(shù)讲仰,可以想start_response函數(shù)一樣接收HTTP STATU和Headers;和environ痪蝇。

在服務(wù)端看來鄙陡,他可以接收2個(gè)參數(shù),并且可以返回一個(gè)類Application對(duì)象躏啰。

下面看一個(gè)例子趁矾,記錄每次請(qǐng)求的消耗時(shí)間:

import time
class ResponseTimingMiddleware(object):
 """記錄請(qǐng)求耗時(shí)"""
 def __init__(self, app):
 self.app = app
 def __call__(self, environ, start_response):
 start_time = time.time()
 response = self.app(environ, start_response)
 response_time = (time.time() - start_time) * 1000
 timing_text = "記錄請(qǐng)求耗時(shí)中間件輸出\n\n本次請(qǐng)求耗時(shí): {:.10f}ms \n\n\n".format(response_time)
 response.append(timing_text.encode('utf-8'))
 return response

協(xié)議內(nèi)容

重點(diǎn)看environ有哪些內(nèi)容,這里面才是瀏覽器每次請(qǐng)求時(shí)的信息给僵。再深入一點(diǎn)探索毫捣,就是HTTP請(qǐng)求消息中的請(qǐng)求頭和請(qǐng)求體都是怎么定義及怎么回去的。

environ是一個(gè)字典,environ中要包含CGI定義的變量培漏,主要是將HTTP協(xié)議中的內(nèi)容,比如請(qǐng)求方法胡本,POST/GET牌柄,請(qǐng)求URI等,另外是WSGI協(xié)議自己定義的變量侧甫,比如請(qǐng)求body中要讀取的信息等珊佣。列一下主要變量項(xiàng)如下:

CGI相關(guān)變量

變量說明REQUEST_METHODPOST,GET等,HTTP請(qǐng)求的動(dòng)詞標(biāo)識(shí)SERVER_PROTOCOL服務(wù)器運(yùn)行的HTTP協(xié)議. 這里當(dāng)是HTTP/1.0.PATH_INFO附加的路徑信息, 由瀏覽器發(fā)出.QUERY_STRING請(qǐng)求URL的“?”后面的部分CONTENT_TYPEHTTP請(qǐng)求中任何Content-Type字段的內(nèi)容CONTENT_LENGTH標(biāo)準(zhǔn)輸入口的字節(jié)數(shù).HTTP_[變量]其他一些變量披粟,例如HTTP_ACCEPT咒锻,HTTP_REFERER等

上述內(nèi)容是動(dòng)態(tài)開發(fā)的根基,只有根據(jù)上述內(nèi)容才可以標(biāo)準(zhǔn)化的動(dòng)態(tài)處理請(qǐng)求守屉。

WSGI定義變量

變量說明wsgi.versionWSGI版本,要求是元組(1,0),標(biāo)識(shí)WSGI 1.0協(xié)議wsgi.url_scheme表示調(diào)用應(yīng)用程序的URL的協(xié)議惑艇,http或httpswsgi.input類文件對(duì)象,讀取HTTP請(qǐng)求體字節(jié)的輸入流wsgi.errors類文件對(duì)象拇泛,寫入錯(cuò)誤輸出的輸出流wsgi.multithread如果是多線程滨巴,則設(shè)置為True,否則為False俺叭。wsgi.multiprocess如果是多進(jìn)程恭取,則設(shè)置為True,否則為False熄守。wsgi.run_once如果只需要運(yùn)行一次蜈垮,設(shè)置為True

WSGI協(xié)議對(duì)于兩個(gè)輸入輸出流有一些方法必須要實(shí)現(xiàn)

流方法

wsgi.inputread(size)wsgi.inputreadline()wsgi.inputreadlines(hint)wsgi.inputiter()wsgi.errorsflush()wsgi.errorswrite(str)wsgi.errorswritelines(seq)

這些基本上就是WSGI協(xié)議中定義的主要變量,也基本上涵蓋了我們開發(fā)時(shí)所需要的變量裕照。

Server端按照協(xié)議的內(nèi)容生成這些environ字典攒发,然后將請(qǐng)求信息交給Application,Application根據(jù)這些信息確認(rèn)請(qǐng)求要處理的內(nèi)容晋南,然后返回響應(yīng)消息晨继。從頭順下來就是這個(gè)流程。

示例展示

Server端涉及到實(shí)現(xiàn)http相關(guān)內(nèi)容搬俊,我們直接使用python內(nèi)置wsgiref來實(shí)現(xiàn)紊扬,具體代碼如下:

import time
from wsgiref.simple_server import make_server
class ResponseTimingMiddleware(object):
 """記錄請(qǐng)求耗時(shí)"""
 def __init__(self, app):
 self.app = app
 def __call__(self, environ, start_response):
 start_time = time.time()
 response = self.app(environ, start_response)
 response_time = (time.time() - start_time) * 1000
 timing_text = "記錄請(qǐng)求耗時(shí)中間件輸出\n\n本次請(qǐng)求耗時(shí): {:.10f}ms \n\n\n".format(response_time)
 response.append(timing_text.encode('utf-8'))
 return response
def simple_app(environ, start_response):
 """Simplest possible application object"""
 status = '200 OK'
 response_headers = [('Content-type', 'text/plain; charset=utf-8')]
 start_response(status, response_headers)
 
 return_body = []
 
 for key, value in environ.items():
 return_body.append("{} : {}".format(key, value))
 
 return_body.append("\nHello WSGI!")
 # 返回結(jié)果必須是bytes
 return ["\n".join(return_body).encode("utf-8")]
# 創(chuàng)建應(yīng)用程序
app = ResponseTimingMiddleware(simple_app)
# 啟動(dòng)服務(wù),監(jiān)聽8080
httpd = make_server('localhost', 8080, app) 
httpd.serve_forever()

如果你依然在編程的世界里迷茫唉擂,
不知道自己的未來規(guī)劃餐屎,
對(duì)python感興趣,
這里推薦一下我的學(xué)習(xí)交流圈QQ群:895 797 751玩祟,
里面都是學(xué)習(xí)python的腹缩,

啟動(dòng)服務(wù)后,我們打開瀏覽器訪問http://localhost:8080,執(zhí)行結(jié)果如下藏鹊。

在這里插入圖片描述

上圖可以看到我們前面提到的中間件以及Application中執(zhí)行返回的結(jié)果全都實(shí)現(xiàn)润讥。

Python學(xué)習(xí)教程關(guān)于WSGI協(xié)議內(nèi)容就到這,可以說是非常良心盘寡,非常詳細(xì)了,大家用心學(xué)哈!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末楚殿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子竿痰,更是在濱河造成了極大的恐慌脆粥,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件影涉,死亡現(xiàn)場離奇詭異变隔,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蟹倾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門匣缘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鲜棠,你說我怎么就攤上這事孵户。” “怎么了岔留?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵夏哭,是天一觀的道長。 經(jīng)常有香客問我献联,道長竖配,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任里逆,我火速辦了婚禮进胯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘原押。我一直安慰自己胁镐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布诸衔。 她就那樣靜靜地躺著盯漂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪笨农。 梳的紋絲不亂的頭發(fā)上就缆,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音谒亦,去河邊找鬼竭宰。 笑死空郊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的切揭。 我是一名探鬼主播狞甚,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼廓旬!你這毒婦竟也來了哼审?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤嗤谚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后怔蚌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體巩步,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年桦踊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了椅野。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡籍胯,死狀恐怖竟闪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杖狼,我是刑警寧澤炼蛤,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站蝶涩,受9級(jí)特大地震影響理朋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绿聘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一嗽上、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧熄攘,春花似錦兽愤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哲思,卻和暖如春惯殊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背也殖。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工土思, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留务热,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓己儒,卻偏偏與公主長得像崎岂,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闪湾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容