多進程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中讀取好看的頁面返回即可。