整天寫CRUD沒勁,寫了個Web服務器


文章來源:Python之禪

作者:江湖十年

今天分享的系列文章是如何用python徒手擼一個web服務器。

我們學知識大致都會經歷三個階段。第一階段是what,瞭解一個東西是什麼,第二階段是how,瞭解一個東西如何使用。第三階段是why,爲什麼要這麼用。當你達到第三層境界才能說明你對這個東西理解透徹。

同理,對於一個做web開發的程序員來說,沒有什麼比自己寫一個web服務器或web框架更能瞭解web編程的本質了。

這個系列是我的一位讀者朋友江湖十年寫的,他過來問我可不可以投稿在公衆號。看完之後就立馬答應了。大家都喜歡看沒營養的爽文,雖然這種乾貨型的技術文看的人少,但是能幫助對web開發感興趣的人就值了。

系列文章工作分爲8章,除了這篇,次條到第8條都是,你也不可能一天看到,先收藏着,看看你能不能一週也寫一個web服務器出來。

教程簡介

本教程使用 Python 語言實現了一個簡易版的 Web 服務器,從 Web 開發基礎開始講解,不使用任何第三方庫或框架,通過實現一個 Todo List 應用來還原 Web 開發的本質。

教程所需基礎

Python、HTML、CSS 語法基礎,對 Web 開發基本概念有所瞭解。

教程面向讀者

本教程爲入門級,主要適合 Python Web 開發者或對 Web 開發理解不是很透徹的同學。

教程特點

本教程並不會使用如 Django、Flask 等常見 Python Web 開發框架,也不會使用任何其他第三方庫,甚至會用文件來替代數據庫存儲數據。其目的是爲了簡化一些對初學者來說看似複雜的概念,使用更少的依賴從零開發一個 Python Web 服務器。以便讀者能夠更深刻的理解 Web 開發。

前言

Web 開發技術一直在高速發展,各種新奇概念與框架層出不窮,尤其在 Web 前端領域,幾年前還是 jQuery 的天下,而如今在 Vue、React 等框架面前也顯得廉頗老矣。

不過,雖然各種框架技術日新月異,但 Web 開發的核心概念與本質依舊不曾改變,本教程將通過一個 Todo List 應用帶你探索 Web 開發基本原理,只有真正明白了 Web 開發的核心基礎,才能更輕鬆的應對新框架與技術。

Web 開發簡介

我們常見的軟件種類有桌面軟件、移動 APP以及網頁應用等,Web 開發通常就是在開發網頁應用。桌面軟件、移動 APP 需要先安裝在 Windows、Android 等宿主機才能使用,每個客戶端每次升級更新軟件時都需要重新下載並安裝。而 Web 應用所依賴的客戶端是瀏覽器,實際數據都存儲在遠程服務器端,如果應用需要升級,那麼只需要升級服務器,所有用戶通過瀏覽器打開網頁時都將實時獲取最新的數據,這也是 Web 開發能夠流行起來的很重要的原因。

HTTP 簡介

要學習 Web 開發,首先要明白什麼是 HTTP 協議,因爲 Web 開發就是建立在 HTTP 協議之上的。

在瀏覽器地址欄輸入網址 https://www.jd.com/ 將得到京東商城首頁。我們在瀏覽器頁面中看到的所有數據都是服務器通過 HTTP 協議傳輸過來的。



HTTP 協議中文叫超文本傳輸協議,可以拆分成三部分理解:超文本、傳輸、協議。

所謂超文本就是 HTML、CSS、圖片、視頻等內容的集合。傳輸既超文本內容從瀏覽器(客戶端)到服務器或從服務器到瀏覽器之間的傳輸過程。而協議是規範,大家約定俗成的規約既是協議。

HTTP 基於請求 —— 響應模型,瀏覽器向服務器的某個網址發起請求,服務器響應瀏覽器對應的資源。以下就是一個 HTTP 請求 —— 響應的模型圖。

左側是瀏覽器,右側是服務器。瀏覽器和服務器之間通訊是通過文本傳輸來完成的。瀏覽器發起請求時發送的數據叫作請求報文,得到的服務端響應的數據叫作響應報文。下圖示展示了請求報文和響應報文的格式。

根據圖示可以看到,無論是請求報文還是響應報文基本都分爲四個部分,每個部分之間以 \r\n 作爲換行分隔符。

請求報文包含請求行、報文首部、空行、報文主體四個部分。

響應報文包含狀態行、報文首部、空行、報文主體四個部分。

在請求報文中,請求行和報文首部可以看成一個整體叫作請求頭,報文主體又叫請求體。

