Linux中國

搭個 Web 伺服器(一)

一天,有一個正在散步的婦人恰好路過一個建築工地,看到三個正在工作的工人。她問第一個人:「你在做什麼?」第一個人沒好氣地喊道:「你沒看到我在砌磚嗎?」婦人對這個答案不滿意,於是問第二個人:「你在做什麼?」第二個人回答說:「我在建一堵磚牆。」說完,他轉向第一個人,跟他說:「嗨,你把牆砌過頭了。去把剛剛那塊磚弄下來!」然而,婦人對這個答案依然不滿意,於是又問了第三個人相同的問題。第三個人仰頭看著天,對她說:「我在建造世界上最大的教堂。」當他回答時,第一個人和第二個人在為剛剛砌錯的磚而爭吵。他轉向那兩個人,說:「不用管那塊磚了。這堵牆在室內,它會被水泥填平,沒人會看見它的。去砌下一層吧。」

這個故事告訴我們:如果你能夠理解整個系統的構造,了解系統的各個部件如何相互結合(如磚、牆還有整個教堂),你就能夠更快地定位及修復問題(那塊砌錯的磚)。

如果你想從頭開始創造一個 Web 伺服器,那麼你需要做些什麼呢?

我相信,如果你想成為一個更好的開發者,你必須對日常使用的軟體系統的內部結構有更深的理解,包括編程語言、編譯器與解釋器、資料庫及操作系統、Web 伺服器及 Web 框架。而且,為了更好更深入地理解這些系統,你必須從頭開始,用一磚一瓦來重新構建這個系統。

荀子曾經用這幾句話來表達這種思想:

不聞不若聞之。 I hear and I forget.

聞之不若見之。 I see and I remember.

知之不若行之。 I do and I understand.

我希望你現在能夠意識到,重新建造一個軟體系統來了解它的工作方式是一個好主意。

在這個由三篇文章組成的系列中,我將會教你構建你自己的 Web 伺服器。我們開始吧~

先說首要問題:Web 伺服器是什麼?

簡而言之,它是一個運行在一個物理伺服器上的網路伺服器(啊呀,伺服器套伺服器),等待客戶端向其發送請求。當它接收請求後,會生成一個響應,並回送至客戶端。客戶端和服務端之間通過 HTTP 協議來實現相互交流。客戶端可以是你的瀏覽器,也可以是使用 HTTP 協議的其它任何軟體。

最簡單的 Web 伺服器實現應該是什麼樣的呢?這裡我給出我的實現。這個例子由 Python 寫成,即使你沒聽說過 Python(它是一門超級容易上手的語言,快去試試看!),你也應該能夠從代碼及注釋中理解其中的理念:

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 上下載這個文件。然後,在命令行中運行這個程序。像這樣:

$ python webserver1.py
Serving HTTP on port 8888 …

現在,在你的網頁瀏覽器的地址欄中輸入 URL:http://localhost:8888/hello ,敲一下回車,然後來見證奇蹟。你應該看到「Hello, World!」顯示在你的瀏覽器中,就像下圖那樣:

說真的,快去試一試。你做實驗的時候,我會等著你的。

完成了?不錯!現在我們來討論一下它實際上是怎麼工作的。

首先我們從你剛剛輸入的 Web 地址開始。它叫 URL,這是它的基本結構:

URL 是一個 Web 伺服器的地址,瀏覽器用這個地址來尋找並連接 Web 伺服器,並將上面的內容返回給你。在你的瀏覽器能夠發送 HTTP 請求之前,它需要與 Web 伺服器建立一個 TCP 連接。然後會在 TCP 連接中發送 HTTP 請求,並等待伺服器返回 HTTP 響應。當你的瀏覽器收到響應後,就會顯示其內容,在上面的例子中,它顯示了「Hello, World!」。

我們來進一步探索在發送 HTTP 請求之前,客戶端與伺服器建立 TCP 連接的過程。為了建立鏈接,它們使用了所謂「 套接字 socket 」。我們現在不直接使用瀏覽器發送請求,而在命令行中使用 telnet 來人工模擬這個過程。

在你運行 Web 伺服器的電腦上,在命令行中建立一個 telnet 會話,指定一個本地域名,使用埠 8888,然後按下回車:

$ telnet localhost 8888
Trying 127.0.0.1 …
Connected to localhost.

這個時候,你已經與運行在你本地主機的伺服器建立了一個 TCP 連接。在下圖中,你可以看到一個伺服器從頭開始,到能夠建立 TCP 連接的基本過程。

在同一個 telnet 會話中,輸入 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!

你剛剛手動模擬了你的瀏覽器(的工作)!你發送了 HTTP 請求,並且收到了一個 HTTP 應答。下面是一個 HTTP 請求的基本結構:

HTTP 請求的第一行由三部分組成:HTTP 方法(GET,因為我們想讓我們的伺服器返回一些內容),以及標明所需頁面的路徑 /hello,還有協議版本。

為了簡單一些,我們剛剛構建的 Web 伺服器完全忽略了上面的請求內容。你也可以試著輸入一些無用內容而不是「GET /hello HTTP/1.1」,但你仍然會收到一個「Hello, World!」響應。

一旦你輸入了請求行並敲了回車,客戶端就會將請求發送至伺服器;伺服器讀取請求行,就會返回相應的 HTTP 響應。

下面是伺服器返回客戶端(在上面的例子里是 telnet)的響應內容:

我們來解析它。這個響應由三部分組成:一個狀態行 HTTP/1.1 200 OK,後面跟著一個空行,再下面是響應正文。

HTTP 響應的狀態行 HTTP/1.1 200 OK 包含了 HTTP 版本號,HTTP 狀態碼以及 HTTP 狀態短語「OK」。當瀏覽器收到響應後,它會將響應正文顯示出來,這也就是為什麼你會在瀏覽器中看到「Hello, World!」。

以上就是 Web 伺服器的基本工作模型。總結一下:Web 伺服器創建一個處於監聽狀態的套接字,循環接收新的連接。客戶端建立 TCP 連接成功後,會向伺服器發送 HTTP 請求,然後伺服器會以一個 HTTP 響應做應答,客戶端會將 HTTP 的響應內容顯示給用戶。為了建立 TCP 連接,客戶端和服務端均會使用套接字。

現在,你應該了解了 Web 伺服器的基本工作方式,你可以使用瀏覽器或其它 HTTP 客戶端進行試驗。如果你嘗試過、觀察過,你應該也能夠使用 telnet,人工編寫 HTTP 請求,成為一個「人形」 HTTP 客戶端。

現在留一個小問題:「你要如何在不對程序做任何改動的情況下,在你剛剛搭建起來的 Web 伺服器上適配 Django, Flask 或 Pyramid 應用呢?」

我會在本系列的第二部分中來詳細講解。敬請期待。

順便,我在撰寫一本名為《搭個 Web 伺服器:從頭開始》的書。這本書講解了如何從頭開始編寫一個基本的 Web 伺服器,裡面包含本文中沒有的更多細節。訂閱郵件列表,你就可以獲取到這本書的最新進展,以及發布日期。

via: https://ruslanspivak.com/lsbaws-part1/

作者:Ruslan 譯者:StdioA 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國