在Python实现web服务器入门学习笔记(6)——多进程实现并发HTTP服务器的面向对象封装中,我们已经实现了并发HTTP服务器,并且将代码进行了面向对象封装。
实际上,在真实的web开发中,为了提高开发效率、复用已有成果、降低系统耦合度,实际运行在服务器上的程序可以大致解耦为web服务器、web框架。
常用的web服务器有Apache、Nginx等,常用的Python后端web框架有Django、Flask、Tornado等。
在Python的web开发中,一般web服务器和web框架之间为了实现互通,二者还会遵循web服务器网关接口(WSGI:Web Server Gateway Interface)
web服务器的功能一般为:
web框架的功能一般为:
总体来说:
进行web服务器和web框架的功能划分是为了实现功能解耦,即:
- 对于Apache、Nginx这类web服务器来说,其主要需解决的问题是高并发:和数以千万计的用户同时保持连接;能够在一定时间内传送大量数据(吞吐量);以HTTP协议接收请求并返回应答;
- 对于Django、Flask这类web框架来说,其主要需解决的问题是“重复造轮子”的困境:在web后端开发中,很多业务逻辑是共通的,如:用户管理、会话保持等。
现今,虽然开发者使用Python语言进行web后端开发时,可以根据需要搭配使用web服务器和web框架,以使得二者的组合可以最大限度满足自己的需求,但是事实并非一直如此。
因为web服务器和web框架的开发者并不是同一批人,二者内部的功能封装并不遵循相同的接口,故早先时候,开发者对web服务器的选择会影响其对可用web框架的选择,反之亦然。
鉴于上述原因,在PEP(Python Enhancement Proposal) 333中,Python社区提出了WSGI,这是一套简单、通用的接口,用于web服务器和web框架之间,使得web服务器和web框架的开发者专注于各自的领域,也能降低web后端的开发者进行web服务器和web框架之间适配的工作。
简单来说:
WSGI是一套用于web服务器和Python web框架之间的协议,用于规定二者之间交换数据的要求。
下图是遵循WSGI协议的web服务器和web框架配合完成浏览器一次动态资源请求的时序图。
下面是自定义遵循WSGI协议的web服务器和web框架:
import socket
import re
import multiprocessing
import mini_web_frame
class WSGIServer(object):
def __init__(self):
# 1.创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 通过设定套接字选项解决[Errno 98]错误
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2.绑定端口
self.tcp_server_socket.bind(("", 7899))
# 3.变为监听套接字
self.tcp_server_socket.listen(128)
# 定义两个实例属性,用于存储response_header信息
self.status = None
self.headers = None
def set_response_header(self, status, headers):
self.status = status
# 在服务器程序中指定web服务器的信息
self.headers = [("Server", "user-defined web server v1.0")]
self.headers += headers
def serve_client(self, new_client_socket):
"""为这个客户端返回数据"""
# 6.接收浏览器发送过来的http请求
request = new_client_socket.recv(1024).decode("utf-8")
# 7.将请求报文分割成字符串列表
request_lines = request.splitlines()
print(request_lines)
# 8.通过正则表达式提取浏览器请求的文件名
file_name = None
ret = re.match(r"^[^/]+(/[^ ]*)", request_lines[0])
if ret:
file_name = ret.group(1)
print("file_name:", file_name)
if file_name == "/":
file_name = "/index.html"
# 9.返回http格式的应答数据给浏览器
# 9.1 如果请求的资源不是以.py结尾,那么就认为浏览器请求的是静态资源(HTML、CSS、JPG、PNG等)
if not file_name.endswith(".py"):
# 静态资源处理逻辑
pass
else:
# 9.2 如果浏览器请求的资源是以.py结尾,则表示浏览器请求的是动态资源
env = dict()
env["PATH_INFO"] = file_name
body = mini_web_frame.application(env, self.set_response_header)
header = "HTTP/1.1 %s\r\n" % self.status
for each in self.headers:
header += "%s:%s\r\n" % (each[0], each[1])
header += "\r\n"
response = header + body
new_client_socket.send(response.encode("utf-8"))
# 10. 关闭此次服务的套接字
new_client_socket.close()
def run_forever(self):
"""用来完成程序整体控制"""
while True:
# 4.等待新客户端连接
new_client_socket, client_addr = self.tcp_server_socket.accept()
# 5.为连接上的客户端服务
process = multiprocessing.Process(target=self.serve_client, args=(new_client_socket,))
process.start()
new_client_socket.close()
# 关闭监听套接字
self.tcp_server_socket.close()
def main():
"""
控制整体,创建一个web服务器对象,然后调用这个对象的run_forever()方法运行
:return:
"""
wsgi_server = WSGIServer()
wsgi_server.run_forever()
if __name__ == "__main__":
main()
import time
def index():
return "这是主页"
def login():
return "这是登录页面"
def application(environ, start_response):
start_response("200 OK", [("Content-Type", "text/html;charset=utf-8")])
file_name = environ['PATH_INFO']
print("file_name = ", file_name)
if file_name == "/index.py":
return index()
elif file_name == "/login.py":
return login()
else:
return "Hello World, 你好,世界: %s" % time.ctime()
上述代码中,假设动态资源是以.py结尾的,启动服务器程序后,假设在浏览器的地址栏输入:127.0.0.1:7899/index.py(服务器程序和浏览器处于同一台物理服务器上),则浏览器成功连接上服务器后: