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()
最后加个彩蛋