【python】如何自己開發 一個Web服務器

  • HTTP 請求報文格式:
Host: 127.0.0.1:8888
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9

image
- HTTP 響應報文格式:

HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
Date: Wed, 14 Mar 2018 09:52:48 GMT
Server: BWS/1.1

image

採用 TCP 協議創建 socket 套接字對象
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 複用端口
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 綁定 IP 和端口
    server_socket.bind(('', 8888))
    # 開啓監聽,由主動模式變爲被動模式
    server_socket.listen()
等待客戶端的連接,並接收客戶端請求數據
        # 等待客戶端連接
        new_socket, client_addr = server_socket.accept()
        # 接收瀏覽器發送的請求
        recv_data = new_socket.recv(1024).decode()
        # 防止客戶端下線導致bug
        if not recv_data:
            print("客戶端下線!")
            new_socket.close()
            continue
解析請求並返回數據
        # 從請求行獲取路徑信息
        data_list = recv_data.splitlines()
        request_line = data_list[0]
        regex = re.match(r'.* (.*) .*', request_line)
        file_path = regex.group(1)
        if file_path == '/':
            file_path = '/myWeChat.html'
        try:
            # 採用二進制讀取本地文件內容(方便讀取圖片等非文本文件)
            with open(f'.{file_path}', 'rb') as f:
                response_body = f.read()
        except Exception as reason:
            # 沒有相關文件資源時返回 404 錯誤
            response_line = "HTTP/1.1 404 Not Found\r\n"
            response_headers = "Server: WASPVAE/6.6\r\n"
            response_headers += "Connection: Keep_Alive\r\n"
            response_headers += "Content-Type: text/html;charset=utf-8\r\n"  # 解決中文亂碼問題
            response_headers += "\r\n"
            response_body = f'<h2>404 {reason}</h2>'.encode()
        else:
            # 解析請求
            response_line = "HTTP/1.1 200 OK\r\n"
            response_headers = "Server: WASPVAE/6.6\r\n"
            response_headers += "Connection: Keep_Alive\r\n"
            response_headers += "Content-Type: text/html;charset=utf-8\r\n"  # 解決中文亂碼問題
            response_headers += "\r\n"
        finally:
            # 響應請求並返回數據
            response = response_line + response_headers
            new_socket.send(response.encode() + response_body)
            # 關閉
            new_socket.close()
        # 從請求行獲取路徑信息
        data_list = recv_data.splitlines()
        request_line = data_list[0]
        regex = re.match(r'.* (.*) .*', request_line)
        file_path = regex.group(1)

該代碼可以顯示指定頁面

if file_path == '/':
    file_path = '/myWeChat.html'

該代碼可以設置默認首頁

完整代碼:
import re
import socket


if __name__ == '__main__':
    """返回固定的頁面數據"""
    # 採用 TCP 協議創建 socket 對象
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 使端口重複使用
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 綁定 IP 和端口
    server_socket.bind(('', 8888))
    # 開啓監聽,由主動模式變爲被動模式
    server_socket.listen()
    while True:
        # 等待客戶端連接
        new_socket, client_addr = server_socket.accept()
        # 接收瀏覽器發送的請求
        recv_data = new_socket.recv(1024).decode()
        # 防止客戶端下線導致bug
        if not recv_data:
            print("客戶端下線!")
            new_socket.close()
            continue
        # 從請求行獲取路徑信息
        data_list = recv_data.splitlines()
        request_line = data_list[0]
        regex = re.match(r'.* (.*) .*', request_line)
        file_path = regex.group(1)
        if file_path == '/':
            file_path = '/myWeChat.html'
        try:
            # 採用二進制讀取本地文件內容(方便讀取圖片等非文本文件)
            with open(f'.{file_path}', 'rb') as f:
                response_body = f.read()
        except Exception as reason:
            response_line = "HTTP/1.1 404 Not Found\r\n"
            response_headers = "Server: WASPVAE/6.6\r\n"
            response_headers += "Connection: Keep_Alive\r\n"
            response_headers += "Content-Type: text/html;charset=utf-8\r\n"  # 解決中文亂碼問題
            response_headers += "\r\n"
            response_body = f'<h2>404 {reason}</h2>'.encode()
        else:
            # 解析請求
            response_line = "HTTP/1.1 200 OK\r\n"
            response_headers = "Server: WASPVAE/6.6\r\n"
            response_headers += "Connection: Keep_Alive\r\n"
            response_headers += "Content-Type: text/html;charset=utf-8\r\n"  # 解決中文亂碼問題
            response_headers += "\r\n"
        finally:
            # 響應請求並返回數據
            response = response_line + response_headers
            new_socket.send(response.encode() + response_body)
            # 關閉
            new_socket.close()

一個 web 服務器開發完了。等等,這個服務器好像有點問題,當一個客戶端連接時其他客戶端就沒法連接了。我開發服務器並不是爲一個人服務,開發服務器也要符合社會主義核心價值觀,要爲人民服務。

通過多協程實現多用戶處理
import re
import socket
from gevent import monkey
import gevent
# 可以簡單理解爲將一些模塊變爲非阻塞,具體解釋需要上網查閱資料
monkey.patch_all()


