python進(jìn)階--搭建一個多任務(wù)的靜態(tài)web服務(wù)器

HTTP協(xié)議

    http協(xié)議:超文本傳輸協(xié)議,基于tcp通信協(xié)議
    作用:瀏覽器和服務(wù)器通訊的數(shù)據(jù)格式

URL

    概念:統(tǒng)一資源定位符/網(wǎng)絡(luò)資源地址/網(wǎng)址
    組成:協(xié)議部分、域名部分、資源路徑部分、查詢參數(shù)部分

http請求方式: GET:沒有請求體 POST:有請求體

http請求報(bào)文格式

    1.請求行
    2.請求頭
    3.空行
    4.請求體 

注意點(diǎn): 請求報(bào)文的原始格式是有\(zhòng)r\n的

請求行是由三部分組成:

請求方式
請求資源路徑
HTTP協(xié)議版本
GET方式的請求報(bào)文沒有請求體,只有請求行、請求頭、空行組成。
POST方式的請求報(bào)文可以有請求行、請求頭、空行、請求體四部分組成,注意:POST方式可以允許沒有請求體,但是這種格式很少見。

響應(yīng)報(bào)文格式

    響應(yīng)行\(zhòng)r\n
    響應(yīng)頭\r\n
    空行\(zhòng)r\n
    響應(yīng)體\r\n
    狀態(tài)碼          說明
    200         請求成功
    307         重定向
    400         錯誤的請求,請求地址或者參數(shù)有誤
    404         請求資源在服務(wù)器不存在
    500         服務(wù)器內(nèi)部源代碼出現(xiàn)錯誤

搭建一個靜態(tài)的web服務(wù)器

    1.編寫一個TCP服務(wù)端程序
    2.獲取瀏覽器發(fā)送的http請求報(bào)文數(shù)據(jù)
    3.讀取固定頁面數(shù)據(jù),把頁面數(shù)據(jù)組裝成HTTP響應(yīng)報(bào)文數(shù)據(jù)發(fā)送給瀏覽器。
    4.HTTP響應(yīng)報(bào)文數(shù)據(jù)發(fā)送完成以后,關(guān)閉服務(wù)于客戶端的套接字。

代碼示例:這里的服務(wù)器不能發(fā)送不同的響應(yīng),無論用戶輸入什么資源路徑,只要ip地址和端口號正確,返回的頁面都是同一個

讀者可以參考我的代碼,如果需要驗(yàn)證結(jié)果請點(diǎn)擊頁面文件夾,然后訪問本地ip(127.0.0.1:端口號)。

import socket
if __name__ == '__main__':
    # 1.創(chuàng)建socket套接字
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.設(shè)置端口重用
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
    # 3. 綁定本地端口
    server_socket.bind(("", 9090))
    # 4. 設(shè)置最大監(jiān)聽數(shù),由主動變?yōu)楸粍?    server_socket.listen(2)
    while True:
        # 連接成功,產(chǎn)生一個新的套接字專門為客戶端服務(wù)
        new_socket_client, client_address_infor = server_socket.accept()
        new_socket_client.recv(4096)
        # 打開返回的文件,讀取文件
        with open("./static/index.html", "rb") as file:
            file_data = file.read()
        # 創(chuàng)建響應(yīng)行
        response_line = "HTTP/1.1 200 OK\r\n"
        # 創(chuàng)建響應(yīng)頭
        response_head = "Server: NBWS/1.1\r\n"
        # 創(chuàng)建響應(yīng)體
        response_body = file_data
        # 拼接響應(yīng)數(shù)據(jù)
        response_data = (response_line + response_head + "\r\n").encode() + response_body
        # 發(fā)送相應(yīng)數(shù)據(jù)
        new_socket_client.send(response_data)
        new_socket_client.close()

這里我們要升級,搭建一個隨著輸入不同的資源路徑,返回不同的頁面

代碼示例:

