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_server.py
解析:
1、在web服务器中,我们首先根据资源的请求不同,给出的处理方式不同即静态资源直接从本地磁盘读取,而动态资源交给web框架处理。
2、在与web框架进行交互,调用了wsgi接口函数,传入了字典env,用来告诉服务器需求页面;以及函数set_response_header用来获取web框架传递的头部信息;并且将返回值作为body;整个与web框架的交互均是围绕着wsgi接口函数来实现的;
3、随后便是让我们的服务器运行可以指定端口和web框架来运行,则定义了main函数下面的一些代码,通过sys.argv来获取到指定的端口和web_frame:application等,再将这些作为参数传入类 WSGIServer中来执行;
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 如果请求的资源不是以.html结尾,那么就认为是静态资源(css/js/png,jpg等)
if not file_name.endswith(".html"):
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())
# 此时 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()
dynamic/mini_frame.py
在该框架内:定义了wsgi接口函数,以及获取指定页面的函数,在下篇中我们将会介绍如何将数据库的内容导入;
import re
import urllib.parse
import logging
from pymysql import connect
"""
URL_FUNC_DICT = {
"/index.html": index,
"/center.html": center
}
"""
URL_FUNC_DICT = dict()
def route(url):
def set_func(func):
# URL_FUNC_DICT["/index.py"] = index
URL_FUNC_DICT[url] = func
def call_func(*args, **kwargs):
return func(*args, **kwargs)
return call_func
return set_func
@route(r"/index.html")
def index(ret):
with open("./templates/index.html") as f:
content = f.read()
# my_stock_info = "哈哈哈哈 这是你的本月名称....."
# content = re.sub(r"\{%content%\}", my_stock_info, content)
# 创建Connection连接
conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
# 获得Cursor对象
cs = conn.cursor()
cs.execute("select * from info;")
stock_infos = cs.fetchall()
cs.close()
conn.close()
tr_template = """<tr>
<td>%s</td><td>%s</td> <td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>
<input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="%s">
</td>
</tr>
"""
html = ""
for line_info in stock_infos:
html += tr_template % (line_info[0],line_info[1],line_info[2],line_info[3],line_info[4],line_info[5],line_info[6],line_info[7], line_info[1])
# content = re.sub(r"\{%content%\}", str(stock_infos), content)
content = re.sub(r"\{%content%\}", html, content)
return content
@route(r"/center.html")
def center(ret):
with open("./templates/center.html") as f:
content = f.read()
# my_stock_info = "这里是从mysql查询出来的数据。。。"
# content = re.sub(r"\{%content%\}", my_stock_info, content)
# 创建Connection连接
conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
# 获得Cursor对象
cs = conn.cursor()
cs.execute("select i.code,i.short,i.chg,i.turnover,i.price,i.highs,f.note_info from info as i inner join focus as f on i.id=f.info_id;")
stock_infos = cs.fetchall()
cs.close()
conn.close()
tr_template = """
<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td>
<td>
<a type="button" class="btn btn-default btn-xs" href="/update/%s.html"> <span class="glyphicon glyphicon-star" aria-hidden="true"></span> 修改 </a>
</td>
<td>
<input type="button" value="删除" id="toDel" name="toDel" systemidvaule="%s">
</td>
</tr>
"""
html = ""
for line_info in stock_infos:
html += tr_template % (line_info[0],line_info[1],line_info[2],line_info[3],line_info[4],line_info[5],line_info[6], line_info[0], line_info[0])
# content = re.sub(r"\{%content%\}", str(stock_infos), content)
content = re.sub(r"\{%content%\}", html, content)
return content
# 给路由添加正则表达式的原因:在实际开发时,url中往往会带有很多参数,例如/add/000007.html中000007就是参数,
# 如果没有正则的话,那么就需要编写N次@route来进行添加 url对应的函数 到字典中,此时字典中的键值对有N个,浪费空间
# 而采用了正则的话,那么只要编写1次@route就可以完成多个 url例如/add/00007.html /add/000036.html等对应同一个函数,此时字典中的键值对个数会少很多
@route(r"/add/(\d+)\.html")
def add_focus(ret):
# 1. 获取股票代码
stock_code = ret.group(1)
# 2. 判断试下是否有这个股票代码
conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
cs = conn.cursor()
sql = """select * from info where code=%s;"""
cs.execute(sql, (stock_code,))
# 如果要是没有这个股票代码,那么就认为是非法的请求
if not cs.fetchone():
cs.close()
conn.close()
return "没有这支股票,大哥 ,我们是创业公司,请手下留情..."
# 3. 判断以下是否已经关注过
sql = """ select * from info as i inner join focus as f on i.id=f.info_id where i.code=%s;"""
cs.execute(sql, (stock_code,))
# 如果查出来了,那么表示已经关注过
if cs.fetchone():
cs.close()
conn.close()
return "已经关注过了,请勿重复关注..."
# 4. 添加关注
sql = """insert into focus (info_id) select id from info where code=%s;"""
cs.execute(sql, (stock_code,))
conn.commit()
cs.close()
conn.close()
return "关注成功...."
@route(r"/del/(\d+)\.html")
def del_focus(ret):
# 1. 获取股票代码
stock_code = ret.group(1)
# 2. 判断试下是否有这个股票代码
conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
cs = conn.cursor()
sql = """select * from info where code=%s;"""
cs.execute(sql, (stock_code,))
# 如果要是没有这个股票代码,那么就认为是非法的请求
if not cs.fetchone():
cs.close()
conn.close()
return "没有这支股票,大哥 ,我们是创业公司,请手下留情..."
# 3. 判断以下是否已经关注过
sql = """ select * from info as i inner join focus as f on i.id=f.info_id where i.code=%s;"""
cs.execute(sql, (stock_code,))
# 如果没有关注过,那么表示非法的请求
if not cs.fetchone():
cs.close()
conn.close()
return "%s 之前未关注,请勿取消关注..." % stock_code
# 4. 取消关注
# sql = """insert into focus (info_id) select id from info where code=%s;"""
sql = """delete from focus where info_id = (select id from info where code=%s);"""
cs.execute(sql, (stock_code,))
conn.commit()
cs.close()
conn.close()
return "取消关注成功...."
@route(r"/update/(\d+)\.html")
def show_update_page(ret):
"""显示修改的那个页面"""
# 1. 获取股票代码
stock_code = ret.group(1)
# 2. 打开模板
with open("./templates/update.html") as f:
content = f.read()
# 3. 根据股票代码查询相关的备注信息
conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
cs = conn.cursor()
sql = """select f.note_info from focus as f inner join info as i on i.id=f.info_id where i.code=%s;"""
cs.execute(sql, (stock_code,))
stock_infos = cs.fetchone()
note_info = stock_infos[0] # 获取这个股票对应的备注信息
cs.close()
conn.close()
content = re.sub(r"\{%note_info%\}", note_info, content)
content = re.sub(r"\{%code%\}", stock_code, content)
return content
@route(r"/update/(\d+)/(.*)\.html")
def save_update_page(ret):
""""保存修改的信息"""
stock_code = ret.group(1)
comment = ret.group(2)
comment = urllib.parse.unquote(comment)
conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
cs = conn.cursor()
sql = """update focus set note_info=%s where info_id = (select id from info where code=%s);"""
cs.execute(sql, (comment, stock_code))
conn.commit()
cs.close()
conn.close()
return "修改成功..."
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! 我爱你中国....'
"""
logging.basicConfig(level=logging.INFO,
filename='./log.txt',
filemode='a',
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
logging.info("访问的是,%s" % file_name)
try:
# func = URL_FUNC_DICT[file_name]
# return func()
# return URL_FUNC_DICT[file_name]()
for url, func in URL_FUNC_DICT.items():
# {
# r"/index.html":index,
# r"/center.html":center,
# r"/add/\d+\.html":add_focus
# }
ret = re.match(url, file_name)
if ret:
return func(ret)
else:
logging.warning("没有对应的函数....")
return "请求的url(%s)没有对应的函数...." % file_name
except Exception as ret:
return "产生了异常:%s" % str(ret)