上一節(jié)已經(jīng)介紹了一些關(guān)于wsgi的一些相關(guān)的知識宾符,這節(jié)我們手動實現(xiàn)一下我們的web框架鱼填。
首先看一下我的項目結(jié)構(gòu):
其中web_server.py文件是我們的服務(wù)器文件刮刑,而Application.py文件是我們的web框架文件干旁,里面定義了wsgi接口。
具體項目代碼如下:
- web_server.py
# -*- coding:utf-8 -*-
import sys,re,socket,gevent
from gevent import monkey; monkey.patch_all()
class WSGIServer(object):
'''定義一個WSGI服務(wù)器的類'''
def __init__(self,port,documents_root,app):
self.server_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.server_sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
self.server_sock.bind(('',port))
self.server_sock.listen(128)
#設(shè)定靜態(tài)資源文件的路徑
self.documents_root = documents_root
#設(shè)定web框架可以調(diào)用的函數(shù)
self.app = app #application
def startup(self):
'''運(yùn)行服務(wù)器'''
while True:
client_sock,addr = self.server_sock.accept()
# client_sock.settimeout(3)
gevent.spawn(self.deal_with_request,client_sock)
# gevent.joinall([
# gevent.spawn(self.deal_with_request,client_sock)
# ])
def deal_with_request(self,client_sock):
'''
以短連接的方式桩撮,為這個瀏覽器服務(wù)
注意:如果是長連接敦第,頻繁的關(guān)閉socket會導(dǎo)致錯誤:1--->[Errno 9] Bad file descriptor
while True:
'''
rev_data = client_sock.recv(4096)
if not rev_data:
print('客戶端已經(jīng)下線了')
client_sock.close()
return
request_lines = rev_data.decode().splitlines()
# GET /static/index.html HTTP/1.1
ret = re.match(r'([^/]*)([^ ]+)', request_lines[0])
if ret:
file_name = ret.group(2)
if file_name == '/':
file_name = '/index.html'
# 如果不是以.html結(jié)尾的文件,都認(rèn)為是普通的文件
# 如果是.html結(jié)尾的文件店量,就讓web框架進(jìn)行處理
if not file_name.endswith('.html'):
try:
# print("./static"+file_name,"*"*100)
with open(self.documents_root + file_name, 'rb') as f:
response_body = f.read()
except:
response_header = 'HTTP/1.1 404 NOT FOUND\r\n\r\n'
# response_header += "Content-Type: text/html;charset=utf-8\r\n"
# response_header += 'Content-Length: %d\r\n\r\n' % len(response_body)
response_body = 'file not found,請輸入正確的URL!'
response = response_header + response_body
client_sock.send(response.encode('utf-8'))
else:
response_header = 'HTTP/1.1 200 OK\r\n\r\n'
'''注意:不能設(shè)置Content-Type芜果,會導(dǎo)致瀏覽器不能解析css,js等文件'''
# response_header += "Content-Type: text/html;charset=utf-8\r\n"
# response_header += 'Content-Length: %d\r\n\r\n' % len(response_body.decode())
response = response_header.encode('utf-8') + response_body
client_sock.send(response)
finally:
client_sock.close()
# 以.html結(jié)尾的文件融师,就認(rèn)為是瀏覽器需要動態(tài)的頁面
else:
env = dict(PATH_INFO=file_name)
# 保存 web返回的數(shù)據(jù)
response_body = self.app(env, self.set_response_headers) # 第2個參數(shù)傳遞的是函數(shù)的引用右钾,由web框架來執(zhí)行。
# print('110 >>>>>>>>',response_body)
response_header = 'HTTP/1.1 %s\r\n' % self.status
# response_header += "Content-Type: text/html;charset=utf-8\r\n"
# response_header += 'Content-Length: %d\r\n' % len(response_body)
for header in self.headers:
response_header += '%s: %s\r\n' % (header[0],header[1]) # *header: 對元祖解包
response_header += '\r\n'
response = response_header + response_body
client_sock.send(response.encode('utf-8')) # Chrome默認(rèn)編碼是gbk旱爆,不設(shè)置成gbk的話瀏覽器無法解析符號
client_sock.close()
def set_response_headers(self,status,headers):
'''這個方法會在web框架中被默認(rèn)調(diào)用舀射。'''
# response_header_default = [
# ('Data',time.ctime()),
# ('Server','ItCast-python mini web server')
# ]
#將web框架設(shè)置的狀態(tài)碼,頭信息存儲起來
# self.headers = [status, response_header_default + headers]
self.status = status
self.headers = headers
#設(shè)置靜態(tài)資源訪問路徑
g_static_document_root = './static'
def main():
if len(sys.argv)==3:
port = sys.argv[1]
if port.isdigit():
port = int(port)
web_frame_module_app_name = sys.argv[2]
else:
print("運(yùn)行方式如: python3 xxx.py 7890 my_web_frame_name:application")
return
# print('http服務(wù)器使用的端口號是:%s' % port)
# my_web_frame_name:application
ret = re.match(r"([^:]*):(.*)",web_frame_module_app_name)
if ret:
web_frame_module_name = ret.group(1)
# print('==',web_frame_module_name)
app_name = ret.group(2)
# print('==',app_name)
web_frame_module = __import__(web_frame_module_name) #return Application
app = getattr(web_frame_module,app_name) # getattr():獲取對象的屬性(application方法)
# print(app)
http_server = WSGIServer(port,g_static_document_root,app)
http_server.startup()
if __name__ == '__main__':
main()
- Application.py
# -*- coding:utf-8 -*-
import re
from pymysql import connect
from urllib.parse import unquote # unquote 對URL進(jìn)行解碼疼鸟。
template_root = './templates'
# 用來存放url路由映射
g_url_route = dict()
def connect_mysql():
conn = connect(host='localhost', user='root', password="cz",
database='stock_db', port=3306,
charset='utf8')
cur = conn.cursor()
return cur, conn
# 裝飾器,添加路由功能
def route(path):
def func1(func):
g_url_route[path] = func
def func2(*args, **kwargs):
return func(*args, **kwargs)
return func2
return func1
@route(r'/index\.html') # 相當(dāng)于執(zhí)行 index = route('/index.html')(index)
def index(file_name, url=None):
try:
with open(template_root + file_name,encoding='utf-8') as f:
content = f.read() # 讀取的是HTML文檔
except Exception as e:
print('2=====', e)
else:
cur, conn = connect_mysql()
sql = '''select * from info'''
ret = cur.execute(sql)
if ret:
data_from_mysql = cur.fetchall()
# print('>>>',data_from_mysql)
html_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 info in data_from_mysql:
html += html_template % (
info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7], info[1])
# 這是django所使用的模板引擎約定的 {{ 變量 }}, {% 代碼段落 %} 表示方法
# {%content%} :前端和后臺提前約定好的字段。
content = re.sub(r"\{%content%\}", html, content)
# print('*'*50,content)
cur.close()
conn.close()
return content
@route(r'/center\.html')
def center(file_name, url=None):
try:
with open(template_root + file_name,encoding='utf-8') as f:
content = f.read()
except Exception as e:
print('3======', e)
else:
cur, conn = connect_mysql()
sql = '''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 where i.id = f.info_id'''
ret = cur.execute(sql)
if ret:
data_from_mysql = cur.fetchall()
# print('>>>', data_from_mysql)
html_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 info in data_from_mysql:
html += html_template % (
info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[0], info[0])
content = re.sub(r'\{%content%\}', html, content)
return content
else:
content = re.sub(r'\{%content%\}',"<h4 style='color: red'>暫時沒有關(guān)注信息庙曙,請先關(guān)注哦<h4>",content)
return content
@route(r'/update/(\d*)\.html') # 底層實現(xiàn): update = route(r'/update/(\d*)\.html')(update)
def update(file_name, url):
'''顯示更新頁面的內(nèi)容'''
try:
with open(template_root + '/update.html') as f:
content = f.read()
except Exception as e:
print('4======', e)
else:
# print('url-----1------', url)
# print('filename------1----', file_name)
ret = re.match(url, file_name)
if ret:
stock_code = ret.group(1)
cur, conn = connect_mysql()
sql = '''select f.note_info from focus as f inner join info as i on f.info_id=i.id where i.code=%s'''
ret = cur.execute(sql, [stock_code])
if ret:
data_from_mysql = cur.fetchone()
# print('4==>',data_from_mysql)
# print('>>>', data_from_mysql)
content = re.sub(r'\{%code%\}', stock_code, content)
content = re.sub(r'\{%note_info%\}', data_from_mysql[0], content)
cur.close()
conn.close()
return content
@route(r'/update/(\d*)/(.*)\.html')
def update_note_info(file_name, url):
'''進(jìn)行數(shù)據(jù)的真正更新'''
ret = re.match(url, file_name)
if ret:
stock_code = ret.group(1)
stock_note_info = ret.group(2)
stock_note_info = unquote(stock_note_info)
cur, conn = connect_mysql()
sql = '''update focus as f inner join info as i on f.info_id=i.id set f.note_info=%s where i.code=%s'''
result = cur.execute(sql, [stock_note_info, stock_code])
if result:
conn.commit()
cur.close()
conn.close()
return '修改股票%s的備注信息成功'%stock_code
@route(r'/add/(\d{6})\.html')
def add(file_name, url):
'''添加關(guān)注'''
# print("hahahahahahahaha"*10)
ret = re.match(url, file_name)
if ret:
stock_code = ret.group(1)
# print('=xxxxxxxxx=',stock_code)
# 判斷是否已經(jīng)關(guān)注
cur, conn = connect_mysql()
sql = """select * from focus inner join info on focus.info_id=info.id where info.code=%s"""
cur.execute(sql, [stock_code])
if cur.fetchone():
cur.close()
conn.close()
return '已經(jīng)關(guān)注了股票%s空镜,請不要重復(fù)關(guān)注' % stock_code
# 沒有關(guān)注,就進(jìn)行關(guān)注
sql = '''insert into focus(info_id) select id from info where info.code=%s'''
ret = cur.execute(sql, [stock_code])
if ret:
conn.commit()
cur.close()
conn.close()
return '關(guān)注股票%s成功了' % stock_code
@route(r'/del/(\d*)\.html')
def delete(file_name, url):
'''取消關(guān)注'''
ret = re.match(url, file_name)
if ret:
stock_code = ret.group(1)
cur, conn = connect_mysql()
# 如果已經(jīng)關(guān)注捌朴,就取消關(guān)注
sql = '''delete from focus where info_id = (select id from info where code=%s)'''
ret = cur.execute(sql, [stock_code])
if ret:
conn.commit()
cur.close()
conn.close()
return '取消關(guān)注股票%s成功了' % stock_code
def application(environ, start_response): # start_response是服務(wù)器傳過來的函數(shù)的引用吴攒,由web框架來執(zhí)行這個函數(shù)
# response_headers = [('Content-Type', 'text/html;charset=utf-8')]
response_headers = [('SERVER_PORT', '8080')]
file_name = environ['PATH_INFO']
for url, func in g_url_route.items(): # url:字典里保存的有正則表達(dá)式的字符串,也有普通的路徑字符串
ret = re.match(url, file_name)
if ret:
start_response('200 OK', response_headers)
# print('==haha===',file_name,url)
return func(file_name, url)
else:
start_response('404 Not Found', response_headers)
return 'sorry,你訪問的資源不存在砂蔽!'
- index.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>首頁 - 個人選股系統(tǒng) V5.87</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<script src="/js/jquery-1.12.4.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script>
$(document).ready(function(){
$("input[name='toAdd']").each(function(){
var currentAdd = $(this);
currentAdd.click(function(){
code = $(this).attr("systemIdVaule");
// alert("/add/" + code + ".html");
$.get("/add/" + code + ".html", function(data, status){
alert("數(shù)據(jù): " + data + "\n狀態(tài): " + status);
});
});
});
});
</script>
</head>
<body>
<div class="navbar navbar-inverse navbar-static-top ">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" data-toggle="collapse" data-target="#mymenu">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="#" class="navbar-brand">選股系統(tǒng)</a>
</div>
<div class="collapse navbar-collapse" id="mymenu">
<ul class="nav navbar-nav">
<li class="active"><a href="">股票信息</a></li>
<li><a href="/center.html">個人中心</a></li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="container-fluid">
<table class="table table-hover">
<tr>
<th>序號</th>
<th>股票代碼</th>
<th>股票簡稱</th>
<th>漲跌幅</th>
<th>換手率</th>
<th>最新價(元)</th>
<th>前期高點</th>
<th>前期高點日期</th>
<th>添加自選</th>
</tr>
{%content%}
</table>
</div>
</div>
</body>
</html>
- center.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>個人中心 - 個人選股系統(tǒng) V5.87</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<script src="/js/jquery-1.12.4.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script>
$(document).ready(function(){
$("input[name='toDel']").each(function(){
var currentAdd = $(this);
currentAdd.click(function(){
code = $(this).attr("systemIdVaule");
// alert("/del/" + code + ".html");
$.get("/del/" + code + ".html", function(data, status){
alert("數(shù)據(jù): " + data + "\n狀態(tài): " + status);
});
window.location.reload()
});
});
});
</script>
</head>
<body>
<div class="navbar navbar-inverse navbar-static-top ">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" data-toggle="collapse" data-target="#mymenu">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="#" class="navbar-brand">選股系統(tǒng)</a>
</div>
<div class="collapse navbar-collapse" id="mymenu">
<ul class="nav navbar-nav">
<li ><a href="/index.html">股票信息</a></li>
<li class="active"><a href="">個人中心</a></li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="container-fluid">
<table class="table table-hover">
<tr>
<th>股票代碼</th>
<th>股票簡稱</th>
<th>漲跌幅</th>
<th>換手率</th>
<th>最新價(元)</th>
<th>前期高點</th>
<th style="color:red">備注信息</th>
<th>修改備注</th>
<th>del</th>
</tr>
{%content%}
</table>
</div>
</div>
</body>
</html>
- update.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>首頁 - 個人選股系統(tǒng) V5.87</title>
<link href="/css/bootstrap.min.css" rel="stylesheet">
<script src="/js/jquery-1.12.4.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
<script>
$(document).ready(function(){
$("#update").click(function(){
var item = $("#note_info").val();
// alert("/update/{%code%}/" + item + ".html");
$.get("/update/{%code%}/" + item + ".html", function(data, status){
alert("數(shù)據(jù): " + data + "\n狀態(tài): " + status);
self.location='/center.html';
});
});
});
</script>
</head>
<body>
<div class="navbar navbar-inverse navbar-static-top ">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" data-toggle="collapse" data-target="#mymenu">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a href="#" class="navbar-brand">選股系統(tǒng)</a>
</div>
<div class="collapse navbar-collapse" id="mymenu">
<ul class="nav navbar-nav">
<li><a href="/index.html">股票信息</a></li>
<li><a href="/center.html">個人中心</a></li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="container-fluid">
<div class="input-group">
<span class="input-group-addon">正在修改:</span>
<span class="input-group-addon">{%code%}</span>
<input id="note_info" type="text" class="form-control" aria-label="Amount (to the nearest dollar)" value="{%note_info%}">
<span id="update" class="input-group-addon" style="cursor: pointer">修改</span>
</div>
</div>
</div>
</body>
</html>
具體的static里面的靜態(tài)css和js文件可以在網(wǎng)上下載洼怔。
運(yùn)行此服務(wù)器需要在終端運(yùn)行,具體運(yùn)行命令格式如下:
python3 web_server.py 8080 Application:application