import socket
if __name__ == '__main__':
    # 創(chuàng)建服務(wù)器的socket套接字
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
    server_socket.bind(("", 9090))
    server_socket.listen(2)
    while True:
        
        new_socket_client, client_address_info = server_socket.accept()
        request_data = new_socket_client.recv(4096)
        # 判斷客戶端是否退出鏈接,避免客戶端斷開導(dǎo)致服務(wù)器崩潰
        if len(request_data) == 0:
            new_socket_client.close()
            continue
        # 從客戶端的請求行里面獲取到資源路徑
        request_list = request_data.decode().split(" ", maxsplit=2)
        file_path = request_list[1]
        # 判斷資源路徑是否為空,如果為空,則返回一個錯誤404頁面(別忘了修改狀態(tài)碼)
        if file_path == "/":
            file_path = "/index.html"
        # try一下避免客戶端用戶訪問的資源路徑不存在導(dǎo)致程序崩潰
        try:
            with open("static" + file_path, "rb") as file:
                file_data = file.read()
        except Exception as e:
            with open("static/error.html", "rb") as file:
                file_data = file.read()
            # 拼接響應(yīng)提數(shù)據(jù)(下同)
            response_line = "HTTP/1.1 404 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()

        else:
            response_line = "HTTP/1.1 200 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()

實(shí)現(xiàn)了返回不同頁面的mini服務(wù)器之后,就升級實(shí)現(xiàn)多任務(wù)版的服務(wù)器

多任務(wù)web服務(wù)器就是能夠支持多個瀏覽器同事訪問咋們的服務(wù)器,之前我寫過多任務(wù)版的tcp服務(wù)端的開發(fā)代碼,其實(shí)多任務(wù)web服務(wù)器就是多任務(wù)tcp服務(wù)端里面加上了http協(xié)議,即tcp是返回?cái)?shù)據(jù),web服務(wù)器返回的是http超文本(二進(jìn)制),若有不清楚多任務(wù)tcp開發(fā)的話,請移步至[tcp多任務(wù)編程(多線程)](www.itdecent.cn/p/88b7a5372de1

多任務(wù)mini-web服務(wù)器代碼示例:

# 1.實(shí)現(xiàn)tcp服務(wù)器端
#   1.1 創(chuàng)建socket套接字
#   1.2 綁定端口
#   1.3 設(shè)置成監(jiān)聽模式
#   1.4 等待客戶端鏈接
#   1.5 收發(fā)數(shù)據(jù)
# 2.接收瀏覽器發(fā)送過來的請求報(bào)文
#   2.1 取出資源路徑
#         GET /index.html HTTP/1.1\r\n ....\r\n ... \r\n
#       按照空格分割字符串
#       取出索引[1]的數(shù)據(jù) -> file_path
# 3.打開指定文件(”static“ + file_path),返回http響應(yīng)報(bào)文
#   響應(yīng)行:
#       HTTP/1.1 200 OK\r\n
#   響應(yīng)頭
#       Server: NBW/1.1\r\n
#   空行
#       \r\n
#   響應(yīng)體
#       file_data
# 4.關(guān)閉鏈接
import socket
from threading import Thread


def client_handler(server_socket):
    new_socket_client, client_address_info = server_socket.accept()
    request_data = new_socket_client.recv(4096)
    # 判斷客戶端是否退出鏈接,避免客戶端斷開導(dǎo)致服務(wù)器崩潰
    if len(request_data) == 0:
        new_socket_client.close()
        return
    # 從客戶端的請求行里面獲取到資源路徑,請求行的構(gòu)成:"GET(POST) /index.html(資源路徑) HTTP/1.1",這里應(yīng)用split方法分割字符串兩次,就可以得到我們想要的資源路徑了。
    request_list = request_data.decode().split(" ", maxsplit=2)
    file_path = request_list[1]
    # 判斷資源路徑是否為空,如果為空,則返回一個錯誤404頁面(別忘了修改狀態(tài)碼)
    if file_path == "/":
        file_path = "/index.html"
    # try一下避免客戶端用戶訪問的資源路徑不存在導(dǎo)致程序崩潰
    try:
        with open("static" + file_path, "rb") as file:
            file_data = file.read()
    except Exception as e:
        with open("static/error.html", "rb") as file:
            file_data = file.read()
        # 拼接響應(yīng)提數(shù)據(jù)(下同)
        response_line = "HTTP/1.1 404 OK\r\n"
        response_head = "Server: NBWS/1.1\r\n"
        response_body = file_data
        response_data = (response_line + response_head + "\r\n").encode() + response_body
        new_socket_client.send(response_data)
        new_socket_client.close()

    else:
        response_line = "HTTP/1.1 200 OK\r\n"
        response_head = "Server: NBWS/1.1\r\n"
        response_body = file_data
        response_data = (response_line + response_head + "\r\n").encode() + response_body
        new_socket_client.send(response_data)
        new_socket_client.close()


def main():
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
    server_socket.bind(("", 9090))
    server_socket.listen(2)
    while True:  # 這里用一個死循環(huán),就是有多少個瀏覽器來訪問我,我就會產(chǎn)生多少個線程和new_socket_client來為瀏覽器服務(wù)
        client_thread = Thread(target=client_handler, args=(server_socket,))
        # 設(shè)置守護(hù)主線程,為了防止服務(wù)器不能徹底關(guān)閉
        client_thread.setDaemon(True)
        # 開啟子線程
        client_thread.start()


if __name__ == '__main__':
    # 創(chuàng)建服務(wù)器的socket套接字
    main()

讀者是不是發(fā)現(xiàn)我這段代碼跟上面的前面的差不多,那是因?yàn)槲揖褪菑?fù)制上面的代碼改寫的,因?yàn)閷W(xué)習(xí)這個是具有連貫性的,有的東西不需要我們重復(fù)去敲,當(dāng)然讀者想重復(fù)去敲的話那當(dāng)然更好啦。畢竟寫代碼還是需要多敲的。

