天天看點

面向對象封裝的web伺服器

import socket
import re
import os
import sys

# 由于前面太繁瑣,可以用類封裝一下,也可以分幾個子產品
class HttpServer(object):

    def __init__(self,port):
        # 1、伺服器建立負責監聽的socket
        self.socket_watch = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 2、設定位址重用
        self.socket_watch.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 3、綁定監聽的端口
        self.socket_watch.bind(('', port))
        # 4、設定監聽隊列
        self.socket_watch.listen(128)

    def handle_client(self,socket_con):
        """
         接收來自用戶端的請求,并接收請求封包,解析,傳回
        """
        # 1、伺服器接收用戶端的請求封包
        request = socket_con.recv(4096).decode()

        # 2、截取請求封包,擷取請求行
        request_lines = request.split("\r\n")
        # 3、擷取請求行
        request_line = request_lines[0]
        # GET /a/ab/c.html HTTP/1.1
        # 通過正規表達式 比對出請求行中請求資源路徑
        res = re.match(r"\w+\s+(\S+)",request_line)
        # 擷取資源路徑
        path = res.group(1)
        # 将資源路徑和我的web檔案夾的絕對路徑拼接(自己填寫)
        path ="# 本地絕對路徑" + path
        # 在判斷是檔案還是檔案夾之前,首先要判斷你這個路徑在伺服器中是否存在
        if not os.path.exists(path):
            response_line = 'HTTP/1.1 404 Not Found\r\n'
            response_head = 'Server:skylark 2.0\r\n'
            response_head += 'Content-type:text/html;charset=utf-8\r\n'
            response_body = '你請求'+ path +'不存在'
            response = response_line + response_head + '\r\n' +response_body
            socket_con.send(response.encode())
            socket_con.close()
            return
        else:
            # 判斷使用者請求的是檔案還是檔案夾
             if os.path.isfile(path):
                 # 如果檔案存在 讀取頁面資料,然後傳回
                response_line = "HTTP/1.1 200 OK\r\n"
                response_head = "Server:skylark 2.0\r\n"
                # 注意請求圖檔需要使用"rb"的方式進行讀取
                file = open(path,"rb")
                # response_body 是二進制是以不用再次編碼
                response_body = file.read()
                response = response_line.encode() + response_head.encode() +"\r\n".encode() +response_body
                socket_con.send(response)
                socket_con.close()
                return
             else:
                if path.endswith("/"):
                    # 例如 www.baidu.com/images
                    # 使用者請求的檔案夾
                    # 1、判斷該檔案夾下是否有預設的檔案,如果有,則傳回,如果沒有
                    # index.html default.html
                    default_document = False
                    # 如果允許你通路我目錄下的預設文檔
                    if default_document:
                        # 判斷使用者通路的檔案夾下是否有index.html 或者 default.html
                        if os.path.exists(path + '/index.html'):
                            response_line = 'HTTP/1.1 200 OK\r\n'
                            response_head = 'Server:skylark 2.0\r\n'
                            file = open(path+'/index.html', 'rb')
                            response_body = file.read()
                            response = response_line.encode() + response_head.encode() +'\r\n'.encode()+response_body
                            socket_con.send(response)
                            socket_con.close()
                            return
                        elif os.path.exists(path + '/default.html'):
                            response_line = 'HTTP/1.1 200 OK\r\n'
                            response_head = 'Server:skylark 2.0\r\n'
                            file = open(path + '/default.html', 'rb')
                            response_body = file.read()
                            response = response_line.encode() + response_head.encode() + '\r\n'.encode() + response_body
                            socket_con.send(response)
                            socket_con.close()
                            return
                        else:
                            # 通路的目錄下,既沒有index.html 也沒有default.html
                            response_line = 'HTTP/1.1 404 Not Found\r\n'
                            response_head = 'Server:skylark 2.0\r\n'
                            response_head += 'Content-Type:text/html;charset=utf-8\r\n'
                            response_body = 'index.html 或者 default.html 不存在'
                            response = response_line +response_head +'\r\n' +response_body
                            socket_con.send(response.encode())
                            socket_con.close()
                        # 2、判斷伺服器是否開啟了目錄浏覽
                    else:
                        # 判斷你是否開啟了目錄浏覽
                        dir_browsing = True
                        if dir_browsing:
                            # 把使用者請求的檔案夾中所有的檔案和檔案夾以目錄的形式傳回到頁面中
                            # 擷取使用者請求的檔案夾
                            list_names = os.listdir(path)
                            response_line = 'HTTP/1.1 200 OK\r\n'
                            response_head = 'Server:skylark 2.0\r\n'
                            # 動态的拼接頁面,将目錄中的檔案或者檔案夾的名稱以HTML頁面的方式傳回給浏覽器
                            response_body = '<html><head><body><ul>'
                            for item in  list_names:
                                response_body +="<li><a href = '#'>"+item+"</a></li>"
                            response_body+='</ul></body></head></html>'
                            response =response_line + response_head +'\r\n' +response_body
                            socket_con.send(response.encode())
                            socket_con.close()
                            return

                else:
                    # 使用者請求的路徑沒有斜線
                    # 重定向到+斜線的目錄下
                    response_line = 'HTTP/1.1 302 Found\r\n'
                    response_head = 'Server:skylark 2.0\r\n'
                    response_body = 'redirect'+ path +'/'
                    response = response_line +response_head +'\r\n' +response_body
                    socket_con.send(response.encode())
                    socket_con.close()

    def run_server(self):
        # 5、通過循環,不停的接收來自用戶端的連接配接請求
        while True:
            socket_con, con_adds = self.socket_watch.accept()
            # 注意将con_adds轉成字元串
            print('用戶端:%s連接配接成功!!!' % str(con_adds))
            # 接收來自用戶端的請求,并接收請求封包,解析,傳回
            self.handle_client(socket_con)

def main():
    # sys.argv方法的用法如下:
    # 在終端輸入 python3 面向對象封裝的web伺服器.py 8888
    # 在使用解釋器執行任意py檔案的時候,可以傳入不止一個參數,會以字元串的形式用清單儲存起來
    # 但是清單的第一個參數[0]位是它自己。是以傳入的參數是從[1]第二位開始的
    # 是以在上面輸入8888以後,調取這個清單的[1]下标就會傳入這個8888作為進到下面的代碼
    # 再轉換一下類型為int就相當于使用者指定端口了
    port = int(sys.argv[1])
    http_server = HttpServer(port)
    http_server.run_server()


if __name__ == '__main__':
    main()