乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Python寫一個(gè)簡(jiǎn)易的web服務(wù)器

       River_LaLaLa 2016-08-21


      Greg Wilson是Software Carpentry(為科學(xué)家和工程師提供在計(jì)算技能方面的速成課程)的創(chuàng)始人。他已經(jīng)在學(xué)術(shù)界和工業(yè)界工作了30年,是幾本計(jì)算方面的書,包括獲得2008年jolt獎(jiǎng)的《代碼之美》和《開源應(yīng)用程序體系結(jié)構(gòu)》的前兩卷的作者或者編輯。Greg于1993在愛丁堡大學(xué)獲得了計(jì)算機(jī)科學(xué)博士學(xué)位。

      介紹

      在過去的二十年里,網(wǎng)絡(luò)已經(jīng)改變了社會(huì)的方方面面,但是它的核心變化很少。大多數(shù)系統(tǒng)仍然遵守Tim Berners-Lee在25年前提出的規(guī)則。特別是,大多數(shù)web服務(wù)器仍然按照以前同樣的方式處理同種類的消息。

      本章將探討他們?nèi)绾巫龅竭@一點(diǎn)。與此同時(shí),它將探索開發(fā)人員如何創(chuàng)建不需要重新寫入來添加新特性的軟件系統(tǒng)。

      背景

      幾乎網(wǎng)絡(luò)上的每一個(gè)程序都在以互聯(lián)網(wǎng)協(xié)議(IP)為通信標(biāo)準(zhǔn)運(yùn)行。我們關(guān)心的其中一個(gè)通信標(biāo)準(zhǔn)是傳輸控制協(xié)議(TCP/ IP),它使得計(jì)算機(jī)之間的通信看起來像讀寫文件。

      使用IP網(wǎng)絡(luò)協(xié)議的程序通過sockets(套接字)進(jìn)行通信。每個(gè)socket(套接字)是一個(gè)點(diǎn)對(duì)點(diǎn)通信信道的一端,就像一個(gè)電話機(jī)是一個(gè)電話呼叫的一端。一個(gè)socket(套接字)由一個(gè)定義了特定機(jī)器和其端口號(hào)的IP地址構(gòu)成。 IP地址由4個(gè)8位的數(shù)字構(gòu)成,例如174.136.14.108(10100100.10001000.00001110.01101100);域名系統(tǒng)(DNS)把這些數(shù)字匹配給像這樣便于人們記憶的符號(hào)名。

      端口號(hào)是0-65535范圍內(nèi)的數(shù)字,定義了主機(jī)上的唯一的socket(套接字)。 (如果IP地址比作一個(gè)公司的電話號(hào)碼,那么端口號(hào)就像是分機(jī)。)端口0-1023被保留用于操作系統(tǒng)的使用;任何人都可以使用剩余的端口。

      超文本傳輸協(xié)議(HTTP)描述了一種程序通過IP交換數(shù)據(jù)的方法。 HTTP很簡(jiǎn)單:客戶端發(fā)送一個(gè)請(qǐng)求,指明它想通過socket(套接字)連接獲得的內(nèi)容,然后服務(wù)器發(fā)送一些數(shù)據(jù)作為響應(yīng)(如圖22.1)。數(shù)據(jù)可以從磁盤上的文件復(fù)制,或由程序動(dòng)態(tài)生成,或兩者的結(jié)合。


      一個(gè)HTTP請(qǐng)求的最重要的事情是,它只是文本:任何程序只要想要就可以創(chuàng)建或解析一個(gè)。不過,為了理解它,該文本必須含有如圖所示的部分。


      HTTP方法幾乎總是“GET”(獲取信息)或“POST”(提交表單數(shù)據(jù)或上傳文件)。 URL指定客戶端想要什么;它通常是磁盤上文件的路徑,如/research/experiments.html,但(這是至關(guān)重要的一部分)它完全取決于服務(wù)器來如何處理它。HTTP版本通常是“HTTP / 1.0”或“HTTP / 1.1”;兩者之間的差異對(duì)我們來說并不重要。

      HTTP頭文件是“鍵/值”對(duì),如下面三行所示:

      Accept: text/html

      Accept-Language: en, fr

      If-Modified-Since: 16-May-2005

      不像在哈希表中的鍵,鍵可能會(huì)在HTTP頭文件中出現(xiàn)任意次數(shù)。這允許request指定它想要接受的多種類型的內(nèi)容。

      最后,請(qǐng)求的主體是任何與請(qǐng)求相關(guān)以外的數(shù)據(jù)。這用于通過網(wǎng)頁表單提交數(shù)據(jù),上傳文件等等的時(shí)候。在最后一個(gè)頭和主體開頭之間必須有一個(gè)空白行表示頭文件的結(jié)束。

      一個(gè)叫Content-Length的頭文件,在請(qǐng)求主體中告訴服務(wù)器請(qǐng)求讀取多少字節(jié)。

      HTTP響應(yīng)格式類似于HTTP請(qǐng)求(如圖22.2):


      版本,頭文件和主體具有相同的形式和意義。狀態(tài)碼是一個(gè)數(shù)字,表示在處理請(qǐng)求的時(shí)候發(fā)生了什么:200的意思是“一切正常”,404表示“未找到”,以及其他代碼有其他的含義。狀態(tài)語以人類可讀短語復(fù)述信息,如“OK”或“未找到”。

      作為本章的目的,我們只需要了解HTTP的兩點(diǎn)。

      首先,它是無狀態(tài)的:每個(gè)請(qǐng)求是自己處理自己,服務(wù)器不記得一個(gè)請(qǐng)求與下一個(gè)請(qǐng)求之間的任何事情。如果應(yīng)用程序想要跟蹤什么如一個(gè)用戶的id,就必須自己跟蹤。

      追蹤常用的方法是使用cookie,它是一個(gè)從服務(wù)器端發(fā)送到客戶端然后客戶端稍后再返回到服務(wù)器端的短字符串。當(dāng)用戶執(zhí)行一些需要保存跨越多個(gè)請(qǐng)求的狀態(tài)的功能時(shí),服務(wù)器會(huì)創(chuàng)建一個(gè)新的cookie,并存儲(chǔ)在數(shù)據(jù)庫中,并且將其發(fā)送到她的瀏覽器。每次她的瀏覽器將cookie發(fā)送回去,服務(wù)器會(huì)用它來查看用戶正在做什么。

      我們需要了解HTTP的第二件事是,一個(gè)URL可以用參數(shù)來補(bǔ)充,以提供更多的信息。舉個(gè)例子,如果我們使用搜索引擎,我們不得不指定我們的搜索詞是什么。我們可以把這些添加到URL路徑中,但我們應(yīng)該做的是把參數(shù)添加到URL。我們通過添加 “?”做到這點(diǎn),后面跟著以'&'分隔的“key=value”對(duì)。例如,URL http://www.?q=Python要求谷歌來搜索Python相關(guān)的網(wǎng)頁:key值是字母“q”,value值為“Python。較長(zhǎng)的查詢http://www./search?q=Python&client=Firefox告訴Google我們正在使用Firefox,等等。我們可以傳遞任何我們想要的傳遞的參數(shù),但同樣,它是由在網(wǎng)站上運(yùn)行的應(yīng)用來決定要注意哪些參數(shù),以及如何解釋它們。

      當(dāng)然,如果 '?'和'&'是特殊字符,必須有方法避開他們,就是必須有個(gè)方法來把一個(gè)雙引號(hào)字符放進(jìn)用雙引號(hào)限定的字符串內(nèi)。 URL編碼標(biāo)準(zhǔn)用%后面跟著2位的代碼代表特殊字符,并以'+'字符代替空格。因此,搜索Google“grade= A+”(有空格),我們應(yīng)該使用URL http://www./search?q=grade+%3D+A%2B。

      打開sockets,構(gòu)建HTTP請(qǐng)求,解析響應(yīng)是乏味的,所以大多數(shù)人使用庫來完成大部分的工作。 Python提供了這樣一個(gè)稱作urllib2的庫(因?yàn)樗且粋€(gè)較早版本的庫urllib的升級(jí)版),但它暴露了很多大多數(shù)人永遠(yuǎn)不想關(guān)注的底層編碼。Requests庫比urllib2更容易使用。下面是一個(gè)使用它從AOSA圖書網(wǎng)站下載頁面的例子:


      request.get發(fā)送一個(gè)HTTP GET請(qǐng)求到服務(wù)器,并返回一個(gè)包含響應(yīng)的對(duì)象。該對(duì)象的status_code成員是響應(yīng)的狀態(tài)碼;它的content_length成員是響應(yīng)數(shù)據(jù)中的字節(jié)數(shù),text是實(shí)際的數(shù)據(jù)(在這種情況下,是一個(gè)HTML頁面)。

      hello,web

      現(xiàn)在,我們準(zhǔn)備寫我們的第一個(gè)簡(jiǎn)單的Web服務(wù)器?;舅枷敕浅:?jiǎn)單:

      1.等待有人來連接我們的服務(wù)器,發(fā)送HTTP請(qǐng)求;

      2.解析請(qǐng)求;

      3.找出它的要求;

      4.獲取數(shù)據(jù)(或動(dòng)態(tài)生成它);

      5.把數(shù)據(jù)格式化為HTML; 

      6.回發(fā)。

      從一個(gè)應(yīng)用程序到另一個(gè),步驟1,2和6是相同的,所以Python標(biāo)準(zhǔn)庫有一個(gè)名為BaseHTTPServer的模塊為我們做這些步驟。我們只需要關(guān)注步驟3-5,這是我們?cè)谙旅娴男〕绦蚶镒龅模?/p>


      庫的BaseHTTPRequestHandler類負(fù)責(zé)解析傳入的HTTP請(qǐng)求,并判斷它含有什么方法。如果方法是GET,類調(diào)用一個(gè)名為do_GET的方法。我們的RequestHandler類覆蓋此方法來動(dòng)態(tài)生成一個(gè)簡(jiǎn)單的頁面:文本被存儲(chǔ)在類級(jí)別的變量Page中,在我們發(fā)送一個(gè)200響應(yīng)碼后被發(fā)回客戶端,Content-Type頭文件,告訴客戶端把我們的數(shù)據(jù)和頁面長(zhǎng)度翻譯為HTML格式。 (調(diào)用end_headers方法插入空白行,來區(qū)分頭文件和頁面本身。)

      但RequestHandler并不是故事的全部:我們還需要最后三行來使服務(wù)器開始運(yùn)行。其中第一行定義服務(wù)器的地址為一個(gè)元組:空字符串意味著“在當(dāng)前計(jì)算機(jī)上運(yùn)行”,8080是端口號(hào)。然后,我們創(chuàng)建一個(gè)BaseHTTPServer.HTTPServer的實(shí)例,實(shí)例中含有服務(wù)器地址和我們的請(qǐng)求處理類作為參數(shù)的名稱,然后要求它永久運(yùn)行(這實(shí)際上意味著,直到我們使用Control-C殺死它才停止運(yùn)行)。

      如果我們?cè)诿钚兄羞\(yùn)行這個(gè)程序,它不顯示任何內(nèi)容:

      $ python server.py

      然后如果我們用我們的瀏覽器訪問http://localhost:8080,那么,在瀏覽器中我們得到這樣的內(nèi)容:

      Hello, web!

      在我們的shell中是這樣的內(nèi)容:

      127.0.0.1 - - [24/Feb/2014 10:26:28] 'GET / HTTP/1.1' 200 -

      127.0.0.1 - - [24/Feb/2014 10:26:28] 'GET /favicon.ico HTTP/1.1' 200 -

      第一行是直觀的:因?yàn)槲覀儧]有要求特定文件,我們的瀏覽器就要求'/'(服務(wù)器提供所有的根目錄)。出現(xiàn)第二行是因?yàn)槲覀兊臑g覽器會(huì)自動(dòng)發(fā)送第二個(gè)請(qǐng)求來請(qǐng)求一個(gè)名為/favicon.ico的圖像文件,如果圖像文件存在,它會(huì)在地址欄顯示為一個(gè)圖標(biāo)。

      顯示數(shù)值

      讓我們修改我們的Web服務(wù)器來顯示一些包含在HTTP請(qǐng)求中的值。 (在調(diào)試的時(shí)候,我們會(huì)相當(dāng)頻繁的做這個(gè),所以我們不妨做一些練習(xí))。為了保持我們的代碼簡(jiǎn)潔,我們將把創(chuàng)建頁面和發(fā)送頁面分開:


      send_page我們之前幾乎都有的:


      因?yàn)槲覀円@示的頁面模板僅僅是一串字符串,字符串包括帶有一些格式占位符的HTML表:


      填充方法是:


      該程序的主體是不變的:和以前一樣,它創(chuàng)建了一個(gè)HTTPServer類的實(shí)例,實(shí)例中含有一個(gè)地址和作為參數(shù)的請(qǐng)求處理器,然后永久服務(wù)請(qǐng)求。如果我們運(yùn)行它,并從瀏覽器中發(fā)送一個(gè)請(qǐng)求:http://localhost:8080/something.html,我們得到

        Date and time  Mon, 24 Feb 2014 17:17:12 GMT

        Client host    127.0.0.1

        Client port    54548

        Command        GET

        Path           /something.html

      請(qǐng)注意,我們沒有收到一個(gè)404錯(cuò)誤,即使something.html頁面文件在磁盤上不存在。這是因?yàn)閃eb服務(wù)器只是一個(gè)程序,當(dāng)它獲得一個(gè)請(qǐng)求時(shí),它可以做任意想做的事:發(fā)回在先前的請(qǐng)求里被命名的文件、提供隨機(jī)選擇的一個(gè)維基百科頁面,或我們編寫的任意的東西。

      提供靜態(tài)頁面服務(wù)

      明顯的,下一步是從磁盤啟動(dòng)服務(wù)頁面,而不是動(dòng)態(tài)生成。我們會(huì)通過改寫do_GET來開始:


      該方法假定提供Web服務(wù)器運(yùn)行目錄下的任何文件都是被允許的(使用os.getcwd函數(shù)來獲得當(dāng)前運(yùn)行目錄)。它把當(dāng)前運(yùn)行目錄與URL提供的路徑(庫將路徑自動(dòng)放入self.path中,并始終以 “/”開頭)相結(jié)合,來獲得用戶想要文件的路徑。

      如果路徑不存在,或者如果它不是一個(gè)文件,該方法通過拋出和捕獲異常報(bào)告錯(cuò)誤。如果路徑匹配一個(gè)文件,在另一方面,它調(diào)用一個(gè)幫助者方法命名為handle_file來讀取和返回內(nèi)容。這種方法只會(huì)讀取文件,并使用我們現(xiàn)有的send_content將其發(fā)送回客戶端:


      請(qǐng)注意,我們?cè)谝远M(jìn)制模式打開文件—the 'b' in 'rb'—,因此Python不會(huì)幫我們?nèi)ネㄟ^改變字節(jié)序列而使其看起來像一個(gè)Windows結(jié)束行。還需要注意的是在現(xiàn)實(shí)生活中,當(dāng)為它服務(wù)時(shí),讀取整個(gè)文件到內(nèi)存是個(gè)壞主意,該文件可能是數(shù)千兆字節(jié)的視頻數(shù)據(jù)。處理這種情況不在本章的范圍之內(nèi)。

      為了完成這個(gè)類,我們需要編寫錯(cuò)誤處理方法和錯(cuò)誤報(bào)告頁面模板:


      這個(gè)程序可用,但前提是我們別太細(xì)看。問題是,它總是返回狀態(tài)碼200,即使當(dāng)被請(qǐng)求的頁面不存在。在這種情況下,發(fā)回的頁面包含一個(gè)錯(cuò)誤信息,但由于我們的瀏覽器讀不懂英文,它不知道該請(qǐng)求其實(shí)失敗了。為了說清楚這點(diǎn),我們需要按如下修改handle_error和send_content:


      請(qǐng)注意,當(dāng)找不到文件時(shí)我們不拋出ServerException,而是產(chǎn)生一個(gè)錯(cuò)誤頁面。ServerException是為了傳遞服務(wù)器代碼中的內(nèi)部錯(cuò)誤信號(hào),即,我們弄錯(cuò)了某東西。在另一方面,通過handle_error創(chuàng)建的錯(cuò)誤頁面,出現(xiàn)在用戶犯了一些錯(cuò)誤的時(shí)候,即,給我們發(fā)送了一個(gè)不存在的文件的URL。(1)

      列出目錄

      作為我們的下一步,當(dāng)URL中的路徑是一個(gè)目錄而不是文件時(shí),我們可以教Web服務(wù)器顯示目錄列表。我們甚至可以更進(jìn)一步,讓它在那個(gè)目錄中以index.html文件顯示,并且文件不存在時(shí),只顯示目錄內(nèi)容的列表。

      但是,把這些規(guī)則寫進(jìn)do_GET將是一個(gè)錯(cuò)誤,因?yàn)榉椒ㄗ罱K將會(huì)成為長(zhǎng)長(zhǎng)一大團(tuán)控制特定行為的if聲明。正確的解決辦法是退后一步,解決一般性問題,指出如何處理URL。這里是重寫的do_GET方法:


      第一步是一樣的:指出被請(qǐng)求的東西的全路徑。盡管在那之后,代碼看起來相當(dāng)不同。該版本遍歷一組存儲(chǔ)在列表中的cases類來代替一堆的內(nèi)聯(lián)測(cè)試。每個(gè)case是一個(gè)有兩個(gè)方法的對(duì)象:方法test,它告訴我們它是否能夠處理請(qǐng)求,方法act,它實(shí)際上采取了一些行動(dòng)。我們一找到正確的case,我們馬上讓它處理請(qǐng)求并跳出循環(huán)。

      這三個(gè)case類復(fù)制我們之前服務(wù)器的行為:


      如下是我們?nèi)绾卧赗equestHandler類的頂部構(gòu)建case handlers的列表:


      現(xiàn)在,表面上看這使得我們的服務(wù)器更加復(fù)雜,而不是更簡(jiǎn)單:該文件已經(jīng)從74行增加至99行,并且有額外的間接尋址,沒有增加任何新的功能。這樣做的好處是,當(dāng)我們回到那個(gè)本章開始的任務(wù),如果有文件的話,就嘗試教我們的服務(wù)器來提供index.html頁面作為目錄,如果沒有的話,就提供目錄的列表。前者的handler程序是:


      在這里,幫助者方法index_path構(gòu)建index.html文件的路徑;把它放進(jìn)case handler中防止主函數(shù)RequestHandler雜亂。 test檢查路徑是否是包含index.html的目錄,act要求主函數(shù)請(qǐng)求處理器提供該頁面。

      唯一需要RequestHandler改變的是把一個(gè)case_directory_index_file對(duì)象添加到我們的Cases列表中:


      如果目錄中不包含index.html頁怎么辦呢?test方法和上面戰(zhàn)略性插入not相同,但act方法該怎么樣呢?他該怎么做?


      我們似乎陷入了困境。從邏輯上講,act方法應(yīng)創(chuàng)建并返回目錄列表,但是我們現(xiàn)有的代碼不允許:RequestHandler.do_GET調(diào)用了act,卻并不想要或處理返回值?,F(xiàn)在,讓我們?cè)赗equestHandler里添加一個(gè)方法來生成一個(gè)目錄列表,并從case handler的act方法調(diào)用該方法:


      CGI協(xié)議

      當(dāng)然,大多數(shù)人都不會(huì)想要編輯他們的網(wǎng)絡(luò)服務(wù)器的源代碼來增加新的功能。為了從這種不得不這么做的泥沼中解脫出來,服務(wù)器一直支持一個(gè)叫做通用網(wǎng)關(guān)接口(CGI)的機(jī)制,這為Web服務(wù)器為了滿足請(qǐng)求去運(yùn)行外部程序提供了一種標(biāo)準(zhǔn)方法。

      例如,假設(shè)我們希望服務(wù)器能在HTML頁面中顯示本地時(shí)間。我們?cè)谥挥袔仔写a的獨(dú)立的程序中就可以做到:


      為了使Web服務(wù)器運(yùn)行該程序,我們添加如下的case handler:


      test方法很簡(jiǎn)單:文件路徑是以.py結(jié)尾嗎?如果是,RequestHandler運(yùn)行此程序。


      這是非常不安全的:如果有人知道我們的服務(wù)器上的一個(gè)Python文件的路徑,我們就只能任他們運(yùn)行它,不管它訪問了什么數(shù)據(jù),它是否包含了一個(gè)無限循環(huán),或任何其他什么(2)

      先不管那個(gè),核心的思想很簡(jiǎn)單:

      1.在子進(jìn)程中運(yùn)行程序。

      2.捕獲任何子進(jìn)程發(fā)送到標(biāo)準(zhǔn)輸出的內(nèi)容。

      3.把內(nèi)容發(fā)送回發(fā)出請(qǐng)求的客戶端。

      完整的CGI協(xié)議比這個(gè)要豐富的多-尤其是它允許url中服務(wù)器傳遞給程序的參數(shù)運(yùn)行-但這些細(xì)節(jié)不影響系統(tǒng)的整體架構(gòu)...這再次變得相當(dāng)亂。 RequestHandler中最初有一個(gè)方法handle_file,用于處理內(nèi)容?,F(xiàn)在,我們已經(jīng)在list_dir和run_cgi表中加入了兩種特殊情況。這三個(gè)方法并不真正在所屬類起作用,因?yàn)樗麄冎饕善渌椒ㄊ褂谩?/p>

      修補(bǔ)程序很簡(jiǎn)單:為所有的case handler創(chuàng)建一個(gè)父類,當(dāng)(且僅當(dāng))他們由兩個(gè)或多個(gè)handler共享時(shí),把其他方法移入這個(gè)類。當(dāng)我們完成后,RequestHandler類看起來是這樣的:

      與此同時(shí)case handler的父類是:


      現(xiàn)有的文件(只是挑選隨機(jī)的一例)的handler是:


      討論

      我們?cè)即a和重構(gòu)版本之間的差異反映了兩個(gè)重要思想。首先是考慮類作為相關(guān)服務(wù)的集合。services.RequestHandler和base_case不作決定或采取行動(dòng);它們提供讓其他類使用去做這些事的工具。

      第二個(gè)是可擴(kuò)展性:人們可以通過編寫一個(gè)外部CGI程序,或通過添加一個(gè)case處理器的類,來給我們的Web服務(wù)器添加新功能。后者確實(shí)需要更改RequestHandler的一行(在Cases列表插入case handler),但我們可以通過Web服務(wù)器讀取一個(gè)配置文件和負(fù)載handler類來避免。在這兩種情況下,它們可以忽略更低層級(jí)的細(xì)節(jié),正如BaseHTTPRequestHandler類的作者已經(jīng)允許我們忽略處理套接字連接和解析HTTP請(qǐng)求的細(xì)節(jié)。

      這些想法通常是有用的;看看你能不能想辦法在自己的項(xiàng)目中使用它們。

      -------------------------------------------------------------------------------------------------------------------

      1.在本章中,包括一些情況下,狀態(tài)代碼404不合適的地方,我們會(huì)好幾次使用handle_error。當(dāng)你繼續(xù)閱讀,試著想想你將如何擴(kuò)展程序使?fàn)顟B(tài)響應(yīng)代碼可以很容易地在每種情況下被提供

      2.我們的代碼還使用了popen2庫函數(shù),該函數(shù)為支持subprocess模塊已經(jīng)被棄用了。然而,在這個(gè)例子中,popen2是較少分散使用的工具。


      英文原文:http:///en/500L/a-simple-web-server.html
      譯者:tiffanyws


        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多