在響應報文中,狀態行和報文首部可以看成一個整體叫作響應頭,報文主體又叫響應體。

如何在 Chrome 瀏覽器中查看請求與響應的報文信息呢?瀏覽器頁面任意位置點擊鼠標右鍵 —— 選擇檢查,就能夠在頁面底部顯示 Chrome 開發者工具。點擊 Elements 選項卡,你就能夠看到網頁的源代碼,也就是服務器的響應數據。

點擊 Network 選項卡,就能夠看到所有請求、響應記錄。


每一行記錄即爲一個請求,爲什麼只是在地址欄裏輸入了 https://www.jd.com/ 會出現這麼多請求呢?我們知道,HTML 中不止可以寫簡單的文本標籤,還可以寫 imgvideo 等多媒體標籤加載圖片或視頻,以及 linkscript 等標籤來加載 CSS 樣式 和 JavaScript 腳本。瀏覽器在得到服務器的第一個響應時,會檢查服務器返回的 HTML 頁面,如果頁面源碼中包含這些特殊的標籤,瀏覽器就會針對每一個標籤專門發起一次請求。

點擊第一個請求,在右側將會顯示該請求及響應信息。找到 Request Headers 點擊右側的 view source,將會看到此次請求的請求頭信息。

第一行 GET / HTTP/1.1 即爲請求行。它由三個部分組成:請求方法、請求地址、HTTP 協議版本號,分別對應 GET/HTTP/1.1。每個部分之間通過一個空格隔開。

HTTP 請求方法有很多,不過最常見的只有四種:POSTDELETEPUTGET 分別對應增、刪、改、查四個操作。由於我們只是想查看京東首頁,所以這裏發送的是 GET 請求。

請求地址 / 代表首頁,你可能看過如 /index/index.html 等類似的地址,其實這只是一個約定俗成的做法,它們都代表首頁。

現在最常用的 HTTP 版本即爲 1.1。雖然 HTTP 2.0 早已發佈,不過目前來說普及率依舊不是很高。所以現在大可不必糾結版本的問題。

從第二行開始,所有內容都是諸如 Key: Value 這種鍵值對的形式組合成的,我們管每一組鍵值對都叫作請求首部字段,這些首部字段加在一起就是請求的報文首部,一般會直接叫請求頭。

其中最重要的請求首部字段就是 Host: www.jd.com 這一行,因爲只有這一行是 HTTP 協議明確規定爲必傳的首部字段,其他請求首部字段都是非必傳字段。Host: www.jd.com 的作用是當服務器上部署了多個網站,那麼服務器就會根據這一行的信息來確定瀏覽器到底想訪問哪個網站。

Connection: keep-alive 代表長連接,User-Agent 標識了瀏覽器信息,Accept 代表瀏覽器能夠接收的報文格式,其他的請求首部字段可以等我們用到了再做說明。

在 HTTP 請求報文中,還缺少一個空行和請求體是我們目前沒有看到的。實際上雖然我們是點擊瀏覽器的 view source 功能來查看請求報文信息,但它仍然是瀏覽器處理過的數據,並不是原始數據,所以空行是看不到的。而此次請求爲 GET 請求,通常 GET 請求是沒有請求體的,所以請求體被省略了。

一個 HTTP GET 請求大概長這樣:

GET / HTTP/1.1
Host: www.jd.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

一個 HTTP POST 請求大概長這樣:

POST /login HTTP/1.1
Host: www.jd.com
Connection: keep-alive
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9

username=test&password=pass

上面 POST 請求示例中 username=test&password=pass 即爲請求體。

需要注意,以上請求報文中每一個換行都是用 \r\n 來標識的,這一點在後面實際開發的章節中就能體會到。

分析完請求報文,接下來分析下響應報文。找到 Response Headers 同樣點擊右側的 view source,將會看到此次請求的響應頭信息。

第一行 HTTP/1.1 200 OK 即爲狀態行。它同樣由三部分組成:HTTP協議版本號、狀態碼、當前狀態碼對應的原因短語,分別對應 HTTP/1.1200OK。每個部分之間仍然通過一個空格隔開。

其中狀態碼用來告知服務器返回的響應狀態,爲一個數字。狀態碼大概分爲四類:2XX3XX4XX5XX,分別對應成功、重定向、客戶端錯誤、服務端錯誤。所以狀態碼爲 200 表示請求成功。

原因短語 OK 其實並不是很重要,它主要是給客戶端返回一個人類可讀的短語,來標識請求結果,對瀏覽器來說沒什麼作用。

