1 製作服務器代碼
前面我們做過簡單的基於tcp的服務器,我們這裏選用多進程的服務器進行接下來的測試。
我們初次做的服務器流程
我們可以看圖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()
最後加個彩蛋