本篇主要介绍WSGI-web-mini框架的基本实现,以及什么是WSGI协议、对于浏览器的资源请求服务端是如何处理的等相关知识;
一、浏览器资源请求
我们知道浏览器和web服务器之间是基于HTTP协议进行文件的传输的,而根据请求消息的不同,服务器处理的流程也是不同的;如下图:
通常而言:
对于浏览器请求的静态资源,服务器在收到请求消息时(处理完后),会直接在服务器的本地磁盘中将信息(index.html页面)读取出来,直接发送给web浏览器;
而对于动态资源的请求,服务器通常会调用web框架来处理,而web框架会根据用户的请求不同,从数据库中读取不同的信息将其替换自HTML模板中,并且返回给HTTP服务器,最后由服务器将消息发送给web浏览器;
二、WSGI协议
2.1 为什么要使用WSGI协议呢?
思考:在以前,选择 Python-web架构 会受制于可用的 web服务器,反之亦然。为了让web架构和服务器可以协同工作,但有可能面对(或者曾有过)下面的问题,当要把一个服务器和一个架构结合起来时,却发现他们不是被设计成协同工作的;
那么,怎么可以不修改服务器和架构代码而确保可以在多个架构下运行web服务器呢?
答案就是 Python Web Server Gateway Interface (或简称 WSGI,读作“wizgy”)。
WSGI允许开发者将选择web框架和web服务器分开。可以混合匹配web服务器和web框架,选择一个适合的配对。比如,可以在Gunicorn 或者 Nginx/uWSGI 或者 Waitress上运行 Django, Flask, 或 Pyramid。真正的混合匹配,得益于WSGI同时支持服务器和架构;
2.2 WSGI接口
而通常web服务器必须具备WSGI接口,所有的现代Python Web框架都已具备WSGI接口,它让你不对代码作修改就能使服务器和特点的web框架协同工作。
WSGI由web服务器支持,而web框架允许你选择适合自己的配对,但它同样对于服务器和框架开发者提供便利使他们可以专注于自己偏爱的领域和专长而不至于相互牵制。其他语言也有类似接口:java有Servlet API,Ruby 有 Rack。
我们需在web框架内定义一个WSGI接口函数,而web服务器与框架进行协同工作时,只需通过该接口函数即可,其他的实现均封装在该框架的底部;该接口函数如下:
# WSGI接口函数
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return 'Hello World!'
参数:
environ :一个包含所有HTTP请求信息的dict对象;即告诉框架需要调取哪些动态资源页面;
start_response :一个发送HTTP响应的函数。该函数相当于HTTP服务器给与框架的一个容器,而框架将头部信息通过参数的方式存放在该函数内,最后服务器再从中取出;
返回值:
返回的是请求的动态资源的响应消息的body部分;通常web框架将动态资源替换HTML模板的消息返回给服务器;
三、实例:
其大致结构如下:
web服务器:
import socket import multiprocessing import re # import dynamic.mini_frame import sys class WSGIServer(object): def __init__(self,port,app,static_path): # 1.创建监听套接字 self.server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 2.为了服务端先断开时,客户端马上能链接 self.server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 3.绑定本地端口 self.server_socket.bind(("",port)) # 4.监听 self.server_socket.listen(128) self.port = port self.application = app self.static_path = static_path def deal_with_static(self,new_socket): # 如果不是以.py结尾的文件,当成静态页面处理 try: f=open("static"+ self.file_name,"rb") except: header = "HTTP/1.1 404 NOT FOUND FILE\r\n" header += "\r\n" body = "not found file <\h1>
" response = header+body new_socket.send(response.encode("utf-8")) else: header="HTTP/1.1 200 OK\r\n" header += "\r\n" body = f.read() response = header.encode("utf-8") + body new_socket.send(response) def deal_with_dynamic(self,new_socket): """处理动态页面""" env = dict() env["PATH_INFO"] = self.file_name # web框架的接口函数 body = self.application(env,self.set_response_header) # headr部分的拼接成指定格式 header= "HTTP/1.1 %s"%self.status for temp in self.header: header += "%s:%s\r\n" % (temp[0], temp[1]) header+="\r\n" print(header) new_socket.send(header.encode("utf-8")) new_socket.send(body.encode("utf-8")) def set_response_header(self,status,header): """作为接口函数的参数,相当于容器接收web框架返回的头部信息""" self.status = status; self.header = header; def serve_client(self,new_socket): """接收请求消息和发送相应消息""" # 1.接受请求消息 GET / HTTP/1.1 request = new_socket.recv(1024).decode("utf-8") # 2.解析请求消息 request_lines = request.splitlines() ret = re.match(r"[^/]+(/[^ ]*)",request_lines[0]) if ret: self.file_name = ret.group(1) if self.file_name =="/": self.file_name = "/index.html" # print(file_name) # 3.尝试打开文件,若文件存在则能打开,否则发送404 if not self.file_name.endswith(".py"): # 处理静态网页 self.deal_with_static(new_socket) # new_socket.close() else: # 处理动态网页 self.deal_with_dynamic(new_socket) new_socket.close() def run_forever(self): """当成主函数""" while True: new_socket,client_addr = self.server_socket.accept() p = multiprocessing.Process(target=self.serve_client,args=(new_socket,)) p.start() p.join() new_socket.close() self.server_socket.close() def main(): # 运行服务器时,给出指定的 端口和运行的框架 if len(sys.argv)==3: try: port=int(sys.argv[1]) # 6968 frame_app_name = sys.argv[2] # mini_frame:application except Exception as e: print("输入方式有误,请按照以下方式运行:") print("python3 web_server.py 6968 mini_frame:application") return ret = re.match(r"([^:]+):(.*)",frame_app_name) # 获取框架文件名 if ret: frame_name = ret.group(1) app_name = ret.group(2) else: print("您的输入方式有误,请按照上述方式运行") return with open("./web_server.conf","r") as f: conf_info = eval(f.read()) # 将字符串转为字典类型 # 将导入动态数据的模块添加到python解释器的环境变量中 sys.path.append(conf_info["dynamic_path"]) print(sys.path) frame =__import__(frame_name) # 导入指定的模块 app = getattr(frame,app_name) # 反射出该模块下的指定的函数app_name wsgi_server = WSGIServer(port,app,conf_info["static_path"]) wsgi_server.run_forever() if __name__ == '__main__': main()
解析:
1、在web服务器中,我们首先根据资源的请求不同,给出的处理方式不同即静态资源直接从本地磁盘读取,而动态资源交给web框架处理。
2、在与web框架进行交互,调用了wsgi接口函数,传入了字典env,用来告诉服务器需求页面;以及函数set_response_header用来获取web框架传递的头部信息;并且将返回值作为body;整个与web框架的交互均是围绕着wsgi接口函数来实现的;
3、随后便是让我们的服务器运行可以指定端口和web框架来运行,则定义了main函数下面的一些代码,通过sys.argv来获取到指定的端口和web_frame:application等,再将这些作为参数传入类 WSGIServer中来执行;
mini-web框架
import re def index(): """显示index.py动态数据""" with open("./templates/index.html","r") as f: content=f.read() # print(content) my_stock_info = "这是是数据库的数据,未完待续。" content = re.sub(r"\{%content%\}", my_stock_info, content) return content def center(): """显示center动态数据""" with open("./templates/center.html","r") as f: content = f.read() # print(content) mys_stock_info = "这是数据库的数据,未完待续。" content = re.sub(r"\{%content%\} ",mys_stock_info,content) return content def application(environ,start_response): start_response("200 OK\r\n",[('Content-Type', 'text/html;charset=utf8')]) file_name=environ["PATH_INFO"] if file_name == "/index.py": return index() elif file_name == "/center.py": return center() else: start_response("400 NOT FOUND FILE\r\n",[('Content-Type', 'text/html;charset=utf8')]) return "Sorry,the file you want get have not found;"
在该框架内:定义了wsgi接口函数,以及获取指定页面的函数,在下篇中我们将会介绍如何将数据库的内容导入;
web_frame.conf 配置文件
{ "static_path":"./static", "dynamic_path":"./dynamic" }
配置文件主要用来指定:动态资源及静态资源存储位置,当请求动态资源时,将这些路径添加至python解释器的环境变量中去,方便web框架模块的导入;
run.sh脚本运行文件
python3 web_server.py 6868 mini_frame:application
定义脚本运行文件,主要是方便运行,不至于每次写一大坨东西去执行;
over~~~ 下篇中会实现路由的功能,并且替换成数据库中的数据~~~