響應首部字段同樣是 Key: Value 這種鍵值對形式,並且有些是和請求首部字段一樣的,稱爲通用首部字段,如 Connection: keep-alive 同樣存在於響應首部字段。還有些字段是和請求首部字段搭配着使用的,如你所見 Content-Type 字段就是搭配請求首部字段中的 Accept 來使用的,Accept 是瀏覽器用來告訴服務器它能夠接收的數據格式,而 Content-Type: text/html 標識了服務器返回的數據格式爲 html。其他的響應首部字段可以等我們用到了再做說明。

同樣的,響應報文中的空行並不會在瀏覽器中展現出來,不過響應體是可以在瀏覽器中看到的。

當前請求的響應體實際上就是服務器返回的 HTML 源碼。

一個 HTTP 響應大概長這樣:

HTTP/1.1 200 OK
Connection: keep-alive
Content-Type: text/html

<html>XXX</html>

URL 簡介

介紹完 HTTP 協議基礎,我們再來簡單介紹下 URL

京東首頁的網址是 https://www.jd.com/,這就是一個 URLURL 中文叫作統一資源定位符。URL 的作用是在網絡中唯一的標記一個資源。

你可能聽說過 URI,實際上 URLURI 的一個子集。在基礎學習階段,我們並不需要弄清楚它們之間到底有什麼差別,只需要把我們常見的網址統稱爲 URL 即可。

一個 URL 的完整格式如下:

scheme://user:passwd@host:port/path?query#fragment

每一個部分代表含義如下:

scheme:協議名,表示資源使用哪種協議,http 就代表了 HTTP 協議,file 代表文件協議。

://:這個是固定寫法,記住就好,沒必要深究。

user:passwd@:身份信息,表示訪問主機時需要提供的用戶名和密碼,但這種將隱私信息完全暴露在外部的作法已經幾乎沒人使用了。

host:port:表示資源所在的主機名和端口號。主機名爲 IP 地址或域名,必須要有,HTTP 協議默認端口號是  80,可以省略,其他端口號則不可省略。

path:標識資源所在位置。它採用類似 Unix 系統的目錄風格,必須以 / 開頭,如果是多級路徑,可以寫成 /a/b/c 這種形式。

query:查詢參數,在路徑之後第一個 ? 開始(不包含?)到 # 之前結束,所有的內容都是查詢參數,一個參數的格式爲 key=value,如果存在多個參數,中間用 & 隔開。假如我們需要對數據做分頁處理就可以使用這個參數,如 page=1&offset=20 表示需要獲取第一頁的數據,每頁數據 20 條。這時候服務器端就可以解析到這個參數並返回對應的數據。

fragment:最後一部分爲片段標識符。也可以將其叫做錨點,從 # 開始,瀏覽器可以根據錨點跳轉到頁面指定位置,但這個參數是不會被髮送到服務器端的,所以開發服務器端通常不太需要關心。

下面通過對一個實際的 URL 地址進行分析來加強理解。

以下圖示爲在京東首頁搜索 Python書籍後瀏覽器地址欄 URL 截圖。爲了能夠更清晰的說明問題 URL 部分略有刪減。

我們可以將 URL 複製出來:

https://search.jd.com/Search?keyword=python%E4%B9%A6%E7%B1%8D&enc=utf-8

以上 URL 按標準格式拆分如下:

scheme: https
host:port: search.jd.com
path: /Search
query: keyword=python%E4%B9%A6%E7%B1%8D&enc=utf-8

你應該已經發現上面複製出來的 URL 和截圖中的有些不同。

首先我們複製出來的 URL 是以 https 開頭,而瀏覽器中卻是以 search 開頭,其實這是瀏覽器爲了讓 URL 更加可讀,而將 https:// 隱藏了,實際上它仍然是存在的。

你可能有個疑惑,我們上面一直在介紹 HTTP 協議,一個完整的 URL 是以 http 開頭的。但實際上京東網站的 URL 都是以 https 開頭的。其實 https 是基於 http 的,只是在 http 的基礎上又加了一個安全套接層,默認端口 443,HTTP 協議傳輸數據都是明文傳輸,HTTPS 協議傳輸數據是經過加密的,所以使得數據的傳輸更加安全,僅此而已。本教程不會使用 HTTPS 協議,故此不做過多講解。

