多進程web動態伺服器
- 2019 年 10 月 6 日
- 筆記
次會用到我們上次寫的多進程伺服器
我們既然學習了 面向對象,就用面向對象來改進一個這個程式:
import socket
import re
import multiprocessing
class server(object):
def __init__(self):
self.tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 關閉套接字後釋放資源
self.tcp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
self.tcp_server.bind(("",7891))
self.tcp_server.listen(128)
print('等待')
def dump_data(self,cli_socket):
recv_data = cli_socket.recv(1024).decode('utf-8')
recv_data_lines = recv_data.splitlines()
print(recv_data_lines)
ret = re.match(r'[^/]+(/[^ ]*)',recv_data_lines[0])
if ret:
f_name = ret.group(1)
if f_name=="/":
f_name = '/index.html'
try:
file = open('F:'+f_name,'rb')
except:
resp_data = 'HTTP/1.1 404 NOT FOUNDrn'
resp_data += 'Content-Type:text/html;charset:uft-8rn'
resp_data += 'rn'
resp_data += '404'
cli_socket.send(resp_data.encode('utf-8'))
else:
html_con = file.read()
file.close()
resp_data = 'HTTP/1.1 200 OKrn'
resp_data += 'Content-Type:text/html;charset:uft-8rn'
resp_data += 'rn'
cli_socket.send(resp_data.encode('utf-8'))
cli_socket.send(html_con)
cli_socket.close()
def run_server(self,):
while True:
cli_socket,cli_addr = self.tcp_server.accept()
# 多進程實現調用該方法
p = multiprocessing.Process(target=self.dump_data,args =(cli_socket,))
p.start()
cli_socket.close()
self.tcp_server.close()
def main():
wsgiserver = server()
wsgiserver.run_server()
if __name__ == "__main__":
main()
現在我們返回的數據都是靜態的,也就是說寫好的,直接返回給瀏覽器,但是這並不能滿足我們的需求,
比如今日頭條裡面的內容都是動態的,我們不能說每天都去修改html程式碼。
接下來我們來寫一個可以解析動態的web伺服器。
我們簡單模擬一下,首先說一下思路,我們認為客戶端如果請求的是.py結尾的文件,我們認為他請求的動態頁面,我們給他返回一個隨機數。(py文件是動態是我們暫時這樣認為)
import socket
import re
import multiprocessing
import random
class server(object):
def __init__(self):
self.tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 關閉套接字後釋放資源
self.tcp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
self.tcp_server.bind(("",7891))
self.tcp_server.listen(128)
print('等待')
def dump_data(self,cli_socket):
recv_data = cli_socket.recv(1024).decode('utf-8')
recv_data_lines = recv_data.splitlines()
print(recv_data_lines)
ret = re.match(r'[^/]+(/[^ ]*)',recv_data_lines[0])
if ret:
f_name = ret.group(1)
if f_name=="/":
f_name = '/index.html'
# 添加判斷是否是.py結尾
if f_name.endswith(".py"):
head = 'HTTP/1.1 404 NOT FOUNDrn'
head += 'rn'
# 生成一個0-100的隨機數
body = random.randint(0,100)
resp_data = head + body
cli_socket.send(resp_data.encode('utf-8'))
else:
try:
file = open('F:'+f_name,'rb')
except:
resp_data = 'HTTP/1.1 404 NOT FOUNDrn'
resp_data += 'Content-Type:text/html;charset:uft-8rn'
resp_data += 'rn'
resp_data += '404'
cli_socket.send(resp_data.encode('utf-8'))
else:
html_con = file.read()
file.close()
resp_data = 'HTTP/1.1 200 OKrn'
resp_data += 'Content-Type:text/html;charset:uft-8rn'
resp_data += 'rn'
cli_socket.send(resp_data.encode('utf-8'))
cli_socket.send(html_con)
cli_socket.close()
def run_server(self,):
while True:
cli_socket,cli_addr = self.tcp_server.accept()
# 多進程實現調用該方法
p = multiprocessing.Process(target=self.dump_data,args =(cli_socket,))
p.start()
cli_socket.close()
self.tcp_server.close()
def main():
wsgiserver = server()
wsgiserver.run_server()
if __name__ == "__main__":
main()
此時在瀏覽器中輸入127.0.0.1:7891/a.py 或者以.py結尾的都會返回一個隨機數,刷新會有變化。
這樣寫是實現了我們想要的效果,但是耦合性太高,我們可以把動態頁面,也就是py文件單獨寫出來,只要是動態頁面,我們讓他執行該py文件即可,這是解耦的體現。
首先我們在當前文件夾下新建一個py文件寫上:
import random
def login():
return f'歡迎登陸{random.randint(0,100)}'
將上面的web伺服器修改:
1、導入該模組
import login
將動態部分的body修改成
body = login.login()
我們發現缺點很多,首先要導入模組,而且動態模組只有一個,要想有更多還有if判斷。
最終還是要在伺服器中國調用函數,我們想要把他分開。
很簡單,我們把請求的方法從伺服器傳過去,在py文件中判斷就好了。自己嘗試一下。
我們剛剛寫的login.py相當於一個很小的web框架,我們的很low,就就用別人寫的,比如Django,在框架我伺服器之前如何動態的傳輸?也有一套規則,我們將這套規則叫做WSGI協議。
WSGI協議
通俗的說:
1、框架中必須要有一個application方法
2、application需要有兩個參數
3、兩個參數:一個是字典,一個是對函數的引用
4、利用第二個參數(函數)獲取到發送過來的header資訊等返回給伺服器狀態和頭資訊
5、框架通過查詢資料庫等,生成一個動態的body,再發送給伺服器
簡單實現一個WSGI協議
簡單實現返回 Holle World
將上面的login文件裡面的內容全部刪掉,寫上:
def application(evairon,start_response):
# 第一個參數返回狀態碼,第二個以鍵值的方式返回你想添加的head部分
start_response('200 ok',[('Content-Type','text/html')])
# 返回給網頁的內容
return 'Holle World'
web伺服器修改成:
import socket
import re
import multiprocessing
import login
class server(object):
def __init__(self):
self.tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 關閉套接字後釋放資源
self.tcp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
self.tcp_server.bind(("",7891))
self.tcp_server.listen(128)
print('等待')
def dump_data(self,cli_socket):
recv_data = cli_socket.recv(1024).decode('utf-8')
recv_data_lines = recv_data.splitlines()
print(recv_data_lines)
ret = re.match(r'[^/]+(/[^ ]*)',recv_data_lines[0])
if ret:
f_name = ret.group(1)
if f_name=="/":
f_name = '/index.html'
# 添加判斷是否是.py結尾
if f_name.endswith(".py"):
# 要傳入兩個值,第一個為字典,第二個為一個方法
# 先定義一個空字典
# 傳入一個方法接收heads值
env = dict()
body = login.application(env,self.set_start_response)
head = f'HTTP/1.1 {self.status}rn'
for item in self.heads:
head += f'{item[0]}:{item[1]}rn'
head += 'rn'
resp_data = head + body
cli_socket.send(resp_data.encode('utf-8'))
else:
try:
file = open('F:'+f_name,'rb')
except:
resp_data = 'HTTP/1.1 404 NOT FOUNDrn'
resp_data += 'Content-Type:text/html;charset:uft-8rn'
resp_data += 'rn'
resp_data += '404'
cli_socket.send(resp_data.encode('utf-8'))
else:
html_con = file.read()
file.close()
resp_data = 'HTTP/1.1 200 OKrn'
resp_data += 'Content-Type:text/html;charset:uft-8rn'
resp_data += 'rn'
cli_socket.send(resp_data.encode('utf-8'))
cli_socket.send(html_con)
cli_socket.close()
def set_start_response(self,status,heads):
self.status = status
self.heads = heads
def run_server(self,):
while True:
cli_socket,cli_addr = self.tcp_server.accept()
p = multiprocessing.Process(target=self.dump_data,args =(cli_socket,))
p.start()
cli_socket.close()
self.tcp_server.close()
def main():
wsgiserver = server()
wsgiserver.run_server()
if __name__ == "__main__":
main()
這樣只要在瀏覽器輸出以.py結尾的就會返回Holle World,如果想要添加中文,需要把login中的添加成:
start_response('200 ok',[('Content-Type','text/html;charset=utf-8')])即可。
我也想實現我的伺服器:
修改成:start_response('200 ok',[('Content-Type','text/html;charset=utf-8'),('server','zhansgan 1.0')]),這樣我們在瀏覽器中就可以看到我們設置的伺服器。但是WSGI就是負責和伺服器交互的,我們需要在伺服器中修改。將set_start_response方法修改成:
def set_start_response(self,status,heads):
self.status = status
self.heads = [('server','zhangsan v1.0')]
self.heads += heads
由此我們看出set_start_response方法就是將伺服器的東西和客戶端發送過來的數據進行整合。
說完了這個函數的作用,我們來說第一個字典的作用,上面我們傳了一個空字典,我們來看看它的作用。
首先我們只要訪問以.py結尾的都會去訪問該方法,我能不能傳入login.py去訪問登陸,create.py去訪問註冊頁面?
這個字典就可以實現這個效果。
將login.py修改成:
def login():
return '<h1>登錄的頁面</h1>'
def create():
return '<h1>註冊的頁面</h1>'
def application(evairon,start_response):
# 第一個參數返回狀態碼,第二個以鍵值的方式返回你想添加的head部分
start_response('200 ok',[('Content-Type','text/html;charset=utf-8')])
# 返回給網頁的內容
if evairon['path_info'] == '/login.py':
return login()
elif evairon['path_info'] == '/create.py':
return create()
為伺服器中的字典傳入我們的頁面名字的值:
env['path_info'] = f_name
即可。運行,在瀏覽器中是127.0.0.1:7891/login.py就會顯示我是登錄頁面,create就會顯示我是註冊頁面。
當然,字典里你也可以添加一些你想要的東西。
如果你想顯示好看的頁面可以在login.py中讀取好看的頁面返回即可。