WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述web server如何与web application通信的规范。
WSGI协议其实是定义了一种server与application解耦的规范,即可以有多个实现WSGI server的服务器,也可以有多个实现WSGI application的框架,那么就可以选择任意的server和application组合实现自己的web应用。
1、服务器代码 web_server.py
import sys
from socket import *
import re
import multiprocessing
class WSGI_Http(object):
"""遵循WSGI协议的http服务器"""
def __init__(self, port, static_path, function_name):
"""服务器的初始化"""
# 创建套接字
self.tcp_server = socket(AF_INET, SOCK_STREAM)
# 端口重复使用
self.tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 绑定(bind)
self.tcp_server.bind(("", port))
# 监听(listen)
self.tcp_server.listen(128)
# 用实例属性来存储 static的路径信息 和 模块中application函数的引用
self.static = static_path
self.application = function_name
def client_request(self, new):
"""浏览器的请求"""
# 将浏览器的请求转换成列表
request_data = new.recv(1024).decode("utf-8").splitlines()
print(request_data)
print("------------------------请求信息------------------------------")
# 根据浏览器请求的路径响应对应的页面
rul = re.match(r"[^/]+(/[^ ]*)", request_data[0]) # [^/]:从左往右取,不是/都不取。 +:必须有/。 (/[^ ]*):从/开始取到空格
if rul:
file_name = rul.group(1)
if file_name == "/": # 如果请求没有指定要访问的页面,默认是/(/默认访问的就是主页)
file_name = "/index.html"
return file_name
def static_page(self,new , file_name):
"""静态页面处理"""
try:
f = open(self.static + file_name, "rb")
except:
response_header = "HTTP/1.1 404 NOT FOUND \r\n"
response_header += "\r\n"
response_header += "file not found"
new.send(response_header.encode("utf-8"))
else:
response_body = f.read()
f.close()
# header:响应的是一个头部信息
response_header = "HTTP/1.1 200 OK \r\n"
response_header += "\r\n"
# 将header和body响应给浏览器
response = response_header.encode("utf-8") + response_body
new.send(response)
def dynamic_page(self,new ,file_name):
"""动态页面处理"""
# 定义一个空字典来存放用户请求
environ = dict()
environ["PATH_NAME"] = file_name
# (引用)调用框架中的application()函数,把 请求 和set_header()这个方法作为实参传递给application()函数
response_body = self.application(environ, self.set_header)
# 此时set_header()中的第一个形参(status_number)接收的是框架中传递过来的状态码
response_header = "HTTP/1.1 %s\r\n"%self.status_number
# 通过for in 遍历set_header()中第二个形参(descirbe)拿到框架中传递过来的描述信息
for demo in self.descirbe:
response_header += "%s:%s\r\n"%(demo[0],demo[1])
response_header += "\r\n"
# 拼接响应头和响应体
response = response_header + response_body
# 响应给浏览器
new.send(response.encode("utf-8"))
def set_header(self, status_number, describe): # status_number:接收状态码 decirbe:接收描述信息
"""存储框架传递过来的实参"""
self.status_number = status_number
self.descirbe = describe
self.descirbe += [("server_version","ChuangXin 3.0")]
def server_response(self, new, file_name):
"""服务器的响应:给浏览器的http格式的数据(header和body)"""
# 如果请求的页面不是以.py结尾的(用字符串的endswith()方法获得后缀)
if not file_name.endswith(".py"):
# 不是以.py结尾的页面也就是执行静态页面,调用static_page()这个方法
self.static_page(new, file_name)
else:
# 是以.py结尾的页面也就是执行动态页面
self.dynamic_page(new, file_name)
def request_response(self, new):
"""浏览器的请求和服务器的响应"""
# 调用请求的函数
file_name = self.client_request(new)
# 调用响应的函数
self.server_response(new, file_name)
# 关闭客户端套接字
new.close()
def run(self):
""""""
while True:
# 等待客户端的连接(accept)
new, other = self.tcp_server.accept()
# 创建进程
p1 = multiprocessing.Process(target=self.request_response, args=(new,))
# 开启进程
p1.start()
# 关闭主进程套接字,因为子进程执行前会复制一份主进程的代码,用fd:文件的描述符
new.close()
# 关闭服务器套接字
tcp_server.close()
def control():
"""让服务器与框架之间降低耦合程度"""
# sys.argv:输入的是一个列表 如['python3','web_server.py','7896','mini_frame','application']
# 长度判断
if len(sys.argv) == 3:
# 端口判断
try:
port = int(sys.argv[1]) # 拿到输入的端口,如果端口占用,抛出异常
frame_name = (sys.argv[2]) # 拿到的是框架名与框架中的函数名
except Exception as e:
print("端口输入有误!")
return "0"
else:
print("请按以下格式书写")
print("python3 服务器名称.py 端口号 框架名称:application")
return "0"
# 用正则表达式拿到sys.argv返回的框架名与函数名
rel = re.match(r"([^:]+):(.*)",frame_name)
if rel:
frame_name = rel.group(1)
function_name = rel.group(2)
else:
print("请按以下格式书写")
print("python3 服务器名称.py 端口号 框架名称:application")
return "0"
# 打开服务器的配置文件,用eval函数按照键值对格式读取路径信息
with open("./web_server.conf") as f:
conf_info = eval(f.read())
# 添加配置文件里面的键,也就是路径
sys.path.append(conf_info["dynamic_path"])
# 用内置方法__import__()导入框架这个模块,它返回的是一个对象,标记导入了这个模块
in_frame_name = __import__(frame_name)
#用point_function指向了/dynamic/mini_frame/application这个函数
point_function = getattr(in_frame_name,function_name)
# 将多个值返回给函数的调用者
return port,conf_info["static_path"],point_function
def main():
# 调用control函数
if control() in ["0"]:
return
port,static_path,point_function = control()
# 实例化对象
wsgi_http = WSGI_Http(port, static_path, point_function)
#对象调用方法开启服务器
wsgi_http.run()
if __name__ == '__main__':
main()
2、框架代码 mini_frame.py
import re
from pymysql import *
URL_RESPONSE = dict()
def connect_join(sql):
"""初始化connect连接对象"""
conn = connect(host="localhost",port=3306,user="root",password="root",database="jing_dong",charset="utf8")
cursor = conn.cursor()
cursor.execute(sql)
cursor_content = cursor.fetchall()
cursor.close()
conn.close()
return cursor_content
# 定义一个装饰器,把请求(flie_name)当做key,把需要装饰的函数当做value,二者进行判断
def set_url(url): # url:请求的地址
def set_func(func): # func:被装饰函数的引用
URL_RESPONSE[url] = func # 把请求的地址(url)当做key,把需要装饰的函数当做value,然后存入字典
def call_func(*args,**kwargs):
return func(*args,**kwargs)
return call_func
return set_func
@set_url("/index.html")
def index():
with open("./templates/index.html") as f:
content = f.read()
sql1 = "select * from goods_cates"
goods_cates = connect_join(sql1)
sql2 = "select * from goods_brands"
goods_brands = connect_join(sql2)
sql3 = "select id,name,price from goods"
goods = connect_join(sql3)
html1 = ""
for line_info in goods_cates:
html1 += "%s %s " %(line_info[0],line_info[1])
content = re.sub(r"\{%content1%\}",html1,content)
html2 = ""
for line_info in goods_brands:
html2 += "%s %s " %(line_info[0],line_info[1])
content = re.sub(r"\{%content2%\}",html2,content)
a = """
%s %s %s
"""
html3 = ""
for line_info in goods:
html3 += a%(line_info[0],line_info[1],line_info[2])
content = re.sub(r"\{%content3%\}",html3,content)
return content
@set_url("/shopping.html")
def shopping():
with open("./templates/shopping.html") as f:
content= f.read()
conn = connect(host="localhost",port=3306,user="root",password="root",database="jing_dong",charset="utf8")
cursor = conn.cursor()
sql = "select g.id,c.name,g.name,d.name,g.price from goods as g inner join goods_cates as c on g.cate_id=c.id inner join goods_brands as d on g.brand_id=d.id order by g.id"
cursor.execute(sql)
shop = cursor.fetchall()
cursor.close()
conn.close()
tr_temp = """
%s
%s
%s
%s
%s
"""
html = ""
for line_info in shop:
html += tr_temp %(line_info[0],line_info[1],line_info[2],line_info[3],line_info[4])
content = re.sub(r"\{%content%\}",html,content)
return content
@set_url("/zcjm.html")
def zcjm():
with open("./templates/zcjm.html") as f:
return f.read()
@set_url("/dljm.html")
def dljm():
with open("./templates/dljm.html") as f:
return f.read()
def application(environ,set_header):
# 此时的save_header()就是调用了服务器里面的set_header(status_number, describe)方法,把'200 OK'作为第一个参数传递过去,
# 把[('Content-Type','text/html;charset=utf-8')]作为第二个参数传递过去,传递过去之后通过for in 遍历拿到请求头信息,
set_header('200 OK',[('Content-Type','text/html;charset=utf-8')])
file_name = environ['PATH_NAME'] # 服务器中以"PATH_NAME"为key,把请求内容当做value存放到字典中,此时,通过字典的key("PATH_NAME)来获取value(file_name)
try:
key = URL_RESPONSE[file_name] # 把请求当做key在字典中寻找对应的key
return key()
except Exception as rel:
return "发生了异常具体如下:%s"%str(rel)
3、服务器配置文件 web_server.conf
{
"static_path":"./static",
"dynamic_path":"./dynamic"
}
4、shell脚本 run.sh
python3 web_server.py 7896 mini_frame:application