有天一個女士出門散步,路過一個建築工地,看到三個男人在幹活。她問第一個男人,“你在幹什麼呢?”,第一個男人被問得很煩,咆哮道,“你沒看到我在碼磚嗎?”。她對回答不滿意,然後問第二個男人他在幹什麼。第二個男人回答,“我正在砌牆”,然後轉移注意力到第一個男人,他說,“嘿,你碼過頭了,你要把最後一塊磚拿掉。”。她還是對回答不滿意,然後問第三個男人在幹什麼。第三個男人仰望著天空對她說,“我正在建造世界上最大的教堂。”。當他站在那裡仰望天空的時候,另外兩個男人開始爭論磚位置不對的問題。第三個男人轉向前兩個男人說,“嘿,夥計們,別擔心那塊磚了,那是裡面的牆,它會被灰泥堵塞起來,然後沒人會看到那塊磚。去另一層幹活吧。“
故事的寓意是說,當你瞭解整個系統,理解不同的部分如何組織到一起的(磚、牆、教堂),你就能找出問題並快速解決之(磚位置不對)。
這跟從零開始搭建你的WEB伺服器有什麼關係呢?
我相信,要成為優秀的開發者,你必須對你每天都用的底層的軟體系統有進一步的理解,包括程式語言、編譯器和直譯器、資料庫和作業系統、WEB伺服器和WEB框架。為了更好更深入的理解這些系統,你必須從零開始一塊磚地,一面牆地,重建它們。
子曰:聞之我也野,視之我也饒,行之我也明
1 |
“我看過的,我還記得。” |
1 |
“我做過的,我都理解了。” |
(子曰:聞之我也野,視之我也饒,行之我也明)
此時我希望你能夠相信,從重建不同的軟體系統來開始來學習它們是如何工作的,是一個好主意。
在這個由3部分組成的系列文章中,我會向你展示怎樣搭建一個基本的WEB伺服器。我們們開始吧。
重中之重,什麼是WEB伺服器?
簡而言之,它是一個位於一個物理伺服器上的網路伺服器(呀,伺服器上的伺服器),它等待客戶端傳送請求。當它接收到一個請求,就會生成一個響應並回發給客戶端。客戶端和伺服器使用HTTP協議通訊。客戶端可以是瀏覽器或者別的使用HTTP協議的軟體。
一個非常簡單的WEB伺服器實現長什麼樣呢?以下是我寫的一個。例子是用Python語言寫的,但是即使你不會Python(它是一個非常易學的語言,試試!),你仍然可以通過程式碼和下面的解釋理解相關概念:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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() |
把上面的程式碼儲存到webserver1.py或者直接從GitHub下載,然後像下面這樣在命令列執行它
1 2 |
; html-script: false ]$ python webserver1.py Serving HTTP on port 8888 … |
現在在你的WEB瀏覽器位址列裡輸入以下URL http://localhost:8888/hello,敲回車,見證奇蹟的時刻。你會看到瀏覽器顯示”Hello, World!“,像這樣:
認真做一下吧,我會等你的。
做完了?很好。現在我們討論一下它到底怎麼工作的。
首先我們從你剛才鍵入的WEB地址開始。它叫URL,這是它的基本結構:
這個就表示怎樣告訴瀏覽器要查詢和連線的WEB伺服器地址,和你要獲取的伺服器上的頁面(路徑)。但是在瀏覽器傳送HTTP請求前,瀏覽器需要先和WEB伺服器建立TCP連線。然後瀏覽器在TCP連線上傳送HTTP請求,然後等待伺服器回發HTTP響應。當瀏覽器接收到響應後,顯示響應,在本次例子中,瀏覽器顯示“Hello, World!”。
我們再詳細探索一下客戶端和伺服器在傳送HTTP請求和響應前如何建立TCP連線的。在建立連線,它們必須使用所謂的sockets。用你命令列下的telnet手動模擬瀏覽器吧,而不是直接使用瀏覽器。
在執行WEB伺服器的同一臺電腦上,在命令列啟動一個telnet會話,指定連線到localhost主機,連線埠為8888,然後按回車:
1 2 3 |
$ telnet localhost 8888 Trying 127.0.0.1 … Connected to localhost. |
此時,你已經和執行在你本地主機的伺服器建立了TCP連線,已經準備好傳送並接收HTTP訊息了。下圖中你可以看到一個伺服器要經過的標準步驟,然後才能接受新的TCP連線。
在同一個telnet會話中,輸入 GET /hello HTTP/1.1然後敲回車:
1 2 3 4 5 6 7 |
$ telnet localhost 8888 Trying 127.0.0.1 … Connected to localhost. GET /hello HTTP/1.1 HTTP/1.1 200 OK Hello, World! |
你完成了手動模擬瀏覽器!你傳送了一個HTTP請求並得到了一個HTTP響應。這是HTTP請求的基本結構:
HTTP請求由行組成。行指示了HTTP方法(GET,因為我們請求我們的伺服器返回給我們一些東西)、代表我們想要的伺服器上的“頁面”的路徑 /hello和協議版本。
為了簡單起見,此時我們的WEB伺服器完全忽略了上面的請求行。你也可以輸入任何垃圾字元取代“GET /hello HTTP/1.1”,你仍然會得到“Hello, World!”響應。
一旦你輸入了請求行,敲了回車,客戶端就傳送請求給伺服器,伺服器讀取請求行,列印出來然後返回相應的HTTP響應。
以下是伺服器回發給客戶端(這個例子中是telnet)的HTTP響應:
我們們分析一下它,響應包含了狀態行HTTP/1.1 200 OK,隨後一個必須的空行,和HTTP響應body。
響應狀態行TTP/1.1 200 OK包含了HTTP版本,HTTP狀態碼和HTTP狀態碼理由短語OK。瀏覽器得到響應時,它就顯示響應的body,所以你就看到了“Hello, World!”
這就是WEB瀏覽器怎麼工作的基本模型。總結來說:WEB伺服器建立一個監聽socket然後開始迴圈接受新連線。客戶端初始化一個TCP連線,在連線成功後,客戶端傳送HTTP請求到伺服器,伺服器響應一個顯示給使用者的HTTP響應。客戶端和伺服器都使用socket建立TCP連線。
你現在你擁有了一個非常基礎的WEB伺服器,你可以用瀏覽器或其他的HTTP客戶端測試它。正如你看到的,使用telnet手動輸入HTTP請求,你也就成了一個人肉 HTTP 客戶端。
對你來說有一個問題:“怎樣在你的剛完成的WEB伺服器下執行 Django 應用、Flask 應用和 Pyramid 應用?在不單獨修改伺服器來適應這些不同的 WEB 框架的情況下。”
我會在本系列的第2部分秀給你看的。繼續收看。
順便說下,我在寫一本書《一起構建WEB伺服器:第一步》,它解釋了從零開始寫一個基本的WEB伺服器,還更詳細地講解了我上面提到的話題。訂閱郵件組來獲取關於書籍和釋出時間和最近更新。
靈感來自於 Lead with a Story: A Guide to Crafting Business Narratives That Captivate, Convince, and Inspire