多进程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中读取好看的页面返回即可。