def handle_request(new_socket):
        # 接收瀏覽器發送的請求
        recv_data = new_socket.recv(1024).decode()
        # 防止客戶端下線導致bug
        if not recv_data:
            print("客戶端下線!")
            new_socket.close()
            return
        # 從請求行獲取路徑信息
        data_list = recv_data.splitlines()
        request_line = data_list[0]
        regex = re.match(r'.* (.*) .*', request_line)
        file_path = regex.group(1)
        if file_path == '/':
            file_path = '/myWeChat.html'
        try:
            # 採用二進制讀取本地文件內容(方便讀取圖片等非文本文件)
            with open(f'.{file_path}', 'rb') as f:
                response_body = f.read()
        except Exception as reason:
            response_line = "HTTP/1.1 404 Not Found\r\n"
            response_headers = "Server: WASPVAE/6.6\r\n"
            response_headers += "Connection: Keep_Alive\r\n"
            response_headers += "Content-Type: text/html;charset=utf-8\r\n"  # 解決中文亂碼問題
            response_headers += "\r\n"
            response_body = f'<h2>404 {reason}</h2>'.encode()
        else:
            # 解析請求
            response_line = "HTTP/1.1 200 OK\r\n"
            response_headers = "Server: WASPVAE/6.6\r\n"
            response_headers += "Connection: Keep_Alive\r\n"
            response_headers += "Content-Type: text/html;charset=utf-8\r\n"  # 解決中文亂碼問題
            response_headers += "\r\n"
        finally:
            # 響應請求並返回數據
            response = response_line + response_headers
            new_socket.send(response.encode() + response_body)
            # 關閉
            new_socket.close()
def main():
    """程序主入口,實現 web 服務器功能"""
    # 採用 TCP 協議創建 socket 對象
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 使端口重複使用
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 綁定 IP 和端口
    server_socket.bind(('', 8888))
    # 開啓監聽,由主動模式變爲被動模式
    server_socket.listen()
    while True:
        # 等待客戶端連接
        new_socket, client_addr = server_socket.accept()
        gevent.spawn(handle_request, new_socket)

if __name__ == '__main__':
    main()

總感覺還是差了點什麼,哦,對了,python 是面嚮對象語言,這個代碼沒有對象啊,現實中沒有對象,寫個代碼還沒有對象,一萬點暴擊傷害。下面來個終極版的面向對象封裝

import re
import socket
from gevent import monkey
import gevent

monkey.patch_all()  # 可以簡單理解爲使一些模塊變爲非阻塞,具體原理可以上網查詢資料


class WebServer:
    def __init__(self):
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        self.server_socket.bind(('', 8888))
        self.server_socket.listen()

    def handle_request(self, new_socket):
        """處理請求並完成響應操作"""
        # 接收瀏覽器發送的請求
        recv_data = new_socket.recv(1024).decode()
        # 防止客戶端下線導致bug
        if not recv_data:
            print("客戶端下線!")
            new_socket.close()
            return
        # 從請求行獲取路徑信息
        data_list = recv_data.splitlines()
        request_line = data_list[0]
        regex = re.match(r'.* (.*) .*', request_line)
        file_path = regex.group(1)
        if file_path == '/':
            file_path = '/myWeChat.html'
        try:
            # 採用二進制讀取本地文件內容(方便讀取圖片等非文本文件)
            with open(f'.{file_path}', 'rb') as f:
                response_body = f.read()
        except Exception as reason:
            response_line = "HTTP/1.1 404 Not Found\r\n"
            response_headers = "Server: WASPVAE/6.6\r\n"
            response_headers += "Connection: Keep_Alive\r\n"
            response_headers += "Content-Type: text/html;charset=utf-8\r\n"  # 解決中文亂碼問題
            response_headers += "\r\n"
            response_body = f'<h2>404 {reason}</h2>'.encode()
        else:
            # 解析請求
            response_line = "HTTP/1.1 200 OK\r\n"
            response_headers = "Server: WASPVAE/6.6\r\n"
            response_headers += "Connection: Keep_Alive\r\n"
            response_headers += "Content-Type: text/html;charset=utf-8\r\n"  # 解決中文亂碼問題
            response_headers += "\r\n"
        finally:
            # 響應請求並返回數據
            response = response_line + response_headers
            new_socket.send(response.encode() + response_body)
            # 關閉
            new_socket.close()

    def run(self):
        """運行服務器,接收客戶端的請求,處理請求並完成響應操作"""
        while True:
            # 等待客戶端連接
            new_socket, client_addr = self.server_socket.accept()
            gevent.spawn(self.handle_request, new_socket)


def main():
    """程序主入口,實現 web 服務器功能"""
    web_server = WebServer()
    web_server.run()


if __name__ == '__main__':
    main()

可以在項目當前文件夾添加幾個 html 文件在瀏覽器輸入 localhost:8888/xxx.html 或者 127.0.0.1:8888/xxx.html測試下
有興趣的朋友可以關注下我的微信個人訂閱號python數據之路,裏面有我之前自學 python 的一些資料和在黑馬學習的心得與筆記。

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