为什么会有web框架
有了上一篇内容,静态、动态web服务器的实现,已经掌握了客户端请求到服务器处理的机制。在动态资源处理中,根据请求 .py 导入模块应用,然后调用应用入口程序实现动态处理。但是在真实的项目中,肯定有很多应用(.py文件),按照这种处理机制(每个应用中都要写一个入口,设置状态码、headers)不符合架构设计原则,这一部分肯定是可以复用的,所用应用应该公用一个入口,这就引入一个概念web框架。
设计思路
- 把每个应用中入口 application(env,start_response) 提取到web服务器中,然后在入口中通过路由机制分发到各应用中处理。
代码如下:
- WebFramework.py
`
# coding:utf-8
import time
from MyWebServer import WebServer
HTML_ROOT_DIR = "./static"
class Application(object):
'''自定义通用的web框架'''
# 初始化路由信息
def __init__(self,urls):
self.urls = urls
# 匹配路由
def __call__(self, env, start_response):
path = env.get("PATH_INFO", "/")
# /static/index.html
if path.startswith("/static"):
file_name = path[7:]
# 打开文件,读取内容
try:
file = open(HTML_ROOT_DIR + file_name, "rb")
except IOError:
# 代表未找到路由信息,404错误
status = "404 Not Found"
headers = []
start_response(status, headers)
return "not found"
else:
file_data = file.read()
file.close()
status = "200 OK"
headers = []
start_response(status, headers)
return file_data.decode("utf-8")
for url,handler in self.urls:
if path == url:
return handler(env,start_response)
# 未匹配到
status = '404 Not Found'
headers = []
start_response(status,headers)
return 'not found'
def showtime(env,start_response):
status = '200 OK'
headers = [
('Content-Type', 'text/plain')
]
start_response(status, headers)
return str(time.time())
def sayhello(env,start_response):
status = '200 OK'
headers = [
('Content-Type','text/plain')
]
start_response(status,headers)
return 'say hello'
def helloworld(env,start_response):
status = '200 OK'
headers =[
('Content-Type','text/plain')
]
start_response(status,headers)
return 'hello world'
if __name__ == '__main__':
urls = [
('/', showtime),
('/sayhello',sayhello),
('/helloworld',helloworld)
]
app = Application(urls)
webServer = WebServer(app)
webServer.bind(8000)
webServer.start()
`
- MyWebServer.py
`
# coding:utf-8
import socket
import re
import sys
from multiprocessing import Process
HTML_ROOT_DIR = './static'
WSGI_PY = './wsgipy'
class WebServer(object):
'''
简单的webserver
'''
def __init__(self,application):
'''application:框架'''
self.sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.app = application
def start(self):
self.sock_server.listen(128)
while True:
sock_client, addr = self.sock_server.accept()
print('[%s,%s]用户已连接......' % addr)
handle_client_process = Process(target=self.handle_client, args=(sock_client,))
handle_client_process.start()
sock_client.close()
def start_response(self, status, headers):
"""
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
star
"""
resp_headers = 'HTTP/1.1 ' + status + '
'
for header in headers:
resp_headers += '%s: %s
' % header
self.resp_headers = resp_headers
def handle_client(self, sock_client):
'''处理客户端请求'''
recv_data = sock_client.recv(1024)
#print('请求数据:', recv_data)
req_lines = recv_data.splitlines()
#for line in req_lines:
# print(line)
req_start_line = req_lines[0]
#print(req_start_line.decode('utf-8'))
file_name = re.match(r"w+ +(/[^ ]*) ", req_start_line.decode("utf-8")).group(1)
method = re.match(r"(w+) +/[^ ]* ", req_start_line.decode("utf-8")).group(1)
env = {
"PATH_INFO": file_name,
"METHOD": method
}
response_body = self.app(env, self.start_response)
response = self.resp_headers + "
" + response_body
# 向客户端返回响应数据
sock_client.send(bytes(response, "utf-8"))
# 关闭客户端连接
sock_client.close()
def bind(self, port):
self.sock_server.bind(('', port))
def main():
sys.path.insert(1,WSGI_PY)
webServer = WebServer()
webServer.bind(8000)
webServer.start()
if __name__ == '__main__':
main()
`
启动
入口实在WebFramework.py 中,直接启动该文件,客户端请求指定路径即可看到效果。但是对于大多框架都是通过命令行启动,我们不可能在程序中这样启动,而且对于webserver来说始终是不变得,变的是web框架,所以改造成命令行启动,代码如下:
- WebFramework.py
`
# coding:utf-8
import time
HTML_ROOT_DIR = "./static"
class Application(object):
'''自定义通用的web框架'''
# 初始化路由信息
def __init__(self,urls):
self.urls = urls
# 匹配路由
def __call__(self, env, start_response):
path = env.get("PATH_INFO", "/")
# /static/index.html
if path.startswith("/static"):
file_name = path[7:]
# 打开文件,读取内容
try:
file = open(HTML_ROOT_DIR + file_name, "rb")
except IOError:
# 代表未找到路由信息,404错误
status = "404 Not Found"
headers = []
start_response(status, headers)
return "not found"
else:
file_data = file.read()
file.close()
status = "200 OK"
headers = []
start_response(status, headers)
return file_data.decode("utf-8")
for url,handler in self.urls:
if path == url:
return handler(env,start_response)
# 未匹配到
status = '404 Not Found'
headers = []
start_response(status,headers)
return 'not found'
def showtime(env,start_response):
status = '200 OK'
headers = [
('Content-Type', 'text/plain')
]
start_response(status, headers)
return str(time.time())
def sayhello(env,start_response):
status = '200 OK'
headers = [
('Content-Type','text/plain')
]
start_response(status,headers)
return 'say hello'
def helloworld(env,start_response):
status = '200 OK'
headers =[
('Content-Type','text/plain')
]
start_response(status,headers)
return 'hello world'
urls = [
('/', showtime),
('/sayhello',sayhello),
('/helloworld',helloworld)
]
app = Application(urls)
# if __name__ == '__main__':
# urls = [
# ('/', showtime),
# ('/sayhello',sayhello),
# ('/helloworld',helloworld)
# ]
# app = Application(urls)
#
# webServer = WebServer(app)
# webServer.bind(8000)
# webServer.start()
`
- MyWebServer.py
`
# coding:utf-8
import socket
import re
import sys
from multiprocessing import Process
HTML_ROOT_DIR = './static'
WSGI_PY = './wsgipy'
class WebServer(object):
'''
简单的webserver
'''
def __init__(self,application):
'''application:框架'''
self.sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.app = application
def start(self):
self.sock_server.listen(128)
while True:
sock_client, addr = self.sock_server.accept()
print('[%s,%s]用户已连接......' % addr)
handle_client_process = Process(target=self.handle_client, args=(sock_client,))
handle_client_process.start()
sock_client.close()
def start_response(self, status, headers):
"""
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
star
"""
resp_headers = 'HTTP/1.1 ' + status + '
'
for header in headers:
resp_headers += '%s: %s
' % header
self.resp_headers = resp_headers
def handle_client(self, sock_client):
'''处理客户端请求'''
recv_data = sock_client.recv(1024)
#print('请求数据:', recv_data)
req_lines = recv_data.splitlines()
#for line in req_lines:
# print(line)
req_start_line = req_lines[0]
#print(req_start_line.decode('utf-8'))
file_name = re.match(r"w+ +(/[^ ]*) ", req_start_line.decode("utf-8")).group(1)
method = re.match(r"(w+) +/[^ ]* ", req_start_line.decode("utf-8")).group(1)
env = {
"PATH_INFO": file_name,
"METHOD": method
}
response_body = self.app(env, self.start_response)
response = self.resp_headers + "
" + response_body
# 向客户端返回响应数据
sock_client.send(bytes(response, "utf-8"))
# 关闭客户端连接
sock_client.close()
def bind(self, port):
self.sock_server.bind(('', port))
def main():
sys.path.insert(1,WSGI_PY)
if len(sys.argv) < 2:
sys.exit("python MyWebServer.py Module:app")
module_name, app_name = sys.argv[1].split(':')
m = __import__(module_name)
app = getattr(m,app_name)
webServer = WebServer(app)
webServer.bind(8000)
webServer.start()
if __name__ == '__main__':
main()
`
调用方式
在命令行中,执行如下命令即可启动webserver服务,模块名称:应用框架名, 其中这里的app指的是WebFramework.py中Application类的实例,其实也就是应用框架的实例。后续自定义框架的时候,直接调用即可。
`
python MyWebServer.py WebFramework:app
`
代码已上传到github:python 小程序