一天,有一個正在散步的婦人恰好路過一個建筑工地,看到三個正在工作的工人。她問第一個人:“你在做什么?”第一個人沒好氣地喊道:“你沒看到我在砌磚嗎?”婦人對這個答案不滿意,于是問第二個人:“你在做什么?”第二個人回答說:“我在建一堵磚墻。”說完,他轉(zhuǎn)向第一個人,跟他說:“嗨,你把墻砌過頭了。去把剛剛那塊磚弄下來!”然而,婦人對這個答案依然不滿意,于是又問了第三個人相同的問題。第三個人仰頭看著天電腦做本地服務(wù)器,對她說:“我在建造世界上最大的教堂。”當(dāng)他回答時,第一個人和第二個人在為剛剛砌錯的磚而爭吵。他轉(zhuǎn)向那兩個人,說:“不用管那塊磚了。這堵墻在室內(nèi),它會被水泥填平,沒人會看見它的。去砌下一層吧。”
這個故事告訴我們:如果你能夠理解整個系統(tǒng)的構(gòu)造,了解系統(tǒng)的各個部件如何相互結(jié)合(如磚、墻還有整個教堂),你就能夠更快地定位及修復(fù)問題(那塊砌錯的磚)。
如果你想從頭開始創(chuàng)造一個 Web 服務(wù)器,那么你需要做些什么呢?
我相信,如果你想成為一個更好的開發(fā)者,你必須對日常使用的軟件系統(tǒng)的內(nèi)部結(jié)構(gòu)有更深的理解,包括編程語言、編譯器與解釋器、數(shù)據(jù)庫及操作系統(tǒng)、Web 服務(wù)器及 Web 框架。而且,為了更好更深入地理解這些系統(tǒng),你必須從頭開始,用一磚一瓦來重新構(gòu)建這個系統(tǒng)。
荀子曾經(jīng)用這幾句話來表達(dá)這種思想:
“不聞不若聞之。I hear and I .”
“聞之不若見之。I see and I .”
“知之不若行之。I do and I .”
我希望你現(xiàn)在能夠意識到,重新建造一個軟件系統(tǒng)來了解它的工作方式是一個好主意。
在這個由三篇文章組成的系列中,我將會教你構(gòu)建你自己的 Web 服務(wù)器。我們開始吧~
先說首要問題:Web 服務(wù)器是什么?
簡而言之,它是一個運行在一個物理服務(wù)器上的網(wǎng)絡(luò)服務(wù)器(啊呀,服務(wù)器套服務(wù)器),等待客戶端向其發(fā)送請求。當(dāng)它接收請求后,會生成一個響應(yīng),并回送至客戶端。客戶端和服務(wù)端之間通過 HTTP 協(xié)議來實現(xiàn)相互交流。客戶端可以是你的瀏覽器,也可以是使用 HTTP 協(xié)議的其它任何軟件。
最簡單的 Web 服務(wù)器實現(xiàn)應(yīng)該是什么樣的呢?這里我給出我的實現(xiàn)。這個例子由 寫成,即使你沒聽說過 (它是一門超級容易上手的語言,快去試試看!),你也應(yīng)該能夠從代碼及注釋中理解其中的理念:
import socket
HOST, PORT = '', 8888
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listen_socket.bind((HOST, PORT))
listen_socket.listen(1)
print 'Serving HTTP on port %s ...' % PORT
while True:
? ?client_connection, client_address = listen_socket.accept()
? ?request = client_connection.recv(1024)
? ?print request
? ?http_response = """\
HTTP/1.1 200 OK
Hello, World!
"""
? ?client_connection.sendall(http_response)
? ?client_connection.close()
將以上代碼保存為 .py,或者直接從[1]上下載這個文件。然后,在命令行中運行這個程序。像這樣:
$ python webserver1.py
Serving HTTP on port 8888 …
現(xiàn)在,在你的網(wǎng)頁瀏覽器的地址欄中輸入 URL::8888/hello ,敲一下回車,然后來見證奇跡。你應(yīng)該看到“Hello, World!”顯示在你的瀏覽器中,就像下圖那樣:
說真的,快去試一試。你做實驗的時候,我會等著你的。
完成了?不錯!現(xiàn)在我們來討論一下它實際上是怎么工作的。
首先我們從你剛剛輸入的 Web 地址開始。它叫URL[2],這是它的基本結(jié)構(gòu):
URL 是一個 Web 服務(wù)器的地址,瀏覽器用這個地址來尋找并連接 Web 服務(wù)器,并將上面的內(nèi)容返回給你。在你的瀏覽器能夠發(fā)送 HTTP 請求之前,它需要與 Web 服務(wù)器建立一個 TCP 連接。然后會在 TCP 連接中發(fā)送 HTTP 請求,并等待服務(wù)器返回 HTTP 響應(yīng)。當(dāng)你的瀏覽器收到響應(yīng)后,就會顯示其內(nèi)容,在上面的例子中,它顯示了“Hello, World!”。
我們來進一步探索在發(fā)送 HTTP 請求之前,客戶端與服務(wù)器建立 TCP 連接的過程。為了建立鏈接,它們使用了所謂“套接字”。我們現(xiàn)在不直接使用瀏覽器發(fā)送請求,而在命令行中使用來人工模擬這個過程。
在你運行 Web 服務(wù)器的電腦上,在命令行中建立一個 會話,指定一個本地域名,使用端口 8888,然后按下回車:
$ telnet localhost 8888
Trying 127.0.0.1 …
Connected to localhost.
這個時候,你已經(jīng)與運行在你本地主機的服務(wù)器建立了一個 TCP 連接。在下圖中,你可以看到一個服務(wù)器從頭開始,到能夠建立 TCP 連接的基本過程。
在同一個 會話中電腦做本地服務(wù)器,輸入GET /hello HTTP/1.1,然后輸入回車:
$ telnet localhost 8888
Trying 127.0.0.1 …
Connected to localhost.
GET /hello HTTP/1.1
HTTP/1.1 200 OK
Hello, World!
你剛剛手動模擬了你的瀏覽器(的工作)!你發(fā)送了 HTTP 請求,并且收到了一個 HTTP 應(yīng)答。下面是一個 HTTP 請求的基本結(jié)構(gòu):
HTTP 請求的第一行由三部分組成:HTTP 方法(GET,因為我們想讓我們的服務(wù)器返回一些內(nèi)容),以及標(biāo)明所需頁面的路徑/hello,還有協(xié)議版本。
為了簡單一些,我們剛剛構(gòu)建的 Web 服務(wù)器完全忽略了上面的請求內(nèi)容。你也可以試著輸入一些無用內(nèi)容而不是“GET /hello HTTP/1.1”,但你仍然會收到一個“Hello, World!”響應(yīng)。
一旦你輸入了請求行并敲了回車,客戶端就會將請求發(fā)送至服務(wù)器;服務(wù)器讀取請求行,就會返回相應(yīng)的 HTTP 響應(yīng)。
下面是服務(wù)器返回客戶端(在上面的例子里是 )的響應(yīng)內(nèi)容:
我們來解析它。這個響應(yīng)由三部分組成:一個狀態(tài)行HTTP/1.1 200 OK,后面跟著一個空行,再下面是響應(yīng)正文。
HTTP 響應(yīng)的狀態(tài)行 HTTP/1.1 200 OK 包含了 HTTP 版本號,HTTP 狀態(tài)碼以及 HTTP 狀態(tài)短語“OK”。當(dāng)瀏覽器收到響應(yīng)后,它會將響應(yīng)正文顯示出來,這也就是為什么你會在瀏覽器中看到“Hello, World!”。
以上就是 Web 服務(wù)器的基本工作模型。總結(jié)一下:Web 服務(wù)器創(chuàng)建一個處于監(jiān)聽狀態(tài)的套接字,循環(huán)接收新的連接。客戶端建立 TCP 連接成功后,會向服務(wù)器發(fā)送 HTTP 請求,然后服務(wù)器會以一個 HTTP 響應(yīng)做應(yīng)答,客戶端會將 HTTP 的響應(yīng)內(nèi)容顯示給用戶。為了建立 TCP 連接,客戶端和服務(wù)端均會使用套接字。
現(xiàn)在,你應(yīng)該了解了 Web 服務(wù)器的基本工作方式,你可以使用瀏覽器或其它 HTTP 客戶端進行試驗。如果你嘗試過、觀察過,你應(yīng)該也能夠使用 ,人工編寫 HTTP 請求,成為一個“人形” HTTP 客戶端。
現(xiàn)在留一個小問題:“你要如何在不對程序做任何改動的情況下,在你剛剛搭建起來的 Web 服務(wù)器上適配 , Flask 或 應(yīng)用呢?”
我會在本系列的第二部分中來詳細(xì)講解。敬請期待。
順便,我在撰寫一本名為《搭個 Web 服務(wù)器:從頭開始》的書。這本書講解了如何從頭開始編寫一個基本的 Web 服務(wù)器,里面包含本文中沒有的更多細(xì)節(jié)。訂閱郵件列表,你就可以獲取到這本書的最新進展,以及發(fā)布日期。
via:
作者:[3]譯者:[4]校對:wxy[5]
本文由LCTT[6]原創(chuàng)編譯,Linux中國[7]榮譽推出
[1]:
[2]:
[3]:
[4]:
[5]:
[6]:
[7]: