python 製作web服務器和框架重點筆記 8

1 製作服務器代碼

前面我們做過簡單的基於tcp的服務器,我們這裏選用多進程的服務器進行接下來的測試。
我們初次做的服務器流程
圖1
我們可以看圖1中所示瀏覽器發送請求
瀏覽器帶有請求頭如代碼:

可以看到第一行:其中/logo1.PNG 就是瀏覽器需要請求得文件。
這個時候服務器就收到後提取出來這段地址,然後拼接我們的文件地址
./xxx/xxx/logo1.PNG然後通過打開文件讀取文件返回給瀏覽器就行了。
GET /logo1.PNG HTTP/1.1
Host: 127.0.0.1:8880
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) 
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.9 Safari/537.36
Accept: image/webp,image/apng,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8880/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9

當服務器返回瀏覽器,這個時候瀏覽器怎麼知道用什麼編碼,怎麼解析呢?
這個時候服務器需要返回的就是又一個返回的頭

//告訴瀏覽器用什麼編碼和怎麼解析
Content-Type: text/html;charset=UTF-8
//服務器的類型和版本
server: mini_fram-1.0

然後頭後面加上我們的內容就可以了

python實現服務器的代碼(多進程)

==請求頭和數據之間需要各一行
例如:

返回的header
# 這一行隔開,我們通過轉義字符\r\n進行轉義,也就是header後面跟\r\n\r\n跟兩對才行。
返回的data
import socket
import re
import multiprocessing 

# get /sdgs/sdgs  HTTP/1.1 200 ok
# 定義一個處理客戶端事件的函數
def deal_task(client_socket,client_addr):
    
    try:
    	# 讀取瀏覽器發來的請求,提取第一行數據
        recv_data = client_socket.recv(1024).decode('utf8')
        print('-'*20)
        print(recv_data)
        recv_data = recv_data.splitlines()
        # 通過正則提取數據
        request_path = re.match("[^/]+(/[^ ]*)", recv_data[0]).group(1)
        # 判斷數據請求的路徑
        if request_path == '/':
            request_path = '/index.html'
        print('請求路徑', './wwwroot'+request_path)
        # 讀取數據,以二進制讀出
        f = open("./wwwroot"+request_path, 'rb')
    except Exception as ex:
    	#準備請求頭髮送
        response_header = 'HTTP/1.1 404 NO FOUND\r\n'
        response_header += '\r\n'
        response_body = '-----no found-----'
        response_data = response_header+response_body
        client_socket.send(response_data.encode("utf8"))
        # print(ex)
    else:
    	# 讀取的數據
        response_body = f.read()
        f.close()
        response_header = 'HTTP/1.1 200 OK\r\n'
        response_header += '\r\n'
        response_data = response_header.encode("utf8")+response_body
        client_socket.send(response_data)
    client_socket.close()

def main():
    # 創建一個套接字
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 刷新端口,避免服務器三次握手等待佔用端口
    server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    # 綁定一個端口
    local_addr = ("",8888)
    server_socket.bind(local_addr)
    # 設置爲被動連接
    server_socket.listen(128)
    # 等待連接
    print('------服務器啓動-----')
    while True:
        client_socket,client_addr = server_socket.accept()
        pro = multiprocessing.Process(target=deal_task,args=[client_socket,client_addr])
        pro.start()
        # 因爲這個套接字公用一個資源,主進程關閉之後,如果子進程也關閉那麼就是關閉成功。只有兩個同時關閉纔會成功
        client_socket.close()

if __name__ == "__main__":
    main()

上面的代碼就是我們做的一個服務器,但是美中不足,我們可不可進行封裝。在主函數中直接幾行簡單的代碼就行。
下面我們進行優化

import socket
import re
import multiprocessing 

# get /sdgs/sdgs  HTTP/1.1 200 ok
class WSGIServer():
    def __init__(self):
        # 創建一個套接字
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 刷新端口
        self.server_socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 綁定一個端口
        local_addr = ("", 8888)
        self.server_socket.bind(local_addr)
        # 設置爲被動連接
        self.server_socket.listen(128)
        # 等待連接
        print('------服務器啓動-----')


    def sever_client(self,client_socket):
        try:
            recv_data = client_socket.recv(1024*1024).decode('utf8')
            print('-'*20)
            print(recv_data)
            recv_data = recv_data.splitlines()
            request_path = re.match("[^/]+(/[^ ]*)", recv_data[0]).group(1)
            if request_path == '/':
                request_path = '/index.html'
            print('請求路徑', './wwwroot'+request_path)
            f = open("./wwwroot"+request_path, 'rb')
        except Exception as ex:
            response_header = 'HTTP/1.1 404 NO FOUND\r\n'
            response_header += '\r\n'
            response_body = '-----no found-----'
            response_data = response_header+response_body
            client_socket.send(response_data.encode("utf8"))
            # print(ex)
        else:
            response_body = f.read()
            f.close()
            response_header = 'HTTP/1.1 200 OK\r\n'
            response_header += '\r\n'
            response_data = response_header.encode("utf8")+response_body
            client_socket.send(response_data)
        client_socket.close()

    def run_forever(self):
        
        while True:
            client_socket,client_addr = self.server_socket.accept()
            pro = multiprocessing.Process(target=self.sever_client, args=[
                                          client_socket])
            pro.start()
            client_socket.close()
        self.server_socket.close()

def main():
    wsgi_server = WSGIServer()
    wsgi_server.run_forever()



if __name__ == "__main__":
    main()

我們接着改,我們發現就是它的耦合性太高了,還有就是它是一個靜態的網站。我們如何做一個動態的呢,就是當他請求得是比如是一個XXXX.py的時候我們根據內容給與反饋一定的數據。

大概思路就是

if 請求的不是xxx.py:
	顯示一個原有的界面
else 如果是xxx.py:
	我們返回我們自己想讓顯示的界面

這個時候我們進行改進
web服務器段代碼

import socket
import re
import multiprocessing 
import time
import mini_fram

# get /sdgs/sdgs  HTTP/1.1 200 ok
class WSGIServer():
    def __init__(self):
        # 創建一個套接字
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 刷新端口
        self.server_socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 綁定一個端口
        local_addr = ("", 8888)
        self.server_socket.bind(local_addr)
        # 設置爲被動連接
        self.server_socket.listen(128)
        # 等待連接
        print('------服務器啓動-----')


    def sever_client(self,client_socket):
        
            recv_data = client_socket.recv(1024*1024).decode('utf8')
            print('-'*20)
            print(recv_data)
            recv_data = recv_data.splitlines()
            request_path = re.match("[^/]+(/[^ ]*)", recv_data[0]).group(1)
            if request_path == '/':
                request_path = '/index.html'
            print('請求路徑', './wwwroot'+request_path)
            # 如果以.py結尾的我們就認定爲靜態資源
            if not request_path.endswith('.py'):
                try:    
                    f = open("./wwwroot"+request_path, 'rb')
                except Exception as ex:
                    response_header = 'HTTP/1.1 404 NO FOUND\r\n'
                    response_header += '\r\n'
                    response_body = '-----no found-----'
                    response_data = response_header+response_body
                    client_socket.send(response_data.encode("utf8"))
                    # print(ex)
                else:
                    response_body = f.read()
                    f.close()
                    response_header = 'HTTP/1.1 200 OK\r\n'
                    response_header += '\r\n'
                    response_data = response_header.encode("utf8")+response_body
                    client_socket.send(response_data)
            else:
                # 如果是以.py爲結尾的我們認爲是動態資源
                # 請求頭
                response_header = "HTTP/1.1 200 OK\r\n"
                response_header += "\r\n"
                # 調用mini_fram中的application函數,請求函數,讓這個函數判斷我們的請求,並且給與返回
                response_body = mini_fram.application(request_path)
                # 組合數據發送
                response_data = response_header+response_body
                client_socket.send(response_data.encode('gb2312'))
            client_socket.close()

    def run_forever(self):
        
        while True:
            client_socket,client_addr = self.server_socket.accept()
            pro = multiprocessing.Process(target=self.sever_client, args=[
                                          client_socket])
            pro.start()
            client_socket.close()
        self.server_socket.close()

def main():
    wsgi_server = WSGIServer()
    wsgi_server.run_forever()



if __name__ == "__main__":
    main()

mini_fram.py代碼

import time


def login():
    current_time = time.ctime()
    response_body = f"<h1>登錄界面:{current_time}</h1>"
    return response_body


def register():
    current_time = time.ctime()
    response_body = f"""
    <body>
    註冊時間{current_time}<br>
    <form>
    
        First name: <input type="text" name="firstname"><br>
        Last name: <input type="text" name="lastname">
    </form>
    <body>
    
    """
    return response_body


# 這個函數進行判斷請求,並給與反饋
def application(request_path):
    if request_path == '/login.py':
        return login()
    elif request_path == '/register.py':
        return register()
    else:
        return "臣妾做不到"

看了上面的代碼我們有的同學會問,爲什麼用application當函數名,可以用其他的嗎,
我回復,當然可以啦,但是這個是一個協議的規定,這個協議就是WSGI
什麼是WSGI呢?

Web服務器網關接口(Python Web Server Gateway Interface,縮寫爲WSGI)是爲Python語言定義的Web服務器和Web應用程序或框架之間的一種簡單而通用的接口。自從WSGI被開發出來以後,許多其它語言中也出現了類似接口。

我們所瞭解的很多框架,都是遵循這個協議跟很多web服務器進行交流的。
支持WSGI的Web應用框架有很多:
BlueBream、bobo、Bottle、CherryPy、Django、Flask、Google App Engine’s webapp2
Gunicorn、prestans、Pylons、Pyramid、restlite、Tornado、Trac、TurboGears、Uliweb、web.py、web2py、weblayer、Werkzeug
在這裏插入圖片描述
我們知道了這個WSGI(不知道的,自己再搜一下)然後它的大體協議是什麼呢?

用Python語言寫的一個符合WSGI的“Hello World”應用程序如下所示:
def application(environ, start_response): 
	start_response('200 OK', [('Content-Type', 'text/plain')]) yield "Hello 	world!\n"
其中
第一行定義了一個名爲 application的 callable,接受兩個參數,environ 和 start_response,environ 是一個字典包含了 CGI 中的環境變量。
第二行調用了start_response,狀態指定爲“200 OK”,消息頭指定爲內容類型是“text/plain”。start_response 也是一個 callable,接受兩個必須的參數,status(HTTP 狀態)和 response_headers(響應消息的頭)。
第三行將響應消息的消息體返回。

那麼接下來我們修改我們的框架,加上相應的函數的參數
我們的目錄結構,

dynamic:放入我們的框架,就是我們的application函數所在的py文件
static:中我們放入我們需要的css js images font等等我們的靜態代碼
templates:中是我們的動態網頁我們的框架調用的網頁文件等。

在這裏插入圖片描述
在這裏插入圖片描述

import socket
import re
import multiprocessing 
import time
import dynamic.mini_fram_wsgi as mini_fram_wsgi

# get /sdgs/sdgs  HTTP/1.1 200 ok
class WSGIServer():
    def __init__(self):
        # 創建一個套接字
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 刷新端口
        self.server_socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 綁定一個端口
        local_addr = ("", 8888)
        self.server_socket.bind(local_addr)
        # 設置爲被動連接
        self.server_socket.listen(128)
        # 等待連接
        print('------服務器啓動-----')


    def sever_client(self,client_socket):
        
            recv_datas = client_socket.recv(1024*1024).decode('utf8')
            print('-'*20)
            print(recv_datas)
            recv_data = recv_datas.splitlines()
            request_path = re.match("[^/]+(/[^ ]*)", recv_data[0]).group(1)
            if request_path == '/':
                request_path = '/index.html'
            print('請求路徑', './static'+request_path)
            # 如果以.py結尾的我們就認定爲靜態資源
            if not request_path.endswith('.py'):
                try:    
                    f = open("./static"+request_path, 'rb')
                except Exception as ex:
                    response_header = 'HTTP/1.1 404 NO FOUND\r\n'
                    response_header += '\r\n'
                    response_body = '-----no found-----'
                    response_data = response_header+response_body
                    client_socket.send(response_data.encode("utf8"))
                    # print(ex)
                else:
                    response_body = f.read()
                    f.close()
                    response_header = 'HTTP/1.1 200 OK\r\n'
                    response_header += '\r\n'
                    response_data = response_header.encode("utf8")+response_body
                    client_socket.send(response_data)
            else:
                # 如果是以.py爲結尾的我們認爲是動態資源
                # # 請求頭
                # response_header = "HTTP/1.1 200 OK\r\n"
                # response_header += "\r\n"
                # 調用請求函數
                environ = dict()
                environ["PATH_INFO"] = request_path
                這裏我們加入的調用函數
                response_body = mini_fram_wsgi.application(
                    environ, self.set_response_header)
                # 整合數據
                response_header = "HTTP/1.1 %s\r\n" % self.status
                for temp in self.headers:
                    response_header += "%s:%s\r\n" % (temp[0],temp[1])
                response_header +='\r\n'
                # 組合數據發送
                response_data = response_header+response_body
                client_socket.send(response_data.encode('utf8'))
            client_socket.close()
	==再這裏我們加上我們收到來自函數application的請求==
    def set_response_header(self,status,headers):
        self.status = status
        self.headers =[("server","mini_fram-1.0")]
        self.headers += headers


    def run_forever(self):
        
        while True:
            client_socket,client_addr = self.server_socket.accept()
            pro = multiprocessing.Process(target=self.sever_client, args=[
                                          client_socket])
            pro.start()
            client_socket.close()
        self.server_socket.close()

def main():
    wsgi_server = WSGIServer()
    wsgi_server.run_forever()



if __name__ == "__main__":
    main()

dynamci中的文件

import time


def index():
    with open("./templates/index.html",'r') as f:
        response_body = f.read()
    return response_body


def about():
    with open("./templates/about.html", 'r',encoding='utf8') as f:
        response_body = f.read()
    return response_body


def single():
    with open("./templates/single.html", 'r', encoding='utf8') as f:
        response_body = f.read()
    return response_body


def contact():
    with open("./templates/contact.html", 'r') as f:
        response_body = f.read()
    return response_body


def register():
    current_time = time.ctime()
    response_body = f"""
    <body>
    註冊時間{current_time}<br>
    <form>
    
        First name: <input type="text" name="firstname"><br>
        Last name: <input type="text" name="lastname">
    </form>
    <body>
    
    """
    return response_body



def application(environ,start_response):
    print(environ["PATH_INFO"])
    if environ["PATH_INFO"] == '/register.py':
        start_response(
            '200 OK', [('Content-Type', 'text/html;charset=UTF-8'),("server", "大哥")])
        return register()

    elif environ["PATH_INFO"] == '/index.py':
        start_response(
            '200 OK', [('Content-Type', 'text/html;charset=UTF-8'), ("server", "二哥")])
        return index()

    elif environ["PATH_INFO"] == '/about.py':
        start_response(
            '200 OK', [('Content-Type', 'text/html;charset=UTF-8'), ("server", "二哥")])
        return about()

    elif environ["PATH_INFO"] == '/contact.py':
        start_response(
            '200 OK', [('Content-Type', 'text/html;charset=UTF-8'), ("server", "二哥")])
        return contact()

    elif environ["PATH_INFO"] == '/single.py':
        start_response(
            '200 OK', [('Content-Type', 'text/html;charset=UTF-8'), ("server", "二哥")])
        return single()

    else:
        start_response(
            '404 NO', [('Content-Type', 'text/html;charset=UTF-8'), ("server", "二哥")])
        return "<h1>沒有的</h1>"

我們看到這個框架竟然可以設計我們的服務器版本,這就有點不好了吧
這個時候我們需要再服務器中設計好,這個屬性。這樣降低耦合性就更好了
我們再加上配置文件。配置文件中

{	# 靜態文件路徑
    "static_path":"./static",
    # 動態框架文件路徑
    "dynamic_path":"./dynamic",
    # 端口號
    "port":"8880",
    # 框架文件名,用於導入
    "web_fram_name":"mini_fram_wsgi",
    # 框架的函數名
    "web_app_name":"application"
}

所以最終的版本
文件目錄

在這裏插入圖片描述
主要改服務器的代碼

import socket
import re
import multiprocessing
import time
import dynamic.mini_fram_wsgi as mini_fram_wsgi
import sys


# get /sdgs/sdgs  HTTP/1.1 200 ok
class WSGIServer(object):
    def __init__(self, port, app, static_path):
        self.static_path = static_path
        # 創建一個套接字
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # 刷新端口
        self.server_socket.setsockopt(
            socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # 綁定一個端口
        local_addr = ("", port)
        self.server_socket.bind(local_addr)
        # 設置爲被動連接
        self.server_socket.listen(128)
        # 等待連接
        self.application = app

        print('------服務器啓動-----')

    def sever_client(self, client_socket):

        recv_datas = client_socket.recv(1024 * 1024).decode('utf8')
        print('-' * 20)
        print(recv_datas)
        recv_data = recv_datas.splitlines()
        request_path = re.match("[^/]+(/[^ ]*)", recv_data[0]).group(1)
        if request_path == '/':
            request_path = '/index.html'
        print('請求路徑', self.static_path + request_path)
        # 如果以.py結尾的我們就認定爲靜態資源
        if not request_path.endswith('.py'):
            try:
                f = open(self.static_path + request_path, 'rb')
            except Exception as ex:
                response_header = 'HTTP/1.1 404 NO FOUND\r\n'
                response_header += '\r\n'
                response_body = '-----no found-----'
                response_data = response_header + response_body
                client_socket.send(response_data.encode("utf8"))
                # print(ex)
            else:
                response_body = f.read()
                f.close()
                response_header = 'HTTP/1.1 200 OK\r\n'
                response_header += '\r\n'
                response_data = response_header.encode("utf8") + response_body
                client_socket.send(response_data)
        else:
            # 如果是以.py爲結尾的我們認爲是動態資源
            # # 請求頭
            # response_header = "HTTP/1.1 200 OK\r\n"
            # response_header += "\r\n"
            # 調用請求函數
            environ = dict()
            environ["PATH_INFO"] = request_path
            response_body = self.application(
                environ, self.set_response_header)
            # 整合數據
            response_header = "HTTP/1.1 %s\r\n" % self.status
            for temp in self.headers:
                response_header += "%s:%s\r\n" % (temp[0], temp[1])
            response_header += '\r\n'
            # 組合數據發送
            response_data = response_header + response_body
            client_socket.send(response_data.encode('utf8'))
        client_socket.close()

    def set_response_header(self, status, headers):
        self.status = status
        self.headers = [("server", "mini_fram-1.0")]
        self.headers += headers

    def run_forever(self):
        while True:
            client_socket, client_addr = self.server_socket.accept()
            pro = multiprocessing.Process(target=self.sever_client, args=[
                client_socket])
            pro.start()
            client_socket.close()
        # self.server_socket.close()


def main():
    # print(len(sys.argv))
    # if len(sys.argv) == 3:
    #     try:
    #         port = int(sys.argv[1])
    #         mini_fram_app_name = sys.argv[2]
    #     except Exception as identifier:
    #         print("請輸入一個端口爲整數")
    #         return
    # else:
    #     print("程序輸入參數有誤,按照下面的格式")
    #     print("python xxx.py 6666 mini_fram_wsgi:application")
    #     return
    #
    # mini_fram_names = re.match("([^:]+):(.+)", mini_fram_app_name)
    # if mini_fram_names:
    #     mini_fram_name = mini_fram_names.group(1)
    #     mini_app_name = mini_fram_names.group(2)
    # else:
    #     print("程序輸入參數有誤,按照下面的格式")
    #     print("python xxx.py 8asd8 mini_fram_wsgi:application")
    #     return
    # 打開配置文件,通過eval生成一個字典
    with open("web_server.conf", 'r')as f:
        config_dict = eval(f.read())
    # 獲取框架的名字
    mini_fram_name = config_dict["web_fram_name"]
    # 獲取函數的名字
    mini_app_name = config_dict["web_app_name"]
    # 獲取端口名字
    port = int(config_dict["port"])
    # 去這個文件夾中搜索我們的框架。py文件
    sys.path.append(config_dict["dynamic_path"])
    # 返回一個模塊
    frame = __import__(mini_fram_name)
    # 得到模塊的application方法
    app = getattr(frame, mini_app_name)
    print(app)
    wsgi_server = WSGIServer(port, app, config_dict["static_path"])
    wsgi_server.run_forever()


if __name__ == "__main__":
    main()

最後加個彩蛋

在這裏插入圖片描述
在這裏插入圖片描述

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