注意點(diǎn):1.讀者千萬記得寫響應(yīng)體的時候一定要加上"\r\n",作者在學(xué)習(xí)的時候也因?yàn)橥浟?,走了很多彎路。切記切記?。。?/h2>

到這里我們的一個基本的mini-web靜態(tài)服務(wù)器就搭建好了,這里用的是面向過程的編程思想,接下來就來用面向?qū)ο缶幊贪?/h3>

同上,還是該代碼,示例代碼:

import socket
from threading import Thread


class WebServer:
    def __init__(self):
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
        server_socket.bind(("", 9090))
        server_socket.listen(2)
        self.server_socket = server_socket

    def start(self):
        while True:  # 這里用一個死循環(huán),就是有多少個瀏覽器來訪問我,我就會產(chǎn)生多少個線程和new_socket_client來為瀏覽器服務(wù)
            new_socket_client, client_address_info = self.server_socket.accept()
            client_thread = Thread(target=self.client_handler, args=(new_socket_client,))
            # 設(shè)置守護(hù)主線程,為了防止服務(wù)器不能徹底關(guān)閉
            client_thread.setDaemon(True)
            # 開啟子線程
            client_thread.start()

    def client_handler(self, new_socket_client):
        request_data = new_socket_client.recv(4096)
        # 判斷客戶端是否退出鏈接,避免客戶端斷開導(dǎo)致服務(wù)器崩潰
        if len(request_data) == 0:
            new_socket_client.close()
            return
        # 從客戶端的請求行里面獲取到資源路徑
        request_list = request_data.decode().split(" ", maxsplit=2)
        file_path = request_list[1]
        # 判斷資源路徑是否為空,如果為空,則返回一個錯誤404頁面(別忘了修改狀態(tài)碼)
        if file_path == "/":
            file_path = "/index.html"
        # try一下避免客戶端用戶訪問的資源路徑不存在導(dǎo)致程序崩潰
        try:
            with open("static" + file_path, "rb") as file:
                file_data = file.read()
        except Exception as e:
            with open("static/error.html", "rb") as file:
                file_data = file.read()
            # 拼接響應(yīng)提數(shù)據(jù)(下同)
            response_line = "HTTP/1.1 404 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()

        else:
            response_line = "HTTP/1.1 200 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()


if __name__ == '__main__':
    # 創(chuàng)建服務(wù)器的socket套接字
    nb_server = WebServer()
    nb_server.start()

對于面向?qū)ο缶幊?,讀者一定要多多體會,多試試。

