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