寫一個簡單的webserver

基於 Python3 寫的極簡版 webserver。用於學習 HTTP協議,及 WEB服務器 工作原理。筆者對 WEB服務器 的工作原理理解的比較粗淺,僅是基於個人的理解來寫的,存在很多不足和漏洞,目的在於給大家提供一個寫 webserver 的思路。項目GitHub地址:https://github.com/hanrenguang/simple-webserver

WEB服務器原理

學過計網的同學應該都知道 HTTP協議 是在 TCP協議 之上實現的。瀏覽器與服務器之間的通信首先是建立 TCP 連接,再進行請求和響應報文的傳輸。服務器是屬於被動的一方,當瀏覽器發起請求的時候,服務器才能和瀏覽器通信,在此之前,服務器都處於一個等待監聽的狀態。

socket連接

實現服務器的第一步是建立一個 socket 連接,socket 套接字是對 TCP/UDP協議 的一個封裝,Python 就自帶有 socket 模塊,所以使用起來很方便。

import socket

sk = socket.socket(
    socket.AF_INET, 
    socket.SOCK_STREAM
)

# 監聽本地 8888 端口
host = '127.0.0.1'
port = 8888

sk.bind((host, port))
sk.listen(5)

while True:
    try:
        clientSk, addr = sk.accept()
        print("address is: %s" % str(addr))

        req = clientSk.recv(1024)

        clientSk.sendall('...')
        clientSk.close()

    except Exception as err:
        print(err)
        clientSk.close()

這是一個極簡的 socket-server,需要注意的是,我們僅實現了 TCP協議 的部分。

解析HTTP請求

拿到瀏覽器的請求很簡單,clientSk.recv() 即可獲取請求報文,而些數據我們無法直接拿來用,因爲它是基於 HTTP協議 封裝的數據,在我們進行下一步操作前,需要對請求報文“解封”。而在此之前,我們需要了解請求報文的格式。最快捷的方式呢,是打開瀏覽器(以 chrome 爲例),隨便打開百度啥的,F12 打開開發者工具,在 Network 一欄就可以觀察到。大概長下面這樣:

GET / HTTP/1.1
Host: xxx
Connection: xxx
Cache-Control: xxx
Upgrade-Insecure-Requests: xxx
User-Agent: xxx
Accept: xxx
Accept-Encoding: xxx
Accept-Language: xxx
Cookie: xxx

我們把關注點放在第一行,GET 方法,請求的資源路徑爲 /,使用的協議是 HTTP1.1,之後就是一回車換行符 \r\n。所以我們對報文的解析如下(存在許多不足之處):

# 第一步先對數據進行解碼 decode(),
# 再以行爲單位進行分割
requestList = clientSk.recv(1024).decode().split("\r\n")

# 調用寫好的函數對其進行解析
parseReq(requestList)

# 解析請求報文
def parseReq(reqList):
    # 保存解析結果
    parseRet = {}

    # 請求的方法,如 GET
    method = reqList[0].split(' ')[0]
    # 請求的資源路徑,如 '/'
    sourcePath = reqList[0].split(' ')[1]

    parseRet['method'] = method
    parseRet['sourcePath'] = sourcePath

    i = len(reqList) - 1

    # 以 key: value 的形式保存解析結果
    while i:
        if len(reqList[i].split(':')) == 1:
            i = i - 1
            continue

        idx = reqList[i].find(':')
        key, value = reqList[i][0:idx], reqList[i][idx+1:]
        parseRet[key] = value.strip()
        i = i - 1

    return parseRet

構造響應報文

拿到了請求報文並將其解析後,我們可以開始構造響應報文的內容了,以請求靜態資源爲例,假設請求報文第一行爲 GET /index.html HTTP/1.1。那麼我首先要做的就是先獲取路徑爲 /index.html 的文件內容:

# 獲取資源內容
try:
    f = open(path, 'r')
    while True:
        chunk = f.read(1024)
        if not chunk:
            f.close()
            break;
        content += chunk
except:
    pass

那接下來就是構造響應報文了,同理可以觀察 HTTP 響應報文的格式,在此就不舉例了,直接上代碼:

try:
    f = open(path, 'r')
    while True:
        chunk = f.read(1024)
        if not chunk:
            f.close()
            break;
        content += chunk
except:
    pass

# 省略了大部分頭部信息
headers = 'HTTP/1.1 200 OK\r\n'
contentType = 'Content-Type: text/html; charset=utf-8\r\n'
contentLen = 'Content-Length: ' + str(len(content)) + '\r\n'

# 組合成響應報文 res
res = headers + contentType + contentLen + '\r\n' + content

# 編碼後發送給瀏覽器,
# 至此,本次通信結束
clientSk.sendall(res.encode(encoding='UTF-8'))
clientSk.close()

示例

到項目GitHub:https://github.com/hanrenguang/simple-webserver,下載本項目到本地,雙擊 server.py,並訪問 http://localhost:8888/index.html,你應該會看到十分親切的 Hello world!

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