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