【python 笔记】WSGI mini-web框架
目录
0. 前导准备(了解)
0.1. 面向对象实现多进程web服务器:
先将之前 多进程web服务器的程序 改为 面向对象实现多进程web服务器(运行前准备好.html资源)。
- 创建套接字在 __init__方法 创建;
- 服务的执行放在实例方法 run_forever
- 程序运行后,在浏览器输入地址端口号:http://127.0.0.1:7890/ ,可以打开准备好的 .html 。
web_server.py
import socket
import re
import multiprocessing
class WSGIServer(object):
def __init__(self):
# 1. 创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定
self.tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
self.tcp_server_socket.listen(128)
def service_client(self, new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
# print("*"*50, file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 返回http格式的数据,给浏览器
try:
f = open("./html" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "------file not found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
# 关闭套接
new_socket.close()
def run_forever(self):
"""用来完成整体的控制"""
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 为这个客户端服务
p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
p.start()
new_socket.close()
# 关闭监听套接字
self.tcp_server_socket.close()
def main():
"""控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行"""
wsgi_server = WSGIServer()
wsgi_server.run_forever()
if __name__ == "__main__":
main()
以上可以实现:
- 浏览器 发送请求(如 index.html)
- http服务器 解析出请求 index.html
- 服务器从硬盘等读取 index.html
- 返回数据给浏览器
0.2. 实现解析动态请求
思考:
以上程序的请求(index.html)是是提前写好的,是静态的。意味着请求资源时,返回的结果是固定的响应,达不到动态请求的效果。而数据库内容、新闻网页等需求是动态更新的。
(1)web服务器里集成解析动态请求的功能:
用*.py模拟动态请求(只要是.py结尾就当作是动态请求)。返回的内容 利用time.ctime()来体现动态内容。
import socket
import re
import multiprocessing
import time
class WSGIServer(object):
def __init__(self):
# 1. 创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定
self.tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
self.tcp_server_socket.listen(128)
def service_client(self, new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
# print("*"*50, file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 返回http格式的数据,给浏览器
# 2.1 如果请求的资源不是以.py结尾,那么就认为是静态资源(html/css/js/png,jpg等)
if not file_name.endswith(".py"):
try:
f = open("./html" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "------file not found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
else:
# 2.2 如果是以.py结尾,那么就认为是动态资源的请求
header = "HTTP/1.1 200 OK\r\n"
header += "\r\n"
body = "hahahah %s " % time.ctime() # 利用time.ctime()来体现动态内容
response = header+body
# 发送response给浏览器
new_socket.send(response.encode("utf-8"))
# 关闭套接
new_socket.close()
def run_forever(self):
"""用来完成整体的控制"""
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 为这个客户端服务
p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
p.start()
new_socket.close()
# 关闭监听套接字
self.tcp_server_socket.close()
def main():
"""控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行"""
wsgi_server = WSGIServer()
wsgi_server.run_forever()
if __name__ == "__main__":
main()
注意:以上一个.py程序实现了两个功能,即 http服务器 和 完成数据处理的逻辑过程。这在一定程度上是不合理的。应该用两个py实现,以解耦合,一个py实现http服务器处理浏览器的请求,一个py实现页面的逻辑过程。
(2)将web服务器和逻辑处理的代码分开
将以上代码拆分为两个文件:mini_frame.py 和 web_server.py 。这样对其中一个功能修改时较为安全,不会出现混乱。
mini_frame.py
import time
def login():
return "i----login--welcome hahahh to our website.......time:%s" % time.ctime()
def register():
return "-----register---welcome hahahh to our website.......time:%s" % time.ctime()
def profile():
return "-----profile----welcome hahahh to our website.......time:%s" % time.ctime()
def application(file_name):
if file_name == "/login.py":
return login()
elif file_name == "/register.py":
return register()
else:
return "not found you page...."
web_server.py
import socket
import re
import multiprocessing
import time
import mini_frame
class WSGIServer(object):
def __init__(self):
# 1. 创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定
self.tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
self.tcp_server_socket.listen(128)
def service_client(self, new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
# print("*"*50, file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 返回http格式的数据,给浏览器
# 2.1 如果请求的资源不是以.py结尾,那么就认为是静态资源(html/css/js/png,jpg等)
if not file_name.endswith(".py"):
try:
f = open("./html" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "------file not found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
else:
# 2.2 如果是以.py结尾,那么就认为是动态资源的请求
header = "HTTP/1.1 200 OK\r\n"
header += "\r\n"
# 调用方法1:不太合理
# body = mini_frame.login() # 该处存在一定的不合理性,因为不一定是调用mini_frame模块的login方法
# 调用方法2:在其他模块定义一个方法、server再通过该方法调用模块的其他方法
body = mini_frame.application(file_name)
response = header+body
# 发送response给浏览器
new_socket.send(response.encode("utf-8"))
# 关闭套接
new_socket.close()
def run_forever(self):
"""用来完成整体的控制"""
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 为这个客户端服务
p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
p.start()
new_socket.close()
# 关闭监听套接字
self.tcp_server_socket.close()
def main():
"""控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行"""
wsgi_server = WSGIServer()
wsgi_server.run_forever()
if __name__ == "__main__":
main()
注:
- 如果 sever 调用其他模块时写死了调用的模块的方法(如调用方法1的 body = mini_frame.login) ,则存在一定的不合理性,因为不一定是调用mini_frame模块的login方法,做不到灵活使用。
- sever 应该永远只调用某个模块的一个方法,达到解耦合。如调用方法2在mini_frame模块定义一个方法application、server永远通过该方法调用模块的其他方法。 如在浏览器输入 http://127.0.0.1:7890/login.py 则调用login方法...。
1. WSGI(重点)
以下对WSGI做了简介,并通过多个实例层层改进逐步对WSGI进行理解!
1.1. WSGI介绍
通过上一章节,实现了简单的方案实现http服务器。但其不能支持框架的运行,无法适应高并发等,没有实际使用价值。实际中应该使用广泛使用的服务器。
- 市面上有许多的服务器和框架的种类。但当要把一个服务器和一个架构结合起来时,可能会发现他们不是被设计成协同工作的。
- Python Web Server Gateway Interface (简称 WSGI,读作“wizgy”) 可以不修改服务器和架构代码而确保可以在多个架构下运行web服务器。
- WSGI允许开发者将选择web框架和web服务器分开。可以混合匹配web服务器和web框架,选择一个适合的配对。比如,可以在Gunicorn 或者 Nginx/uWSGI 或者 Waitress上运行 Django, Flask, 或 Pyramid。WSGI同时支持服务器和架构 可以实现混合匹配。
- web服务器必须具备WSGI接口,所有的现代Python Web框架都已具备WSGI接口,它让你不对代码作修改就能使服务器和特点的web框架协同工作。
WSGI同时支持服务器和架构:
1.2. 定义WSGI接口
想要实现WSGI协议,Web框架需要实现一个可以直接调用的东西(比如函数)。
WSGI接口定义只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的“Hello World!”:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')]) # 返回web的header:状态码、列表
return 'Hello World!' # return的是web的body!
上面的application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
- environ:一个包含所有HTTP请求信息的 字典dict对象 ;
- start_response:一个发送HTTP响应的函数。
整个application()
函数本身没有涉及到任何解析HTTP的部分,其把底层web服务器解析部分和应用程序逻辑部分进行了分离,开发者可以专心于其一。application()
函数必须由WSGI服务器来调用,而不是我们手动调用。有很多符合WSGI规范的服务器。
1.3. 让web服务器支持WSGI协议
mini_frame.py
import time
def application(environ, start_response): # 最基本的要有一个application函数,第一个参数传字典、第二个参数传函数
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
return 'Hello World!...time is:%s . ' % time.ctime() # 返回作为body
web_server.py
注意对框架mini_frame 中 application方法 的调用。
- 传入字典、函数引用作为 mini_frame框架application方法 的参数,其返回值作为body
- set_response_header函数引用传给application后,set_response_header 在 application方法获取header信息、把header作为实例属性保存下来。
- 在web服务器中组成 header和body ,用返回给浏览器。
import socket
import re
import multiprocessing
import time
import mini_frame
class WSGIServer(object):
def __init__(self):
# 1. 创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定
self.tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
self.tcp_server_socket.listen(128)
def service_client(self, new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
# print("*"*50, file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 返回http格式的数据,给浏览器
# 2.1 如果请求的资源不是以.py结尾,那么就认为是静态资源(html/css/js/png,jpg等)
if not file_name.endswith(".py"):
try:
f = open("./html" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "------file not found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
else:
# 2.2 如果是以.py结尾,那么就认为是动态资源的请求
env = dict()
body = mini_frame.application(env, self.set_response_header) # 参数传入字典、函数引用,返回的作为body
header = "HTTP/1.1 %s\r\n" % self.status
for temp in self.headers:
header += "%s:%s\r\n" % (temp[0], temp[1])
header += "\r\n"
response = header+body
# 发送response给浏览器
new_socket.send(response.encode("utf-8"))
# 关闭套接
new_socket.close()
def set_response_header(self, status, headers):
# set_response_header函数引用传给application后,set_response_header把header作为实例属性保存下来
self.status = status
self.headers = [("server", "mini_web v8.8")] # 可以写上服务器的信息
self.headers += headers
def run_forever(self):
"""用来完成整体的控制"""
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 为这个客户端服务
p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
p.start()
new_socket.close()
# 关闭监听套接字
self.tcp_server_socket.close()
def main():
"""控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行"""
wsgi_server = WSGIServer()
wsgi_server.run_forever()
if __name__ == "__main__":
main()
运行web_server.py,在浏览器输入 http://127.0.0.1:7890/xxx.py ,可以看到浏览器显示: Hello World!...time is:Tue Feb 25 22:07:16 2020 .
1.4. 通过字典传参给mini_frame框架
1.3.中,实现的程序不支持中文解码,也没有用到传参时的字典。
先对 mini_frame.py 做一些更改,增加 charset=utf-8 使其支持中文、增加不同请求的不同返回。
def index():
return "这是主页"
def login():
return "这是登录页面"
def application(env, start_response):
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')]) # 添加charset=utf-8支持中文
file_name = env['PATH_INFO']
# file_name = "/index.py"
if file_name == "/index.py":
return index()
elif file_name == "/login.py":
return login()
else:
return 'Hello World! 我爱你中国....' # 返回作为body
web_server.py
通过字典传参给mini_frame框架,实现浏览器请求的资源不一样时响应不一样:将解析到的请求存到字典,传给mini_frame模块的application作参数。通过浏览器输入不同的请求可以验证。
import socket
import re
import multiprocessing
import time
import mini_frame
class WSGIServer(object):
def __init__(self):
# 1. 创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定
self.tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
self.tcp_server_socket.listen(128)
def service_client(self, new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
# print("*"*50, file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 返回http格式的数据,给浏览器
# 2.1 如果请求的资源不是以.py结尾,那么就认为是静态资源(html/css/js/png,jpg等)
if not file_name.endswith(".py"):
try:
f = open("./html" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "------file not found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
else:
# 2.2 如果是以.py结尾,那么就认为是动态资源的请求
env = dict() # 这个字典中存放的是web服务器要传递给 web框架的数据信息
env['PATH_INFO'] = file_name # 把上面解析到的 file_name存到字典
# {"PATH_INFO": "/index.py"}
body = mini_frame.application(env, self.set_response_header) # 参数传入字典、函数引用,返回的作为body
header = "HTTP/1.1 %s\r\n" % self.status
for temp in self.headers:
header += "%s:%s\r\n" % (temp[0], temp[1])
header += "\r\n"
response = header+body
# 发送response给浏览器
new_socket.send(response.encode("utf-8"))
# 关闭套接
new_socket.close()
def set_response_header(self, status, headers):
# set_response_header函数引用传给application后,set_response_header把header作为实例属性保存下来
self.status = status
self.headers = [("server", "mini_web v8.8")]
self.headers += headers
def run_forever(self):
"""用来完成整体的控制"""
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 为这个客户端服务
p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
p.start()
new_socket.close()
# 关闭监听套接字
self.tcp_server_socket.close()
def main():
"""控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行"""
wsgi_server = WSGIServer()
wsgi_server.run_forever()
if __name__ == "__main__":
main()
2. 实现模板文件
前面demo的写法不太规范,也不好区分两个py文件的作用。
可以使用一个模版来规范以下,框架和服务器应该 在根目录下 web_server.py 接收浏览器请求,在danamic文件夹实现mini_frame.py框架...用文件夹区分不同功能的文件。前后端开发也类似这样,要做到解耦合。
文件结构:
.
├── dynamic
│ ├── __init__.py
│ ├── mini_frame.py
│ └── __pycache__
│ ├── __init__.cpython-35.pyc
│ ├── __init__.cpython-36.pyc
│ ├── mini_frame.cpython-35.pyc
│ └── mini_frame.cpython-36.pyc
├── static
│ ├── css
│ │ ├── bootstrap.min.css
│ │ ├── main.css
│ │ └── swiper.min.css
│ └── js
│ ├── a.js
│ ├── bootstrap.min.js
│ ├── jquery-1.12.4.js
│ ├── jquery-1.12.4.min.js
│ ├── jquery.animate-colors.js
│ ├── jquery.animate-colors-min.js
│ ├── jquery.cookie.js
│ ├── jquery-ui.min.js
│ ├── server.js
│ ├── swiper.jquery.min.js
│ ├── swiper.min.js
│ └── zepto.min.js
├── templates
│ ├── center.html
│ └── index.html
└── web_server.py
mini_frame.py
注意:程序以为web_server.py运行,所有模块打开文件时,路径都应该以运行的文件为基准。
import re
def index():
with open("./templates/index.html",encoding="utf-8") as f:
# 注意:程序以为web_server.py运行,所有模块打开文件时,路径都应该以运行的文件为基准。所以用../不对
content = f.read()
my_stock_info = "哈哈哈哈....."
content = re.sub(r"\{%content%\}", my_stock_info, content)
return content
def center():
with open("./templates/center.html") as f:
content = f.read()
my_stock_info = "这里是从mysql查询出来的数据。。。"
content = re.sub(r"\{%content%\}", my_stock_info, content)
return content
def application(env, start_response):
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
file_name = env['PATH_INFO']
# file_name = "/index.py"
if file_name == "/index.py":
return index()
elif file_name == "/center.py":
return center()
else:
return 'Hello World! 我爱你中国....'
web_server.py
import socket
import re
import multiprocessing
import time
import dynamic.mini_frame
class WSGIServer(object):
def __init__(self):
# 1. 创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定
self.tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
self.tcp_server_socket.listen(128)
def service_client(self, new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
# print("*"*50, file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 返回http格式的数据,给浏览器
# 2.1 如果请求的资源不是以.py结尾,那么就认为是静态资源(html/css/js/png,jpg等)
if not file_name.endswith(".py"):
try:
f = open("./static" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "------file not found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response ic.mini_frame.applicationbody发送给浏览器
new_socket.send(html_content)
else:
# 2.2 如果是以.py结尾,那么就认为是动态资源的请求
env = dict() # 这个字典中存放的是web服务器要传递给 web框架的数据信息
env['PATH_INFO'] = file_name
# {"PATH_INFO": "/index.py"}
body = dynamic.mini_frame.application(env, self.set_response_header)
header = "HTTP/1.1 %s\r\n" % self.status
for temp in self.headers:
header += "%s:%s\r\n" % (temp[0], temp[1])
header += "\r\n"
response = header+body
# 发送response给浏览器
new_socket.send(response.encode("utf-8"))
# 关闭套接
new_socket.close()
def set_response_header(self, status, headers):
self.status = status
self.headers = [("server", "mini_web v8.8")]
self.headers += headers
def run_forever(self):
"""用来完成整体的控制"""
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 为这个客户端服务
p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
p.start()
new_socket.close()
# 关闭监听套接字
self.tcp_server_socket.close()
def main():
"""控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行"""
wsgi_server = WSGIServer()
wsgi_server.run_forever()
if __name__ == "__main__":
main()
3. 给服务器程序传参数(实现指定端口和模块)
观察前面的demo,思考:
- demo端口不能更改,怎样可以更换端口?
- 怎么在不修改服务器的前提下可以支持其他的框架?指定模块
实现运行时 指定端口、指定框架及其函数(如:python3 xxxx.py 7890 mini_frame:application)
在上一个demo的基础上,修改web_server.py:
- 导入 sys;
- 在main函数指定 sys.argv 长度、即参数个数 (用于给程序传递端口和模块参数);
- main函数中 用正则分组提取出框架的模块名和方法名;
- 注意:即使 frame_name 是一个变量名,但是 import frame_name ,找到的是frame_name.py,而不是frame_name 指向的变量所指的模块。应该使用 __import__(frame_name) 来导入(返回值会标记着导入的模块) ,还应注意提前用 sys.path.append 把模块路径添加。
- getattr(frame, app_name) 会到 frame 模块找 app_name 方法,并返回。
(此demo关注点在main函数)
import socket
import re
import multiprocessing
import time
# import dynamic.mini_frame
import sys
class WSGIServer(object):
def __init__(self, port, app):
# 1. 创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定
self.tcp_server_socket.bind(("", port))
# 3. 变为监听套接字
self.tcp_server_socket.listen(128)
self.application = app
def service_client(self, new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
# print("*"*50, file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 返回http格式的数据,给浏览器
# 2.1 如果请求的资源不是以.py结尾,那么就认为是静态资源(html/css/js/png,jpg等)
if not file_name.endswith(".py"):
try:
f = open("./static" + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "------file not found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response ic.mini_frame.applicationbody发送给浏览器
new_socket.send(html_content)
else:
# 2.2 如果是以.py结尾,那么就认为是动态资源的请求
env = dict() # 这个字典中存放的是web服务器要传递给 web框架的数据信息
env['PATH_INFO'] = file_name
# {"PATH_INFO": "/index.py"}
# body = dynamic.mini_frame.application(env, self.set_response_header)
body = self.application(env, self.set_response_header)
header = "HTTP/1.1 %s\r\n" % self.status
for temp in self.headers:
header += "%s:%s\r\n" % (temp[0], temp[1])
header += "\r\n"
response = header+body
# 发送response给浏览器
new_socket.send(response.encode("utf-8"))
# 关闭套接
new_socket.close()
def set_response_header(self, status, headers):
self.status = status
self.headers = [("server", "mini_web v8.8")]
self.headers += headers
def run_forever(self):
"""用来完成整体的控制"""
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 为这个客户端服务
p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
p.start()
new_socket.close()
# 关闭监听套接字
self.tcp_server_socket.close()
def main():
"""控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行"""
if len(sys.argv) == 3: # 控制程序运行的参数
try:
port = int(sys.argv[1]) # 7890
frame_app_name = sys.argv[2] # mini_frame:application
except Exception as ret:
print("端口输入错误。。。。。")
return
else:
print("请按照以下方式运行:")
print("python3 xxxx.py 7890 mini_frame:application")
return
# mini_frame:application(frame_app_name中的内容)
ret = re.match(r"([^:]+):(.*)", frame_app_name) # 用正则分组提取出框架的模块名和方法名
if ret:
frame_name = ret.group(1) # mini_frame
app_name = ret.group(2) # application
else:
print("请按照以下方式运行:")
print("python3 xxxx.py 7890 mini_frame:application")
return
sys.path.append("./dynamic") # 把mini_frame的路径加进去,导入时才能找到
# import frame_name --->找到的是frame_name.py
frame = __import__(frame_name) # 返回值标记着导入的这个模块
app = getattr(frame, app_name) # 此时app就指向了 dynamic/mini_frame模块中的application这个函数
# print(app)
wsgi_server = WSGIServer(port, app)
wsgi_server.run_forever()
if __name__ == "__main__":
main()
以上demo实现了 给服务器程序传参数,即实现指定端口和模块。但解耦合还不够彻底,如 f = open("./static" + file_name, "rb") ,文件必须到static文件夹查找,类似的还有 sys.path.append("./dynamic") 只能文件夹 dynamic 找框架的模块。可以通过 配置文件 来解决此问题,让服务器程序加载配置文件。
4.让web服务器支持配置文件
通过上一个demo还存在的问题,可以让web服务器支持配置文件,通过配置文件来配置 框架的路径 和 静态文件 的路径。
文件结构:
.
├── dynamic
│ ├── __init__.py
│ ├── mini_frame.py
│ └── __pycache__
│ ├── __init__.cpython-35.pyc
│ └── mini_frame.cpython-35.pyc
├── readme.txt
├── run.sh
├── static
│ ├── css
│ │ ├── bootstrap.min.css
│ │ ├── main.css
│ │ └── swiper.min.css
│ └── js
│ ├── a.js
│ ├── bootstrap.min.js
│ ├── jquery-1.12.4.js
│ ├── jquery-1.12.4.min.js
│ ├── jquery.animate-colors.js
│ ├── jquery.animate-colors-min.js
│ ├── jquery.cookie.js
│ ├── jquery-ui.min.js
│ ├── server.js
│ ├── swiper.jquery.min.js
│ ├── swiper.min.js
│ └── zepto.min.js
├── templates
│ ├── center.html
│ └── index.html
├── test.py
├── web_server.conf
└── web_server.py
配置文件web_server.conf,类似字典格式的字符串:
{
"static_path":"./static",
"dynamic_path":"./dynamic"
}
服务器 web_server.py 文件:
- 打开.conf文件,通过eval函数将配置文件的内容转成字典返回( 注:eval() 函数用来执行一个字符串表达式,并返回表达式的值。);
- 再读取字典里的值来用。
这样,服务器程序就不会再有和框架相关的代码了。
import socket
import re
import multiprocessing
import time
# import dynamic.mini_frame
import sys
class WSGIServer(object):
def __init__(self, port, app, static_path):
# 1. 创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定
self.tcp_server_socket.bind(("", port))
# 3. 变为监听套接字
self.tcp_server_socket.listen(128)
self.application = app
self.static_path = static_path
def service_client(self, new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
# print("*"*50, file_name)
if file_name == "/":
file_name = "/index.html"
# 2. 返回http格式的数据,给浏览器
# 2.1 如果请求的资源不是以.py结尾,那么就认为是静态资源(html/css/js/png,jpg等)
if not file_name.endswith(".py"):
try:
f = open(self.static_path + file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "------file not found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response ic.mini_frame.applicationbody发送给浏览器
new_socket.send(html_content)
else:
# 2.2 如果是以.py结尾,那么就认为是动态资源的请求
env = dict() # 这个字典中存放的是web服务器要传递给 web框架的数据信息
env['PATH_INFO'] = file_name
# {"PATH_INFO": "/index.py"}
# body = dynamic.mini_frame.application(env, self.set_response_header)
body = self.application(env, self.set_response_header)
header = "HTTP/1.1 %s\r\n" % self.status
for temp in self.headers:
header += "%s:%s\r\n" % (temp[0], temp[1])
header += "\r\n"
response = header+body
# 发送response给浏览器
new_socket.send(response.encode("utf-8"))
# 关闭套接
new_socket.close()
def set_response_header(self, status, headers):
self.status = status
self.headers = [("server", "mini_web v8.8")]
self.headers += headers
def run_forever(self):
"""用来完成整体的控制"""
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 为这个客户端服务
p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
p.start()
new_socket.close()
# 关闭监听套接字
self.tcp_server_socket.close()
def main():
"""控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行"""
if len(sys.argv) == 3:
try:
port = int(sys.argv[1]) # 7890
frame_app_name = sys.argv[2] # mini_frame:application
except Exception as ret:
print("端口输入错误。。。。。")
return
else:
print("请按照以下方式运行:")
print("python3 xxxx.py 7890 mini_frame:application")
return
# mini_frame:application
ret = re.match(r"([^:]+):(.*)", frame_app_name)
if ret:
frame_name = ret.group(1) # mini_frame
app_name = ret.group(2) # application
else:
print("请按照以下方式运行:")
print("python3 xxxx.py 7890 mini_frame:application")
return
with open("./web_server.conf") as f:
conf_info = eval(f.read()) # eval函数将配置文件的内容转成字典返回
# 此时 conf_info是一个字典里面的数据为:
# {
# "static_path":"./static",
# "dynamic_path":"./dynamic"
# }
sys.path.append(conf_info['dynamic_path']) # 读取字典的值添加到路径
# import frame_name --->找frame_name.py
frame = __import__(frame_name) # 返回值标记这 导入的这个模板
app = getattr(frame, app_name) # 此时app就指向了 dynamic/mini_frame模块中的application这个函数
# print(app)
wsgi_server = WSGIServer(port, app, conf_info['static_path'])
wsgi_server.run_forever()
if __name__ == "__main__":
main()
运行的格式 :python3 xxxx.py 7890 mini_frame:application
可见,运行时输入的指令比较长,不方便。
可以将linux 命令(shell脚本)放到文件里,只要执行该文件,文件里的linux命令即可执行。
运行的shell脚本 run.sh :
python3 web_server.py 7890 mini_frame:application
注意,给run.sh 文件加上执行权限:chmod +x run.sh
写一个 readme.txt 文档说明,说明是如何使用的:
-----copyright版权......
运行方式:./run.sh
或者 python3 xxx.py 7890 mini_frame:application
author:laowang
date:xxx
versioin:1.0
总结:
这样,对web_server服务器进行配置时,就不用再修改其程序,通过修改配置文件即可。服务器和框架之间的耦合度也比较低,修改时相互之间不必担心互相影响。
------end------------