還有一個較大的差異就是在瀏覽器地址欄中看到的查詢參數 Python書籍 複製出來以後就變成了 python%E4%B9%A6%E7%B1%8D。這是 URL 編碼造成的,通常在 URL 中只能使用 ASCII 碼,並且 URL 本身會使用如 ?& 等符號作爲分隔符。這就導致了很多特殊符號或者 ASCII 以外的字符(如中文)不能夠直接使用,所以就規定了一種轉碼規範,將不能直接用於 URL 的字符通過轉義操作使其變爲可用字符。這就是 書籍 兩個字符變成了 %E4%B9%A6%E7%B1%8D 的原因。在瀏覽器地址欄中能夠正常顯示同樣是瀏覽器爲了讓 URL 更加可讀做的特殊處理。URL 轉義具體規則可以查看相關文檔進行學習。

TCP/IP 簡介

多個計算機之間通訊靠的是網絡協議,最早的計算機廠商生產的計算機只能跟自家的計算機通訊,爲讓所有計算機之間都能夠通訊就有了 TCP/IP 協議。這就比如幾個人對話,有的說漢語、有的說英語、有的說日語,大家誰也聽不懂其他人說話,所以最後大家都規定用一種語言來交流一樣。

TCP/IP 協議並不是單個的協議,它是一個協議族,包含了很多種網絡通訊協議,HTTP 協議也在其中。在網絡中兩臺計算機之間如果需要通訊,就要知道對方在哪,所以就有了 IP 地址。就像我們要給別人發快遞,那麼就要知道對方的家庭住址一樣。現在常用的 IP 地址爲 IPv4 版本,格式如 192.168.3.14,最新版本是 IPv6,格式如 ABCD:EF01:2345:6789:ABCD:EF01:2345:6789

有了 IP 地址還不夠,就像你把快遞郵寄到一個家庭住址,但家裏有五口人,你還要確定這個快遞是寄給誰的。端口的作用就是幹這個的。計算機通過端口號區分收到的信息該轉發給哪個軟件。

IP 地址加上端口號就是上面介紹的 URLhost:port 部分,如 192.168.3.14:80。但 IP 地址是一串數字,不容易記住,所以就有了域名。www.jd.com 就是京東的域名。我們在瀏覽器中輸入這個域名時,計算機會自動將其轉換成 IP 地址加端口號的形式。這個轉換過程是通過一個叫 DNS 解析的技術。它能夠將域名和 IP 之間的關係做綁定,知道了域名它就能查出其對應的 IP 地址是多少。

HTTP 協議是建立在 TCP/IP 協議之上的,HTTP 的數據傳輸依靠的是 TCP 協議。總結起來就是 TCP 用來傳輸數據,HTTP 用來規範數據傳輸格式。

擴展

從瀏覽器地址欄輸入網址 https://www.jd.com/ 到看見京東網站首頁經歷了什麼?

  1. 瀏覽器從地址欄獲取 URL

  2. 瀏覽器或操作系統通過 DNS 解析將 URL 中域名和端口號解析成對應的 IP 地址和端口號

  3. 瀏覽器通過 TCP 與服務器建立連接

  4. 瀏覽器向服務器發送請求報文

  5. 服務器收到請求報文後做對應的處理,將處理結果組裝成響應報文返回給瀏覽器

  6. 瀏覽器解析響應報文,渲染頁面

以上就是從瀏覽器地址欄輸入網址 https://www.jd.com/ 到看見京東網站首頁所經歷的過程概述。

如果你對 Web 開發完全零基礎,看了以上我對 HTTP 協議的簡介,感覺不是很明白的話,那麼我推薦你可以看一本叫作《圖解HTTP》的書,看完後再來學習本教程能夠達到事半功倍的效果。

開發環境

本教程項目開發環境如下:

  • Python:Python3.7

  • 瀏覽器:Chrome83

  • 開發工具:PyCharm 2020.1

  • 操作系統:macOS 10.15

有 Python 基礎的同學想必搭建開發環境肯定不在話下,所以具體搭建過程這裏不再做過多介紹,需要強調的一點是瀏覽器我只推薦 Chrome 或者 Firefox,這樣能夠得到更好的開發體驗。

聯繫作者:

  • 微信:jianghushinian

  • 郵箱:[email protected]

  • 博客地址:https://jianghushinian.cn/

每日留言

你寫過什麼有意思的項目嗎?

或者新學的一個小技巧?

(字數不少於15字)

留言贈書

恭喜 TLHorse 獲得昨日的留言贈書一本

聯繫老表微信:pythonbrief

近期推薦閱讀:
【1】整理了我開始分享學習筆記到現在超過250篇優質文章,涵蓋數據分析、爬蟲、機器學習等方面,別再說不知道該從哪開始,實戰哪裏找了【2】【終篇】Pandas中文官方文檔:基礎用法6(含1-5)
如果你覺得文章不錯的話,分享、收藏、在看、留言666是對老表的最大支持。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章