這是個(gè)很長(zhǎng)的故事, 讓我們從Web服務(wù)器來開始。 Web服務(wù)器是個(gè)挺簡(jiǎn)單的東西,工作很簡(jiǎn)單,在80端口上監(jiān)聽,解析客戶端發(fā)過來的HTTP的請(qǐng)求, 然后把相對(duì)應(yīng)的HTML文件、Image等返回給客戶端就可以了。 像這樣: 這就是一個(gè)靜態(tài)內(nèi)容服務(wù)器,所謂靜態(tài)內(nèi)容,就是服務(wù)器端的內(nèi)容如HTML不會(huì)變化,每次請(qǐng)求都是一樣的。除非人們手工改了它。 實(shí)現(xiàn)這樣一個(gè)“玩具Web服務(wù)器”并不難,只要能了解服務(wù)器端Socket編程就可以了, 主要工作是編程處理HTTP協(xié)議的細(xì)節(jié)。 但是如果想再往前走一步,讓W(xué)eb服務(wù)器能產(chǎn)生動(dòng)態(tài)內(nèi)容,那就難了。 比如說來了一個(gè)HTTP請(qǐng)求,在其中攜帶者用戶名和密碼,要求你去數(shù)據(jù)庫做一個(gè)查詢,看看用戶是否存在。 POST /login user=xxxx&pwd=xxx 這個(gè)靜態(tài)的Web服務(wù)器就搞不定了,它根本,也不應(yīng)該去查詢數(shù)據(jù)庫。 怎么辦呢?你可以用某種語言(比如C語言)寫個(gè)程序, 來查詢數(shù)據(jù)庫,假設(shè)這個(gè)程序的名字叫db-query。 可是你將面對(duì)非常棘手的問題: Web服務(wù)器是個(gè)進(jìn)程,db-query也是個(gè)進(jìn)程,這倆貨之間怎么通信呢? (友情提示,下面內(nèi)容略顯枯燥,可跳過) 首先是參數(shù)的傳遞,一種辦法是這樣:對(duì)于每個(gè)動(dòng)態(tài)請(qǐng)求,Web服務(wù)器進(jìn)程創(chuàng)建一個(gè)db-query的子進(jìn)程,然后通過環(huán)境變量把參數(shù)傳遞過去。 web服務(wù)器: setenv('QUERY_STRING','user=xxxx&pwd=xxx') db-query子進(jìn)程 : param = getenv('QUERY_STRING')。 下一個(gè)問題:db-query這個(gè)子進(jìn)程獲得了用戶名和密碼,查詢了數(shù)據(jù)庫,怎么把查詢結(jié)果返回給瀏覽器? 有個(gè)很巧妙的辦法! 每個(gè)程序都有所謂的標(biāo)準(zhǔn)輸出(STDOUT),db-query只要調(diào)用printf這個(gè)函數(shù),數(shù)據(jù)就會(huì)輸出到STDOUT,我們就可以在黑乎乎的控制臺(tái)上看到了數(shù)據(jù)輸出了。 但是輸出到控制臺(tái)是萬萬不行的,我們得輸出到socket才可以發(fā)回瀏覽器。 每個(gè)瀏覽器和服務(wù)器的連接都是一個(gè)Socket, 每個(gè)socket都有一個(gè)文件描述符fd, 如果把查詢數(shù)據(jù)庫程序db-query的STDOUT重定向到那個(gè)fd,會(huì)發(fā)生什么? 沒錯(cuò)!db-query的所有輸出都直接發(fā)送的客戶端的socket了,Web服務(wù)器可以撒手不管了! 當(dāng)然,如果瀏覽器要看到的是HTML頁面, 那db-query這個(gè)程序就需要輸出HTML了。 這種方式就就是大名鼎鼎的CGI,當(dāng)你看到網(wǎng)址中有cgi-bin字樣的時(shí)候,很有可能就是用CGI實(shí)現(xiàn)的。 只要遵循CGI協(xié)議, 可以用任何語言來實(shí)現(xiàn)動(dòng)態(tài)的網(wǎng)站。 這是人類邁出的一大步,有了這一步,才能在網(wǎng)上購物,辦公,社交,聊天...... 你才能看到我這篇文章(嗯,也許騰訊把微信公眾號(hào)的文章都靜態(tài)化了, 請(qǐng)了解詳情的同學(xué)告知) 但是,CGI是非常復(fù)雜和笨拙的, 主要體現(xiàn)在: 第一,對(duì)每個(gè)請(qǐng)求,都得創(chuàng)建一個(gè)子進(jìn)程去執(zhí)行,這是個(gè)非常大的開銷。 第二,對(duì)程序員來說,編程極為痛苦,要操作環(huán)境變量,還需要直接在編程語言中輸出HTML! 麻煩不麻煩,難受不難受,上個(gè)世紀(jì)的程序員苦逼不苦逼? 怎么才能跳出苦海?必須得做到關(guān)注點(diǎn)的分離! 程序員的關(guān)注點(diǎn)是:拿到Http 請(qǐng)求中的數(shù)據(jù),執(zhí)行業(yè)務(wù), 然后輸出Http 響應(yīng)。 別的什么環(huán)境變量,重定向,別來煩我! 那就簡(jiǎn)單了,讓程序員寫個(gè)類,里邊是業(yè)務(wù)邏輯, 然后我們想辦法構(gòu)建一個(gè)HttpRequest對(duì)象和HttpResponse對(duì)象,傳遞給程序員的類讓他使用不就行了? 誰來創(chuàng)建這個(gè)HttpRequest和Response 對(duì)象, 然后調(diào)用程序員寫的類? 靜態(tài)Web服務(wù)器表示我不愿意,我就想管好我這一畝三分地,把靜態(tài)內(nèi)容給大家服務(wù)好。 Tomcat已經(jīng)迫不及待地要上場(chǎng)了,我來我來。碼農(nóng)朋友們,我送給你們一個(gè)規(guī)范,叫Servlet, 你們按照Servlet的規(guī)范來寫程序,放到我這里運(yùn)行,別的什么都不用管了。 程序員很高興,只需要寫簡(jiǎn)單的Servlet就行了,HttpRequest和HttpResponse對(duì)象由Tomcat來創(chuàng)建,可以從HttpRequest中獲得Header, Cookie, QueryString 等信息, 從HttpResponse中獲得輸出流,直接向?yàn)g覽器輸出結(jié)果, 簡(jiǎn)單又直接。 Tomcat還鄭重向大家聲明:對(duì)于每個(gè)請(qǐng)求,我只會(huì)用一個(gè)線程來出來,線程的開銷可比進(jìn)程小多了。 對(duì)于那個(gè)在代碼中混雜HTML的問題怎么處理? Tomcat也有辦法, 可以在HTML混雜代碼!這就是JSP。執(zhí)行期其實(shí)會(huì)被編譯成Servlet。 (碼農(nóng)翻身注:請(qǐng)移步《JSP:一個(gè)裝配工的沒落》) 你看,責(zé)任分離了,每個(gè)人只要辦好自己的事情就好。 (注:實(shí)際上,我們不會(huì)在Servlet中寫業(yè)務(wù)邏輯, Servlet現(xiàn)在通常是一個(gè)通往框架的入口。) CGI表示不服:遵循我的協(xié)議,任何語言都可以來實(shí)現(xiàn)動(dòng)態(tài)網(wǎng)站,你Servlet只是Java規(guī)范,不管別的語言了? Servlet規(guī)范確實(shí)沒法跨語言實(shí)現(xiàn),那要是Python也想做動(dòng)態(tài)Web網(wǎng)站,該怎么辦? 既然已經(jīng)認(rèn)識(shí)到動(dòng)態(tài)網(wǎng)站的本質(zhì)了, 可以采用類似的思想來處理嘛! 我們?yōu)镻ython也定義一個(gè)規(guī)范,叫做WSGI (Web Service Gateway Interface)。 讓程序員寫個(gè)類或者函數(shù)(稱為wsgi application),在其中實(shí)現(xiàn)邏輯。讓某個(gè)動(dòng)態(tài)服務(wù)器(稱為wsgi server)把Http Request和Response傳遞給它,就可以執(zhí)行了。 但是Python表示:我不喜歡你們Java 那一套啰里啰嗦的類,HttpRequest 不就是一些key value嗎?放到我鐘愛的dict中多好 !我把它叫做enviroment, HttpResponse也沒必要,直接用函數(shù)的返回值(確切說是一個(gè)可迭代對(duì)象)就好。 看看,是不是和Java 的Servlet 很像?(當(dāng)然,忽略了很多細(xì)節(jié)。) 從本質(zhì)上來說,都是為了關(guān)注點(diǎn)的分離: 1. 用一個(gè)動(dòng)態(tài)內(nèi)容服務(wù)器(wsgi server,Tomcat等)來接受并且封裝HTTP 請(qǐng)求,降低程序員的負(fù)擔(dān)。 2. 程序員只需要遵循約定(servlet,wsgi)就可以輕松實(shí)現(xiàn)自己的業(yè)務(wù),不用關(guān)注系統(tǒng)的處理細(xì)節(jié)。 如果你先學(xué)的Java,通過Servlet理解了動(dòng)態(tài)內(nèi)容網(wǎng)站的本質(zhì)和解決問題思路,再看到Python的wsgi,一眼就能看透,學(xué)起來飛快,反過來也是如此。 Web服務(wù)器的例子還比較簡(jiǎn)單,但是也體現(xiàn)出了這個(gè)道理:遇到問題要深度思考,努力看到本質(zhì),這樣才能舉一反三。 ![]() 劉欣,前IBM架構(gòu)師,近20年從業(yè)經(jīng)驗(yàn),'碼農(nóng)翻身'公眾號(hào)作者,暢銷書《碼農(nóng)翻身》作者,用故事講解技術(shù)是拿手好戲。撥開技術(shù)迷霧,輕松了解技術(shù)本質(zhì),從'碼農(nóng)翻身'開始。 |
|
|