拓展:到這里我們的服務(wù)器就算搭建好了,但是還有一點(diǎn)要注意這里面的端口號我們不能自定義,也不能由不懂python的人來更改,如果我們的服務(wù)器上已經(jīng)運(yùn)行一個程序正占用著當(dāng)前(9090)這個端口號的話,那么我們的服務(wù)器是開不起來的,又不能隨便去關(guān)閉服務(wù)器的其他程序,這怎么辦呢?接下來就來教你在命令行自定義端口號

對于sys標(biāo)準(zhǔn)庫里面的argv的使用:

代碼示例:

#此文件名:study_sys_argv.py  windows:在命令行運(yùn)行  ubuntu:在終端運(yùn)行
import sys
a = sys.argv
print(a)

運(yùn)行結(jié)果如下:
python@PC:~python3 ~/Desktop/web_static_server/study_sys_argv.py ['/home/python/Desktop/web_static_server/study_sys_argv.py'] 這里運(yùn)行結(jié)果是個字典格式,sys.argv方法就是獲取終端(命令行)的參數(shù)并生成一個列表,例如在python3 ~/Desktop/web_static_server/study_sys_argv.py這個命令里面python3就是命令,而/home/python/Desktop/web_static_server/study_sys_argv.py就是參數(shù)。這個參數(shù)還可以有多個。 如: python@PC:~ python3 ~/Desktop/web_static_server/study_sys_argv.py a b c d
輸出(終端運(yùn)行的內(nèi)容):['/home/python/Desktop/web_static_server/study_sys_argv.py', 'a', 'b', 'c', 'd']
這里讀者可能就明白了,下來我們就把他運(yùn)用到自定義端口上去。

動態(tài)自定義端口mini-web多任務(wù)服務(wù)器代碼示例:

import socket
from threading import Thread
import sys


class WebServer:

    def __init__(self, port):
        server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, True)
        server_socket.bind(("", port))
        server_socket.listen(2)
        self.server_socket = server_socket

    def start(self):
        while True:  # 這里用一個死循環(huán),就是有多少個瀏覽器來訪問我,我就會產(chǎn)生多少個線程和new_socket_client來為瀏覽器服務(wù)
            new_socket_client, client_address_info = self.server_socket.accept()
            client_thread = Thread(target=self.client_handler, args=(new_socket_client,))
            # 設(shè)置守護(hù)主線程,為了防止服務(wù)器不能徹底關(guān)閉
            client_thread.setDaemon(True)
            # 開啟子線程
            client_thread.start()

    @staticmethod
    def client_handler(new_socket_client):
        request_data = new_socket_client.recv(4096)
        # 判斷客戶端是否退出鏈接,避免客戶端斷開導(dǎo)致服務(wù)器崩潰
        if len(request_data) == 0:
            new_socket_client.close()
            return
        # 從客戶端的請求行里面獲取到資源路徑
        request_list = request_data.decode().split(" ", maxsplit=2)
        file_path = request_list[1]
        # 判斷資源路徑是否為空,如果為空,則返回一個錯誤404頁面(別忘了修改狀態(tài)碼)
        if file_path == "/":
            file_path = "/index.html"
        # try一下避免客戶端用戶訪問的資源路徑不存在導(dǎo)致程序崩潰
        try:
            with open("./static" + file_path, "rb") as file:
                file_data = file.read()
        except Exception as e:
            with open("./static/error.html", "rb") as file:
                file_data = file.read()
            # 拼接響應(yīng)提數(shù)據(jù)(下同)
            response_line = "HTTP/1.1 404 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()

        else:
            response_line = "HTTP/1.1 200 OK\r\n"
            response_head = "Server: NBWS/1.1\r\n"
            response_body = file_data
            response_data = (response_line + response_head + "\r\n").encode() + response_body
            new_socket_client.send(response_data)
            new_socket_client.close()


if __name__ == '__main__':
    argv_list = sys.argv
    str_port = argv_list[1]
    port = int(str_port)
    print(port)
    nb_server = WebServer(port)
    nb_server.start()

總結(jié):用了sys.argv方法就相當(dāng)于在外部去靈活的定義服務(wù)器的端口,不用去修改程序。

注意:運(yùn)行此代碼時,一定要在終端(命令行),并且一定要進(jìn)入項(xiàng)目目錄里面,不然會報(bào)錯。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容