WSGI接口
而通常web服務(wù)器必須具備WSGI接口,所有的現(xiàn)代Python Web框架都已具備WSGI接口,它讓你不對(duì)代碼作修改就能使服務(wù)器和特點(diǎn)的web框架協(xié)同工作。
WSGI由web服務(wù)器支持,而web框架允許你選擇適合自己的配對(duì),但它同樣對(duì)于服務(wù)器和框架開(kāi)發(fā)者提供便利使他們可以專(zhuān)注于自己偏愛(ài)的領(lǐng)域和專(zhuān)長(zhǎng)而不至于相互牽制。其他語(yǔ)言也有類(lèi)似接口:java有Servlet API,Ruby 有 Rack。
我們需在web框架內(nèi)定義一個(gè)WSGI接口函數(shù),而web服務(wù)器與框架進(jìn)行協(xié)同工作時(shí),只需通過(guò)該接口函數(shù)即可,其他的實(shí)現(xiàn)均封裝在該框架的底部;該接口函數(shù)如下:
# WSGI接口函數(shù)
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return 'Hello World!'
參數(shù):
environ :一個(gè)包含所有HTTP請(qǐng)求信息的dict對(duì)象;即告訴框架需要調(diào)取哪些動(dòng)態(tài)資源頁(yè)面;
start_response :一個(gè)發(fā)送HTTP響應(yīng)的函數(shù)。該函數(shù)相當(dāng)于HTTP服務(wù)器給與框架的一個(gè)容器,而框架將頭部信息通過(guò)參數(shù)的方式存放在該函數(shù)內(nèi),最后服務(wù)器再?gòu)闹腥〕觯?/p>
返回值:
返回的是請(qǐng)求的動(dòng)態(tài)資源的響應(yīng)消息的body部分;通常web框架將動(dòng)態(tài)資源替換HTML模板的消息返回給服務(wù)器;
代碼實(shí)例
web_server.py
解析:
1、在web服務(wù)器中,我們首先根據(jù)資源的請(qǐng)求不同,給出的處理方式不同即靜態(tài)資源直接從本地磁盤(pán)讀取,而動(dòng)態(tài)資源交給web框架處理。
2、在與web框架進(jìn)行交互,調(diào)用了wsgi接口函數(shù),傳入了字典env,用來(lái)告訴服務(wù)器需求頁(yè)面;以及函數(shù)set_response_header用來(lái)獲取web框架傳遞的頭部信息;并且將返回值作為body;整個(gè)與web框架的交互均是圍繞著wsgi接口函數(shù)來(lái)實(shí)現(xiàn)的;
3、隨后便是讓我們的服務(wù)器運(yùn)行可以指定端口和web框架來(lái)運(yùn)行,則定義了main函數(shù)下面的一些代碼,通過(guò)sys.argv來(lái)獲取到指定的端口和web_frame:application等,再將這些作為參數(shù)傳入類(lèi) WSGIServer中來(lái)執(zhí)行;
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. 創(chuàng)建套接字
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. 變?yōu)楸O(jiān)聽(tīng)套接字
self.tcp_server_socket.listen(128)
self.application = app
self.static_path = static_path
def service_client(self, new_socket):
"""為這個(gè)客戶(hù)端返回?cái)?shù)據(jù)"""
# 1. 接收瀏覽器發(fā)送過(guò)來(lái)的請(qǐng)求 ,即http請(qǐng)求
# 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格式的數(shù)據(jù),給瀏覽器
# 2.1 如果請(qǐng)求的資源不是以.html結(jié)尾,那么就認(rèn)為是靜態(tài)資源(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 準(zhǔn)備發(fā)送給瀏覽器的數(shù)據(jù)---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 準(zhǔn)備發(fā)送給瀏覽器的數(shù)據(jù)---boy
# response += "hahahhah"
# 將response header發(fā)送給瀏覽器
new_socket.send(response.encode("utf-8"))
# 將response ic.mini_frame.applicationbody發(fā)送給瀏覽器
new_socket.send(html_content)
else:
# 2.2 如果是以.py結(jié)尾,那么就認(rèn)為是動(dòng)態(tài)資源的請(qǐng)求
env = dict() # 這個(gè)字典中存放的是web服務(wù)器要傳遞給 web框架的數(shù)據(jù)信息
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
# 發(fā)送response給瀏覽器
new_socket.send(response.encode("utf-8"))
# 關(guān)閉套接
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):
"""用來(lái)完成整體的控制"""
while True:
# 4. 等待新客戶(hù)端的鏈接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 為這個(gè)客戶(hù)端服務(wù)
p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
p.start()
new_socket.close()
# 關(guān)閉監(jiān)聽(tīng)套接字
self.tcp_server_socket.close()
def main():
"""控制整體,創(chuàng)建一個(gè)web 服務(wù)器對(duì)象,然后調(diào)用這個(gè)對(duì)象的run_forever方法運(yùn)行"""
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("端口輸入錯(cuò)誤。。。。。")
return
else:
print("請(qǐng)按照以下方式運(yùn)行:")
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("請(qǐng)按照以下方式運(yùn)行:")
print("python3 xxxx.py 7890 mini_frame:application")
return
with open("./web_server.conf") as f:
conf_info = eval(f.read())
# 此時(shí) conf_info是一個(gè)字典里面的數(shù)據(jù)為:
# {
# "static_path":"./static",
# "dynamic_path":"./dynamic"
# }
sys.path.append(conf_info['dynamic_path'])
# import frame_name --->找frame_name.py
frame = __import__(frame_name) # 返回值標(biāo)記這 導(dǎo)入的這個(gè)模板
app = getattr(frame, app_name) # 此時(shí)app就指向了 dynamic/mini_frame模塊中的application這個(gè)函數(shù)
# print(app)
wsgi_server = WSGIServer(port, app, conf_info['static_path'])
wsgi_server.run_forever()
if __name__ == "__main__":
main()
dynamic/mini_frame.py
在該框架內(nèi):定義了wsgi接口函數(shù),以及獲取指定頁(yè)面的函數(shù),在下篇中我們將會(huì)介紹如何將數(shù)據(jù)庫(kù)的內(nèi)容導(dǎo)入;
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 = "哈哈哈哈 這是你的本月名稱(chēng)....."
# content = re.sub(r"\{%content%\}", my_stock_info, content)
# 創(chuàng)建Connection連接
conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
# 獲得Cursor對(duì)象
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查詢(xún)出來(lái)的數(shù)據(jù)。。。"
# content = re.sub(r"\{%content%\}", my_stock_info, content)
# 創(chuàng)建Connection連接
conn = connect(host='localhost',port=3306,user='root',password='mysql',database='stock_db',charset='utf8')
# 獲得Cursor對(duì)象
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
# 給路由添加正則表達(dá)式的原因:在實(shí)際開(kāi)發(fā)時(shí),url中往往會(huì)帶有很多參數(shù),例如/add/000007.html中000007就是參數(shù),
# 如果沒(méi)有正則的話(huà),那么就需要編寫(xiě)N次@route來(lái)進(jìn)行添加 url對(duì)應(yīng)的函數(shù) 到字典中,此時(shí)字典中的鍵值對(duì)有N個(gè),浪費(fèi)空間
# 而采用了正則的話(huà),那么只要編寫(xiě)1次@route就可以完成多個(gè) url例如/add/00007.html /add/000036.html等對(duì)應(yīng)同一個(gè)函數(shù),此時(shí)字典中的鍵值對(duì)個(gè)數(shù)會(huì)少很多
@route(r"/add/(\d+)\.html")
def add_focus(ret):
# 1. 獲取股票代碼
stock_code = ret.group(1)
# 2. 判斷試下是否有這個(gè)股票代碼
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,))
# 如果要是沒(méi)有這個(gè)股票代碼,那么就認(rèn)為是非法的請(qǐng)求
if not cs.fetchone():
cs.close()
conn.close()
return "沒(méi)有這支股票,大哥 ,我們是創(chuàng)業(yè)公司,請(qǐng)手下留情..."
# 3. 判斷以下是否已經(jīng)關(guān)注過(guò)
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,))
# 如果查出來(lái)了,那么表示已經(jīng)關(guān)注過(guò)
if cs.fetchone():
cs.close()
conn.close()
return "已經(jīng)關(guān)注過(guò)了,請(qǐng)勿重復(fù)關(guān)注..."
# 4. 添加關(guān)注
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 "關(guān)注成功...."
@route(r"/del/(\d+)\.html")
def del_focus(ret):
# 1. 獲取股票代碼
stock_code = ret.group(1)
# 2. 判斷試下是否有這個(gè)股票代碼
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,))
# 如果要是沒(méi)有這個(gè)股票代碼,那么就認(rèn)為是非法的請(qǐng)求
if not cs.fetchone():
cs.close()
conn.close()
return "沒(méi)有這支股票,大哥 ,我們是創(chuàng)業(yè)公司,請(qǐng)手下留情..."
# 3. 判斷以下是否已經(jīng)關(guān)注過(guò)
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,))
# 如果沒(méi)有關(guān)注過(guò),那么表示非法的請(qǐng)求
if not cs.fetchone():
cs.close()
conn.close()
return "%s 之前未關(guān)注,請(qǐng)勿取消關(guān)注..." % stock_code
# 4. 取消關(guān)注
# 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 "取消關(guān)注成功...."
@route(r"/update/(\d+)\.html")
def show_update_page(ret):
"""顯示修改的那個(gè)頁(yè)面"""
# 1. 獲取股票代碼
stock_code = ret.group(1)
# 2. 打開(kāi)模板
with open("./templates/update.html") as f:
content = f.read()
# 3. 根據(jù)股票代碼查詢(xún)相關(guān)的備注信息
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] # 獲取這個(gè)股票對(duì)應(yīng)的備注信息
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! 我愛(ài)你中國(guó)....'
"""
logging.basicConfig(level=logging.INFO,
filename='./log.txt',
filemode='a',
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
logging.info("訪問(wèn)的是,%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("沒(méi)有對(duì)應(yīng)的函數(shù)....")
return "請(qǐng)求的url(%s)沒(méi)有對(duì)應(yīng)的函數(shù)...." % file_name
except Exception as ret:
return "產(chǎn)生了異常:%s" % str(ret)