1.web服务器示意图
当浏览器请求的是一个静态页面时,服务器直接调用静态资源再发送给浏览器即可
当浏览器请求的是需要展示动态页面时,则需要将要展示的动态内容重新写在一个文件里,这个文件就是一个web框架,浏览器发来的页面请求信息传入给框架中,在框架中进行资源的寻找调用,并最后返回给服务器一个body内容,服务器再将body和header信息结合后发送给浏览器。
·
·
2.关于WSGI协议的流程
WSGI协议是为了匹配服务器使用框架的规则,也就是说所有的框架只要符合WSGI协议的规定,就可以被服务器拿来使用
·
·
·
定义WSGI接口:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return body(传给服务器请求页面的内容)
·
上面的 application()
函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:
··environ
:一个包含所有HTTP请求信息的dict对象;
··start_response
:一个发送HTTP响应的函数。
·
在服务器中会自建一个函数,用来保存框架给出的HTTP协议header头信息,在服务器调用框架的
application()
函数时,会将这个函数传进去,也就是传给start_response
,在框架中会在application()
函数里,对接收到的这个函数传入实参,也就是传入HTTP协议的header头信息,这样在服务器中,这个函数便会收到并保存框架给的头信息,浏览器请求的页面文件名字会以字典的形式传入environ,这样在框架中可以通过environ这个参数得到浏览器请求的页面文件名字。
在框架中的
application()
函数中执行了所有任务后,最后一定会返回一个值,这个值就是HTTP协议的body信息,服务器调用框架的application()
函数后就可以得到这个body内容,并和之前得到的header信息一起发送给浏览器。
·
(这里的文件为自定义文件,需自行补充)
服务器代码实例:
WSGI_http_socket.py
import socket
import view.mini_frame
import time
import multiprocessing
import re
class http_server(object):
def __init__(self):
# 创建套接字
self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置先让服务器断开连接
self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定本地ip和端口
self.tcp_socket.bind(("",8080))
# 设置套接字改主动为被动状态
self.tcp_socket.listen(128)
def handle_data(self, new_client_socket):
# 接受浏览器最大为1024个字节的请求信息并将字节流解码为字符串形式
recv_data = new_client_socket.recv(1024).decode("utf-8")
recv_data_list = recv_data.splitlines()
# 利用正则表达式得到浏览器请求的页面文件
find_dire = re.match(r"[^/]+(/[^ ]*)", recv_data_list[0])
find_data = find_dire.group(1)
if find_data == "/":
find_data = "./templates/1.html"
# 判断浏览器请求的页面是否以.py为结尾
if not find_data.endswith(".py"):
try:
f = open("./static" + find_data, "rb")
except:
responds = "HTTP/1.1 404 NOT FOUND\r\n"
responds += "\r\n"
responds += "file not found"
new_client_socket.send(responds.encode("gbk"))
else:
content = f.read()
f.close()
responds = "HTTP/1.1 200 OK \r\n"
responds += "\r\n"
new_client_socket.send(responds.encode("utf-8"))
new_client_socket.send(content)
else:
# 将文件名保存到字典中传到框架
env = dict()
env["Path_file"] = find_data
# 将函数实例函数set_response_header传给框架是为了用实例函数接收header信息,body用来接收框架运行结束后返回的要显示的内容
body = view.mini_frame.application(env, self.set_response_header)
print(body)
print("**")
# 在服务器中确定HTTP协议的版本号"HTTP/1.1"
header = "HTTP/1.1 %s\r\n" % self.status
for i in self.headers:
header += "%s:%s\r\n" % (i[0], i[1])
header += "\r\n"
responds = header + body
new_client_socket.send(responds.encode("utf-8"))
new_client_socket.close()
def set_response_header(self, status, headers):
self.status = status
# 在服务器中添加服务器名称版本
self.headers = [("server", "mini-webv1.0")]
self.headers += headers
def runserver(self):
while True:
print("等待浏览器链接")
new_client_socket,client_addr = self.tcp_socket.accept()
p = multiprocessing.Process(target=self.handle_data, args=(new_client_socket,))
p.start()
new_client_socket.close()
self.tcp_socket.close()
def main():
server = http_server()
server.runserver()
if __name__ == "__main__":
main()
·
框架代码实例:
mini_frame.py
def index():
with open("./templates/index.html") as f:
content = f.read()
return content
def center():
with open("./templates/center.html", "r") as f:
content = f.read()
return content
def application(env, start_response):
# 给服务器中保存头信息的函数传入实参(header信息)
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
# 得到浏览器请求的页面文件名
file_name = env['Path_file']
if file_name == "/index.py":
return index()
elif file_name == "/center.py":
return center()
else:
return 'Hello World!我爱你中国'
·
·
优化服务器代码,使服务器与框架基本解耦
(将所有路径存入配置文件中,将端口号,和调用哪个模块里的哪个函数在运行程序时传入)
文件路径配置代码:
web_server.conf
{
"static_path":"./static",
"view_path":"./view"
}
·
框架代码:
mini_frame.py
def index():
with open("./templates/index.html") as f:
content = f.read()
return content
def center():
with open("./templates/center.html", "r") as f:
content = f.read()
return content
def application(env, start_response):
# 给服务器中保存头信息的函数传入实参(header信息)
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
# 给服务器中保存头信息的函数传入实参(header信息)
file_name = env['Path_file']
if file_name == "/index.py":
return index()
elif file_name == "/center.py":
return center()
else:
return 'Hello World!我爱你中国'
·
服务器代码:
WSGI_http_socket.py
import socket
import sys
# import view.mini_frame
import time
import multiprocessing
import re
# 默认端口号为8080
PORT = 8080
class http_server(object):
def __init__(self, PORT, app, static_path):
# 创建套接字
self.tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置先让服务器断开连接
self.tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定本地ip和端口
self.tcp_socket.bind(("", PORT))
# 设置套接字改主动为被动状态
self.tcp_socket.listen(128)
self.application = app
self.static_path = static_path
def handle_data(self, new_client_socket):
# 接受浏览器最大为1024个字节的请求信息并将字节流解码为字符串形式
recv_data = new_client_socket.recv(1024).decode("utf-8")
recv_data_list = recv_data.splitlines()
# 利用正则表达式得到浏览器请求的页面文件
find_dire = re.match(r"[^/]+(/[^ ]*)", recv_data_list[0])
find_data = find_dire.group(1)
if find_data == "/":
find_data = "/1.html"
# 判断浏览器请求的页面是否以.py为结尾
if not find_data.endswith(".py"):
try:
f = open(self.static_path+find_data, "rb")
except:
responds = "HTTP/1.1 404 NOT FOUND\r\n"
responds += "\r\n"
responds += "file not found"
new_client_socket.send(responds.encode("gbk"))
else:
content = f.read()
f.close()
responds = "HTTP/1.1 200 OK \r\n"
responds += "\r\n"
new_client_socket.send(responds.encode("utf-8"))
new_client_socket.send(content)
else:
# 将文件名保存到字典中传到框架
env = dict()
env["Path_file"] = find_data
# 将函数实例函数set_response_header传给框架是为了用实例函数接收header信息,body用来接收框架运行结束后返回的要显示的内容
body = self.application(env, self.set_response_header)
print(body)
print("**")
# 在服务器中确定HTTP协议的版本号"HTTP/1.1"
header = "HTTP/1.1 %s\r\n" % self.status
for i in self.headers:
header += "%s:%s\r\n" % (i[0], i[1])
header += "\r\n"
responds = header + body
new_client_socket.send(responds.encode("utf-8"))
new_client_socket.close()
def set_response_header(self, status, headers):
self.status = status
# 在服务器中添加服务器名称版本
self.headers = [("server", "mini-webv1.0")]
self.headers += headers
def runserver(self):
while True:
print("等待浏览器链接")
new_client_socket,client_addr = self.tcp_socket.accept()
p = multiprocessing.Process(target=self.handle_data, args=(new_client_socket,))
p.start()
new_client_socket.close()
self.tcp_socket.close()
def main():
global PORT
frame_app_name = ""
frame_name = ""
if len(sys.argv) == 3:
try:
PORT = int(sys.argv[1])
frame_app_name = sys.argv[2]
except:
print("端口输入有误")
# 得到框架名和要调用的函数名
ret = re.match(r"([^:]+):(.*)", frame_app_name)
if ret:
frame_name = ret.group(1)
app_name = ret.group(2)
else:
print("请按照:python3 xxx.py 端口号 框架名:函数名")
with open("./web_server.conf", "r") as f:
# eval()函数将字符串当作有效的表达式,这里将读出的内容转变成有效的字典存入content中
conf_info = eval(f.read())
# 向系统变量中添加路径,使得程序在用import导入模块时也会在导入的路径中寻找该模块
sys.path.append(conf_info["view_path"])
# 通过import+变量会在路径中直接找该变量名,如果用__import__(变量)会先解析该变量,再去引入解析后的模块名,返回值将会标记着导入的模块
frame = __import__(frame_name)
# getattr()函数,使app指向了mini—frame模块中的application函数
app = getattr(frame, app_name)
server = http_server(PORT, app, conf_info["static_path"])
server.runserver()
if __name__ == "__main__":
main()
·
·
优化框架代码,通过使用装饰器传参,实现路由功能
对于不同的请求,安排调用不同的函数并进行回应的这种机制也叫做路由功能
框架代码:
URL_DICT = dict()
def dict_url(url):
def add_dict(func):
# 裝飾器傳的參數保存在url,func指向被裝飾的函數
URL_DICT[url] = func
def call_func(*args, **kwargs):
return func(*args, **kwargs)
return call_func
return add_dict
@dict_url("/index.py")
def index():
with open("./templates/index.html") as f:
content = f.read()
return content
@dict_url("/center.py")
def center():
with open("./templates/center.html", "r") as f:
content = f.read()
return content
def add_dict(func):
def call_func(*args, **kwargs):
return func(*args, **kwargs)
return call_func
def application(env, start_response):
# 给服务器中保存头信息的函数传入实参(header信息)
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
# 给服务器中保存头信息的函数传入实参(header信息)
file_name = env['Path_file']
try:
# 利用字典,key保存路徑名,value保存函數的引用,加括號表示函數的調用
return URL_DICT[file_name]()
except Exception as ret:
return "輸入的地址不正確:%s" % str(ret)
·
·
·
3.在框架中添加对数据库的增删改查功能,并增添log日志
框架代码:
import re
import logging
from pymysql import *
from urllib.parse import unquote
URL_DICT = dict()
template_root = "./templates"
def dict_url(url):
def add_dict(func):
# 裝飾器傳的參數保存在url,func指向被裝飾的函數
URL_DICT[url] = func
def call_func(*args, **kwargs):
return func(*args, **kwargs)
return call_func
return add_dict
@dict_url(r"/index.html")
def index(ret, file_name):
"""返回index.py需要的页面内容"""
try:
f = open(template_root + file_name)
except Exception as res:
return "%s" % res
else:
content = f.read()
f.close()
conn = connect(host="localhost", port=3306, user="root", password="mysql", database="gupiao", charset="utf8")
cs = conn.cursor()
cs.execute("select * from info;")
stock_info = cs.fetchall()
cs.close()
conn.close()
# 定義展示內容的格式
tr = """<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 i in stock_info:
html += tr % (i[0], i[1], i[2] ,i[3], i[4], i[5], i[6], i[7], i[1])
# 用查詢到的信息內容(強轉爲字符串)去替換content裏的\{%content%\}
content = re.sub(r"\{%content%\}", html, content)
return content
@dict_url(r"/center.html")
def center(ret, file_name):
"""返回center.py需要的页面内容"""
# return "hahha" + os.getcwd() # for test 路径问题
try:
f = open(template_root + file_name)
except Exception as res:
return "%s" % res
else:
content = f.read()
f.close()
conn = connect(host="localhost", port=3306, user="root", password="mysql", database="gupiao", charset="utf8")
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;")
text = cs.fetchall()
cs.close()
conn.close()
tr = """
etr>
<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 i in text:
html += tr % (i[0], i[1], i[2], i[3], i[4], i[5], i[6], i[0], i[0])
content = re.sub(r"\{%content%\}", html, content)
return content
@dict_url(r"/update/(\d+)\.html")
def update(ret, file_name):
"""显示 更新页面的内容"""
try:
f = open("./templates/update.html")
except Exception as ret:
return "%s,,,没有找到%s" % (ret, template_file_name)
else:
content = f.read()
f.close()
if ret:
stock_code = ret.group(1)
else:
stock_code = 0
conn = connect(host='localhost',port=3306,user='root',password='mysql',database='gupiao',charset='utf8')
cursor = conn.cursor()
# 会出现sql注入,怎样修改呢? 参数化
sql = """select focus.note_info from focus inner join info on focus.info_id=info.id where info.code=%s;""" % stock_code
cursor.execute(sql)
stock_note_info = cursor.fetchone()
cursor.close()
conn.close()
content = re.sub(r"\{%code%\}", stock_code, content)
content = re.sub(r"\{%note_info%\}", str(stock_note_info[0]), content)
return content
@dict_url(r"/update/(\d*)/(.*)\.html")
def update_note_info(ret, file_name):
"""进行数据的真正更新"""
stock_code = 0
stock_note_info = ""
if ret:
stock_code = ret.group(1)
stock_note_info = ret.group(2)
stock_note_info = unquote(stock_note_info)
conn = connect(host='localhost',port=3306,user='root',password='mysql',database='gupiao',charset='utf8')
cursor = conn.cursor()
# 会出现sql注入,怎样修改呢? 参数化
sql = """update focus inner join info on focus.info_id=info.id set focus.note_info=%s where info.code=%s;"""
cursor.execute(sql, (stock_note_info, stock_code))
conn.commit()
cursor.close()
conn.close()
return "修改成功"
@dict_url(r"/add/(\d+)\.html")
def add_focus(ret, file_name):
stock_code = ret.group(1)
conn = connect(host="localhost", port=3306, user="root", password="mysql", database="gupiao", charset="utf8")
cs = conn.cursor()
cs.execute("select * from focus as f inner join info as i on f.info_id=i.id where i.code=%s;", (stock_code,))
if cs.fetchone():
cs.close()
conn.close()
return "该信息已经关注过啦"
cs.execute("insert into focus(info_id) select id from info where code=%s;", (stock_code,))
conn.commit()
cs.close()
conn.close()
return "消息增加成功"
@dict_url(r"/del/(\d+)\.html")
def del_focus(ret, file_name):
stock_code = ret.group(1)
conn = connect(host="localhost", port=3306, user="root", password="mysql", database="gupiao", charset="utf8")
cs = conn.cursor()
cs.execute("delete from focus where info_id=(select id from info where code=%s)", (stock_code,))
conn.commit()
cs.close()
conn.close()
def application(env, start_response):
# 给服务器中保存头信息的函数传入实参(header信息)
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
# 给服务器中保存头信息的函数传入实参(header信息)
file_name = env['Path_file']
# 添加log日志打印,打印到文件中
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:
# 利用字典,key保存路徑名,value保存函數的引用,加括號表示函數的調用
for url,call_func in URL_DICT.items():
print(url)
ret = re.match(url, file_name)
if ret:
return call_func(ret, file_name)
break
else:
logging.warning("访问不到页面")
return "访问不到页面--%s" % file_name
except Exception as ret:
return "%s" % str(ret)