使用Python開發輕量級的Web框架以及基於WSGI的服務器來實現一個網站頁面

說明:該篇博客是博主一字一碼編寫的,實屬不易,請尊重原創,謝謝你們!javascript

目錄css

一丶項目說明html

二丶數據準備 java

三丶使用網絡TCP開發一個基於WSGI協議的Web服務器python

四丶使用python3開發一個輕量級的Web框架mysql

五丶在框架中實現添加股票功能jquery

六丶在框架中實現刪除股票功能web

七丶在框架中實現修改股票功能ajax


一丶項目說明

1.實現過程正則表達式

用戶經過瀏覽器向Web服務器請求資源,Web服務器基於WSGI協議調用Web框架中application接口函數,在application函數中根據用戶請求地址,匹配路由規則,匹配成功後調用請求地址對應的視圖函數,在視圖函數中主要操做爲對MySQL數據庫的增刪改查以及讀取html頁面模板內容,而後將模板中須要填充的數據與MySQL返回的數據進行字符串拼接,而後返回給Web服務器,最終Web服務器將拿到的數據看成響應體數據,並與狀態碼和響應頭進行拼接後返回給瀏覽器,在瀏覽器上呈現用戶想要看到的資源數據

2.最終效果展現

  • 股票信息(index.html)
  • 邏輯說明:當用戶訪問該網頁,將數據庫中的info股票信息表的數據進行分頁查詢後顯示在網頁上,每頁顯示12條數據;當點擊不一樣的頁數時,那麼就要求顯示其對應頁數的正確數據;當前頁數爲第一頁或者最後一頁時,則不顯示上一頁和下一頁功能按鈕;每條數據都會有一個添加功能按鈕,當用戶點擊添加而且成功後,那麼這條股票數據就不會出現添加功能按鈕了

  •  我的中心(center.html)
  • 邏輯說明:在我的中心頁面顯示出用戶添加成功後的股票信息,每隻股票都有修改和刪除功能按鈕;當用戶點擊刪除功能後則該支股票不顯示在我的中心頁面中,那麼此時這支被刪除的股票在回到股票信息頁面中則出現添加功能按鈕;當用戶點擊修改功能按鈕,則成功跳轉到修改頁面進行修改

  • 修改界面(update.html) 
  • 邏輯說明:在修改頁面中顯示兩個股票信息字段,一個是股票代碼另外一個是備註信息,修改或添加備註信息後,立即修改功能按鈕,修改爲功後則跳轉到我的中心頁面,並正確顯示出該支股票修改添加後的信息

 3.效果動圖

二丶數據準備 

1.在MySQL中建立stock_db數據庫

create database stock_db charset=utf8;

2.在stock_db數據庫下建立股票信息表info

3.在stock_db數據庫下建立關注信息表focus

4.向focus表中的info_id字段添加info表的id外鍵

5.插入表數據

INSERT INTO `info` VALUES (1,'000007','全新好','10.01%','4.40%',16.05,14.60,'2019-03-18'),(2,'000036','華聯控股','10.04%','10.80%',11.29,10.26,'2019-03-20'),(3,'000039','中集集團','1.35%','1.78%',18.07,18.06,'2019-02-28'),(4,'000050','深天馬A','4.38%','4.65%',22.86,22.02,'2019-03-19'),(5,'000056','皇庭國際','0.39%','0.65%',12.96,12.91,'2019-03-20'),(6,'000059','華錦股份','3.37%','7.16%',12.26,12.24,'2018-12-11'),(7,'000060','中金嶺南','1.34%','3.39%',12.08,11.92,'2019-03-20'),(8,'000426','興業礦業','0.41%','2.17%',9.71,9.67,'2019-03-20'),(9,'000488','晨鳴紙業','6.30%','5.50%',16.37,15.59,'2019-03-10'),(10,'000528','柳工','1.84%','3.03%',9.42,9.33,'2019-03-19'),(11,'000540','中天金融','0.37%','5.46%',8.11,8.08,'2019-03-20'),(12,'000581','威孚高科','3.49%','3.72%',27.00,26.86,'2019-02-26'),(13,'000627','天茂集團','5.81%','12.51%',10.93,10.33,'2019-03-20'),(14,'000683','遠興能源','6.42%','21.27%',3.48,3.29,'2019-03-19'),(15,'000703','恆逸石化','0.24%','1.65%',16.92,16.88,'2019-03-20'),(16,'000822','山東海化','6.60%','8.54%',9.05,8.75,'2019-03-06'),(17,'000830','魯西化工','1.38%','4.80%',7.36,7.26,'2019-03-20'),(18,'000878','雲南銅業','1.26%','3.23%',14.50,14.47,'2019-03-19'),(19,'000905','廈門港務','5.44%','10.85%',15.90,15.60,'2018-12-20'),(20,'000990','誠志股份','0.53%','1.00%',16.99,16.90,'2019-03-20'),(21,'002019','億帆醫藥','1.19%','2.81%',17.05,16.85,'2019-03-20'),(22,'002078','太陽紙業','2.05%','1.90%',8.45,8.29,'2019-03-19'),(23,'002092','中泰化學','7.25%','6.20%',15.53,14.48,'2019-03-20'),(24,'002145','中核鈦白','2.43%','7.68%',6.75,6.61,'2019-03-19'),(25,'002285','世聯行','8.59%','5.66%',9.23,8.50,'2019-03-20'),(26,'002311','海大集團','1.13%','0.24%',18.81,18.63,'2019-03-19'),(27,'002460','贛鋒鋰業','9.41%','9.00%',63.70,58.22,'2019-03-20'),(28,'002466','天齊鋰業','3.62%','3.66%',68.44,66.05,'2019-03-20'),(29,'002470','金正大','2.30%','0.99%',8.00,7.82,'2019-03-20'),(30,'002496','輝豐股份','3.15%','4.29%',5.24,5.08,'2018-12-10'),(31,'002497','雅化集團','0.38%','12.36%',13.10,13.05,'2019-03-20'),(32,'002500','山西證券','0.44%','3.70%',11.49,11.44,'2019-03-20'),(33,'002636','金安國紀','2.70%','11.59%',19.80,19.42,'2019-03-19'),(34,'300032','金龍機電','0.66%','0.72%',15.28,15.18,'2019-03-20'),(35,'300115','長盈精密','0.60%','0.59%',33.50,33.41,'2019-03-19'),(36,'300268','萬福生科','-10.00%','0.27%',31.77,13.57,'2018-12-10'),(37,'300280','南通鍛壓','3.31%','0.66%',32.20,32.00,'2018-12-11'),(38,'300320','海達股份','0.28%','0.82%',18.26,18.21,'2019-03-20'),(39,'300408','三環集團','1.69%','0.81%',23.42,23.17,'2019-03-19'),(40,'300477','合縱科技','2.84%','5.12%',22.10,22.00,'2019-03-12'),(41,'600020','中原高速','5.46%','4.48%',5.60,5.31,'2019-03-20'),(42,'600033','福建高速','1.01%','1.77%',4.00,3.99,'2019-02-26'),(43,'600066','宇通客車','4.15%','1.49%',23.08,23.05,'2019-02-13'),(44,'600067','冠城大通','0.40%','2.97%',7.56,7.53,'2019-03-20'),(45,'600110','諾德股份','2.08%','4.26%',16.16,15.83,'2019-03-20'),(46,'600133','東湖高新','9.65%','21.74%',13.64,12.44,'2019-03-20'),(47,'600153','建發股份','3.65%','2.03%',13.35,13.21,'2019-03-10'),(48,'600180','瑞茂通','2.20%','1.07%',14.86,14.54,'2019-03-20'),(49,'600183','生益科技','6.94%','4.06%',14.94,14.12,'2019-03-19'),(50,'600188','兗州煤業','1.53%','0.99%',14.56,14.43,'2019-03-19'),(51,'600191','華資實業','10.03%','11.72%',15.80,14.36,'2019-03-20'),(52,'600210','紫江企業','6.03%','10.90%',6.68,6.30,'2019-03-20'),(53,'600212','江泉實業','1.39%','1.78%',10.20,10.15,'2019-03-19'),(54,'600225','*ST松江','4.96%','2.47%',5.71,5.61,'2018-12-13'),(55,'600230','滄州大化','5.74%','13.54%',43.26,40.91,'2019-03-20'),(56,'600231','凌鋼股份','2.79%','3.77%',3.68,3.60,'2019-03-19'),(57,'600291','西水股份','10.02%','9.23%',34.71,31.55,'2019-03-20'),(58,'600295','鄂爾多斯','4.96%','12.62%',16.51,15.73,'2019-03-20'),(59,'600303','曙光股份','8.37%','14.53%',11.53,10.64,'2019-03-20'),(60,'600308','華泰股份','1.12%','2.66%',6.30,6.26,'2019-03-19'),(61,'600309','萬華化學','0.03%','1.78%',31.81,31.80,'2019-03-20'),(62,'600352','浙江龍盛','0.39%','1.85%',10.32,10.28,'2019-03-20'),(63,'600354','敦煌種業','7.89%','18.74%',9.44,8.75,'2019-03-20'),(64,'600408','安泰集團','1.98%','3.38%',4.13,4.12,'2018-12-13'),(65,'600409','三友化工','0.62%','3.78%',11.36,11.29,'2019-03-20'),(66,'600499','科達潔能','0.46%','3.94%',8.84,8.80,'2019-03-20'),(67,'600508','上海能源','3.26%','2.99%',13.32,13.01,'2019-03-19'),(68,'600563','法拉電子','0.32%','1.36%',53.67,53.50,'2019-03-20'),(69,'600567','山鷹紙業','0.76%','2.85%',3.98,3.96,'2019-03-19'),(70,'600585','海螺水泥','0.45%','0.61%',24.51,24.44,'2019-03-19'),(71,'600668','尖峯集團','4.35%','6.43%',18.70,18.36,'2018-12-13'),(72,'600688','上海石化','2.72%','0.91%',6.80,6.74,'2019-02-01'),(73,'600729','重慶百貨','5.70%','3.34%',27.45,27.13,'2019-02-28'),(74,'600739','遼寧成大','3.30%','3.50%',19.74,19.11,'2019-03-20'),(75,'600779','水井坊','3.85%','2.77%',29.39,28.30,'2019-03-20'),(76,'600781','輔仁藥業','8.61%','4.16%',23.46,21.89,'2019-01-02'),(77,'600801','華新水泥','4.00%','10.15%',12.99,12.49,'2019-03-20'),(78,'600846','同濟科技','2.06%','17.41%',9.39,9.26,'2018-12-13'),(79,'600884','杉杉股份','1.08%','3.53%',20.67,20.45,'2019-03-20'),(80,'600966','博彙紙業','2.89%','5.54%',6.41,6.28,'2019-03-19'),(81,'600971','恆源煤電','2.36%','8.81%',12.16,11.88,'2019-03-20'),(82,'601012','隆基股份','0.76%','1.30%',19.93,19.78,'2019-03-20'),(83,'601100','恆立液壓','4.78%','0.92%',19.31,18.97,'2019-03-13'),(84,'601101','昊華能源','4.03%','6.06%',11.10,10.80,'2019-03-19'),(85,'601216','君正集團','2.16%','2.26%',5.20,5.10,'2018-12-17'),(86,'601666','平煤股份','2.81%','6.14%',6.96,6.77,'2019-03-20'),(87,'601668','中國建築','2.39%','1.42%',10.70,10.45,'2019-03-20'),(88,'601678','濱化股份','0.13%','2.47%',7.92,7.91,'2019-03-20'),(89,'601918','新集能源','1.23%','3.11%',4.93,4.92,'2019-03-19'),(90,'603167','渤海輪渡','2.77%','3.34%',11.87,11.61,'2018-12-13'),(91,'603369','今世緣','3.34%','2.13%',14.24,13.78,'2019-03-20'),(92,'603589','口子窖','3.99%','1.84%',39.37,39.04,'2019-02-26'),(93,'603799','華友鈷業','2.38%','7.19%',67.46,65.89,'2019-03-20'),(94,'603993','洛陽鉬業','2.94%','2.50%',7.36,7.16,'2019-03-19');
INSERT INTO `focus` VALUES (1,'先試一試',30),(2,'很是穩',38),(3,'通常般',78),(12,'不咋地',80),(17,'',23);

 6.查詢表數據

  

三丶使用網絡TCP開發一個基於WSGI協議的Web服務器

1.建立工程目錄以及文件

說明:dynamic目錄----存放動態資源,static目錄----存放靜態資源,templates目錄----存放模板文件

server.conf----服務器配置文件,web_server.py----服務器啓動文件

2.服務器文件web_server.py代碼實現

  • step1  代碼三大步,定義一個WSGIServer類,一個main主函數,腳本運行方式
import socket
import multiprocessing


class WSGIServer(object):
    def __init__(self):
        pass


def main():
    pass


if __name__ == '__main__':
    main()
  • step2 在__init__方法中建立TCP套接字綁定端口以及轉換爲被動套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

self.tcp_server_socket.bind(("", 7979))

self.tcp_server_socket.listen(128)
  • step3 由於這裏是做爲TCP服務器因此須要使用進程中的子線程來完成客戶端的鏈接處理,因此在main函數中進行截堵塞操做
while True:
    client_socket, client_addr = self.tcp_server_socket.accept()

    # 使用進程中的子線程來完成客戶端的鏈接處理
    p = multiprocessing.Process(target=self.handle_client, args=(client_socket,))
    p.start()

    client_socket.close()

tcp_server_socket.close()
  • step4 緊接着在類中定義handle_client方法,用於客戶端的處理,獲取客戶端的請求數據,從客戶端的請求數據中經過正則匹配獲取用戶請求地址,若是匹配成功,則獲取用戶請求的靜態文件名;經過文件讀寫方式,打開本地html目錄下的靜態文件,讀取靜態文件的內容,成功則組織服務器正確響應數據(響應頭響應體),返回給瀏覽器;例如用戶在瀏覽器中向http://127.0.0.1:7979/index.html地址發送請求則在handle_client函數中經過splitlines方法進行分割,獲取第一行數據GET /index.html HTTP/1.1,經過正則匹配到(/index.html)文件名,打開並讀取本地的靜態目錄html下的此文件的數據,返回給瀏覽器,最後呈現到頁面給用戶看
def handle_client(client_socket):

    request_data = client_socket.recv(1024).decode("utf-8")
    print(request_data)

    request_lines = request_data.splitlines()
    # print(request_lines)

    result = re.match(r"[^/]+([^ ]*)", request_lines[0])
    if result:
        # print(result.group(1)+"*"*10)
        file_name = result.group(1)

    try:
        with open("./html"+file_name, "rb") as f:
            server_response_body = f.read()
    except:
        server_response = "HTTP/1.1 404 NOT FOUND \r\n"
        server_response += "\r\n"
        server_response += "-----File Not Found-----"
        client_socket.send(server_response.encode("utf-8"))
    else:
        server_response_header = "HTTP/1.1 200 OK \r\n"
        server_response_header += "\r\n"

        client_socket.send(server_response_header.encode("utf-8"))
        client_socket.send(server_response_body)

        client_socket.close()

3.讓該TCP服務器遵循WSGI協議

  • step1 在dynamic目錄下建立__init__.py文件,讓該目錄成爲一個python包,而後在這個包下建立web_frame.py框架文件,在這個文件中定義application方法,遵循WSGI服務器協議
def index():
    return "this is index page!"


def login():
    return "this is login page!"


def application(environ, start_response):
    pass
  • step2 回到web_server.py服務器文件中,首先進行判斷,判斷用戶請求的是靜態資源仍是動態資源,這裏請求資源文件名爲.py結尾的爲動態資源進行判斷,當用戶請求的文件名不是以.py結尾的則在本地html靜態目錄中讀取文件內容直接返回給瀏覽器,若是用戶請求的是.py結尾的文件,則遵循WSGI協議,首先導入web_frame框架文件,調用其application方法,這個方法須要傳遞兩個參數,第一個參數environ是字典類型用做於服務器與框架之間須要的屬性,第二個參數start_response則爲函數引用用做於服務器響應客戶端的參數(header和body)
# 以.py爲結尾表示請求動態資源不然爲靜態資源
if not file_name.endswith(".py"):
    try:
        with open("../html"+file_name, "rb") as f:
            server_response_body = f.read()
    except:
        server_response = "HTTP/1.1 404 NOT FOUND \r\n"
        server_response += "\r\n"
        server_response += "-----File Not Found-----"
        client_socket.send(server_response.encode("utf-8"))
    else:
        server_response_header = "HTTP/1.1 200 OK \r\n"
        server_response_header += "\r\n"

        client_socket.send(server_response_header.encode("utf-8"))
        client_socket.send(server_response_body)
else:
    """遵循WSGI協議"""
    # 1.導入定義的web_frame框架,模擬WSGI調用過程
    import web_frame
    # 2.調用框架中的application方法
    env = dict()
    # print(file_name) /index.py
    env["PATH_INFO"] = file_name #{'PATH_INFO':'/index.py'}
    body = web_frame.application(env, self.start_response)
    # print(body, env)
    server_response_header = "HTTP/1.1 %s \r\n" % self.status
    for temp in self.header:
        server_response_header += '%s:%s\r\n' %(temp[0], temp[1])
    server_response_header += "\r\n"
    # server_response_body = "<h1 style='color:red;text-align:center'>這是動態資源 %s </h1>" % time.ctime()
    server_response_data = server_response_header + body
    client_socket.send(server_response_data.encode("utf-8"))

client_socket.close()
  • step3 在web_server.py服務器文件中在調用mini_frame框架中的application方法時,傳遞的第二個參數爲函數start_response的引用,因此在WSGIServer類中就須要定義start_response函數,這個函數須要接收兩個參數,一個是響應碼另外一是響應頭,博主在響應頭中添加了一個屬性爲server也就是服務器,爲web_frame/1.0,像百度等其餘大型網站的server都是有說明的,因此博主在這裏也給本身開發的服務器定義了一個版本名
# 3.定義start_response函數
def start_response(self, status, header):
    self.status = status
    self.header = [("server"," web_frame/1.0")]
    self.header += header

  • step4 在web_server.py服務器中將start_response函數的引用傳遞給application函數,因此在application函數中,須要調用服務器傳遞過來的start_response方法,並傳遞正確的響應碼和響應體
def application(environ, start_response):
    file_name = environ['PATH_INFO']
    start_response('200 OK', [('Content-Type', ' text/html;charset=utf-8')])
    if file_name == "/index.py":
        return index()
    elif file_name == "/login.py":
        return login()
    else:
        return '<h1 style="color:red;text-align:center">Hello World! 你好 中國!<h1>'

4.在瀏覽器中渲染出網頁模板文件

  •  step1 在templates模板目錄下建立一個index.html文件,其中{%content%}爲預留空間,用於以後數據替換
<!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>首頁 - 我的選股系統 V5.87</title>
    <link href="/css/bootstrap.min.css" rel="stylesheet">
  </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">選股系統</a>
    </div>
    <div class="collapse navbar-collapse" id="mymenu">
        <ul class="nav navbar-nav">
            <li class="active"><a href="/index.html">股票信息</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" style="background-color: #f1fde1">
        <tr style="background-color:#a6e22b;">
            <th>序號</th>
            <th>股票代碼</th>
            <th>股票簡稱</th>
            <th>漲跌幅</th>
            <th>換手率</th>
            <th>最新價(元)</th>
            <th>前期高點</th>
            <th>前期高點日期</th>
            <th>添加自選</th>
        </tr>          
      </table>
      {%content%}
    </div>
  </div>
    <script src="/js/bootstrap.min.js"></script>
  </body>
</html>
  • step2 在templates模板目錄下建立一個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>我的中心 - 我的選股系統 V5.87</title>
    <link href="/css/bootstrap.min.css" rel="stylesheet">
  </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">選股系統</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="/center.html">我的中心</a></li>
        </ul>

    </div>
    </div>
</div>
  <div class="container">

    <div class="container-fluid">

      <table class="table table-hover" style="background-color: #f1fde1">
        <tr style="background-color:#a6e22b;">
            <th>股票代碼</th>
            <th>股票簡稱</th>
            <th>漲跌幅</th>
            <th>換手率</th>
            <th>最新價(元)</th>
            <th>前期高點</th>
            <th style="color:red">備註信息</th>
            <th>修改備註</th>
            <th>刪除</th>
        </tr>
        {%content%}                     
      </table>
    </div>
  </div>
    <script src="/js/bootstrap.min.js"></script>
  </body>
</html>
  • step3 以前在web_server.py服務器文件中定義以.py爲結尾的文件名錶示動態資源,這樣作的話,用戶就知道咱們的服務器是以什麼代碼進行編寫的,就利於不懷好意者攻擊網站服務器,因此須要改成以.html爲結尾的請求資源名爲動態資源
if not file_name.endswith(".html"):
    # print(self.static_path_+file_name)
    try:
        with open("./static" + file_name, "rb") as f:
            server_response_body = f.read()
    except:
        server_response = "HTTP/1.1 404 NOT FOUND \r\n"
        server_response += "\r\n"
        server_response += "-----File Not Found-----"
        client_socket.send(server_response.encode("utf-8"))
    else:
        server_response_header = "HTTP/1.1 200 OK \r\n"
        server_response_header += "\r\n"

        client_socket.send(server_response_header.encode("utf-8"))
        client_socket.send(server_response_body)
else:
    """遵循WSGI協議"""
    # 1.導入定義的web_frame框架,模擬WSGI調用過程
    import web_frame
    # 2.調用框架中的application方法
    env = dict()
    # print(file_name) /index.html
    env["PATH_INFO"] = file_name #{'PATH_INFO':'/index.html'}
    env["page_num"] = 1
    body = web_frame.application(env, self.start_response)
    # print(body, env)
    # print(body, "5555555555")
    server_response_header = "HTTP/1.1 %s \r\n" % self.status
    for temp in self.header:
        server_response_header += '%s:%s\r\n' %(temp[0], temp[1])
    server_response_header += "\r\n"
    # server_response_body = "<h1 style='color:red;text-align:center'>這是動態資源 %s </h1>" % time.ctime()
    server_response_data = server_response_header + body
    client_socket.send(server_response_data.encode("utf-8"))

client_socket.close()
if result:
    # print(result.group(1)+"*"*10)
    file_name = result.group(1)
    if file_name == "/":
        file_name = "/index.html"
    # print(file_name,"qwqwqwq")
  •  step5 在web_frame.py框架文件中進行以下修改,首先判斷服務器傳過來的文件名爲哪一個資源名,再調用對應的接口函數,讀取對應的資源文件內容,返回給服務器
def index():
    with open("./templates/index.html",encoding="utf-8") as f:
        content = f.read()
    return content

def center():
    with open("./templates/center.html") as f:
        return f.read()
     

def application(env, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
    
    file_name = env['PATH_INFO']
    # file_name = "/index.html"

    if file_name == "/index.html":
        return index()
    elif file_name == "/center.html":
        return center()
    else:
        return '<h1 style="color:red;text-align:center">Hello World! 你好 中國!<h1>'
  • step6 在WSGIServer類中定義一個run_forever方法,將main函數中的邏輯代碼放在此函數中,在main函數中只須要建立實例對象調用run_forever方法,來控制整個程序
def run_forever(self):
        while True:
            client_socket, client_addr = self.tcp_server_socket.accept()

            # 使用進程中的子線程來完成客戶端的鏈接處理
            p = multiprocessing.Process(target=self.handle_client, args=(client_socket,))
            p.start()

            client_socket.close()

            # handle_client(client_socket)

        tcp_server_socket.close()
def main():
    WSGIServer().run_forever()

5.以解釋器 程序 端口號 框架名:方法,如python3 web_server.py 7979 web_frame:application運行此程序

  • step1 在server.conf文件中進行以下編寫,靜態文件路徑以及動態文件路徑,這樣作的目的是下降項目的耦合性,方便之後對代碼進行有效變動
{
    "static_path":"./static",
    "dynamic_path":"./dynamic"
}
  • step2 獲取配置文件中的內容
with open("./server.conf") as f:
    config = eval(f.read())
  • step3 獲取端口號
port = int(sys.argv[1])
  • step4 獲取框架名以及方法
frame_app = sys.argv[2].split(':')
frame_name = frame_app[0]
app_name = frame_app[1]
  • step5 導入web_frame框架下的application方法對象
sys.path.append(config["dynamic_path"])
frame = __import__(frame_name) #__import__方法以變量的值去導入模塊 返回對象
app = getattr(frame, app_name) # getattr返回函數
  • step6  在建立WSGIServer實例對象時須要傳遞三個參數(端口號,application函數引用,靜態文件路徑)
WSGIServer(port, app, config['static_path']).run_forever()
  • step7 同時在WSGIServer類中須要接收這三個參數 ,在類中將./static以及mini_frame.application進行對應修改以下

在__init__方法中

def __init__(self, port, app, static_path):
    self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    self.tcp_server_socket.bind(("", port))

    self.tcp_server_socket.listen(128)

    self.application = app

    self.static_path = static_path

 在handle_client方法中

"""遵循WSGI協議"""

# 2.調用框架中的application方法
env = dict()
# print(file_name) /index.py
env["PATH_INFO"] = file_name #{'PATH_INFO':'/index.py'}
env["page_num"] = 1
body = self.application(env, self.start_response)
# print(body, env)
# print(body, "5555555555")
server_response_header = "HTTP/1.1 %s \r\n" % self.status
for temp in self.header:
    server_response_header += '%s:%s\r\n' %(temp[0], temp[1])
server_response_header += "\r\n"
# server_response_body = "<h1 style='color:red;text-align:center'>這是動態資源 %s </h1>" % time.ctime()
server_response_data = server_response_header + body
client_socket.send(server_response_data.encode("utf-8"))
  • step8 在Terminal終端中輸入:python3 web_server.py 7979 web_frame:application成功運行項目,在瀏覽器中訪問http://127.0.0.1:7979/index.html成功

四丶使用python3開發一個輕量級的Web框架

1.在web_frame.py框架文件中,使用裝飾器完成路由功能

  • step1 定義通用裝飾器
URL_FUNC_DICT = dict()
def route(url):
    def set_func(func):
        # URL_FUNC_DICT["/index.html"] = index
        URL_FUNC_DICT[url] = func
        def call_func(*args, **kwargs):
            return func(*args, **kwargs)
        return call_func
    return set_func
  • step2 在index函數和center函數上添加裝飾器
@route("/index.html")
def index():
    with open("./templates/index.html",encoding="utf-8") as f:
        content = f.read()
    return content

@route("/center.html")
def center():
    with open("./templates/center.html") as f:
        return f.read()
  • step3 修改application方法中的代碼
def application(env, start_response):
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
    
    file_name = env['PATH_INFO']
    try:
        return URL_FUNC_DICT[file_name]()
    except Exception as ret:
        return "產生了異常:%s" % str(ret)

2.讓路由支持正則匹配規則

  • step1 修改application函數中的代碼,經過遍歷URL_FUNC_DICT常量,獲取url地址以及對應的方法名,經過正則匹配服務器傳過來的用戶請求的文件名,匹配成功則將該調用此函數
for url, func in URL_FUNC_DICT.items():
    # {
    #   r"/index.html":index,
    #   r"/center.html":center,
    # }
    ret = re.match(url, file_name)
    if ret:
        return func()
else:
    return "請求的url(%s)沒有對應的函數...." % file_name
  • step2 將對應的index和center函數裝飾器修改成正則表達式
@route(r"/index.html")
def index():
    with open("./templates/index.html",encoding="utf-8") as f:
        content = f.read()
    return content

@route(r"/center.html")
def center():
    with open("./templates/center.html") as f:
        return f.read()

3.將數據庫中的數據顯示到index.html以及center.html網頁中

  • step1 由於須要從數據庫中查詢數據,因此須要鏈接數據庫,博主這裏定義一個connect_db方法,用做於鏈接數據操做,在這個方法進行判斷,若是是對數據庫的查詢操做則不須要commit提交操做,其餘的增刪改都是須要提交
def connect_db(sql):
    conn = connect(host="localhost",port=3306,user="root",password="mysql",database="stock_db",charset="utf8")
    cs = conn.cursor()
    cs.execute(sql)
    info_data = cs.fetchall()
    if sql.startswith("select"):
        conn.close()
        cs.close()
    else:
        conn.commit()
        cs.close()
    return info_data
  • step2 在index函數中調用connect_db方法獲取info股票信息表全部數據,定義一個info_data模板數據,經過遍歷info表數據,向html_data空字符串中追加info_data模板所須要info表字段內容數據,最後經過正則匹配將index.html文件中{%content%}數據替換成拼接好的html_data數據,最終返回給服務器,服務器再將此body數據,與響應體和響應頭進行拼接最後返回給瀏覽器,最終顯示到頁面上
with open("./templates/index.html", encoding="utf-8") as f:
    content = f.read()
info_data = connect_db("select * from info;")
info_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_data = ""
for temp in info_data:
    html_data += info_template % (temp[0],temp[1],temp[2],temp[3],temp[4],temp[5],temp[6],temp[7],temp[1])  #後面加的temp[1]股票代碼是爲了填充添加列systemidvaule得值
content = re.sub(r"\{%content%\}",html_data,content)
return content
  • step3 在center函數跟index函數大致一致,不一樣的就是查詢語句不一樣以及定義的模板數據不一樣
with open("./templates/center.html", encoding="utf-8") as f:
    content = f.read()
info_data = connect_db("select i.code,i.short,i.chg,i.turnover,i.price,i.highs,f.not_info from info as i inner join focus as f on i.id = f.info_id;")
info_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 aria-hidden="true">⇋</span> 修改 </a>
                    </td>
                    <td>
                        <input type="button" value="刪除" id="toDel" name="toDel" systemidvaule="%s">
                    </td>
                </tr>
                """
html_data = ""
for temp in info_data:
    html_data += info_template % (temp[0],temp[1],temp[2],temp[3],temp[4],temp[5],temp[6],temp[0],temp[0])
content = re.sub(r"\{%content%\}",html_data,content)
return content

4.將主頁index.html數據進行分頁顯示

  • step1 首先在index.html中給網頁下方預留一個顯示頁碼的位置{%page%}

  • step2 在框架index函數中定義一個頁碼模板
page_template = """
            <div class="pagenation">
                <a href="#" id="up">上一頁</a>
                <a href="/index.html" class="active">1</a>
                <a href="/index.html?page=2">2</a>
                <a href="/index.html?page=3">3</a>
                <a href="/index.html?page=4">4</a>
                <a href="/index.html?page=5">5</a>
                <a href="/index.html?page=6">6</a>
                <a href="/index.html?page=7">7</a>
                <a href="/index.html?page=8">8</a>
                <a href="/index.html?page=9">9</a>
                <a href="#" id="next">下一頁</a>
            </div>
            """
  • step3 替換index.html中的{%page%}
content = re.sub(r"\{%page%\}",page_template,content)
  • step4 編寫頁碼CSS樣式
<style type="text/css">
     .pagenation{height:32px;text-align:center;font-size:0;margin:30px auto;}
     .pagenation a{display:inline-block;border:1px solid #d2d2d2;background-color:#f1e7f8;font-size:12px;padding:7px 10px;color:#666;margin:5px;text-decoration: none;}
     .pagenation .active{background-color:#fff;color:#43a200}
     .pagenation span{display: inline-block;font-size: 12px;}
     .pagenation a:hover{background-color: #9bb797}
 </style>
  • step5 刷新網頁,查看頁碼顯示樣式

  • step6 回到web_server.py服務器文件中,匹配並獲取頁碼鏈接地址中的數字,並保存到env字典中的page_num Key中
page_num = re.match(r".*page=(\d+)",request_lines[0])
if page_num:
    page_num = page_num.group(1)
    # print(type(page_num))
    if file_name.endswith("page=%s" % page_num):
        env = dict()
        # print(file_name) /index.py
        env["PATH_INFO"] = "/index.html"  # {'PATH_INFO':'/index.py'}
        env["page_num"] = page_num
        body = self.application(env, self.start_response)
  • step7 在框架文件application方法中須要獲取用戶請求的頁數,並傳遞給index函數,由於這裏使用了路由正則,因此center函數也必須定義一個形參,用不用無所謂,否則程序會報錯
def application(environ, start_response):
    file_name = environ['PATH_INFO']
    page_num = environ["page_num"]
    start_response('200 OK', [('Content-Type', ' text/html;charset=utf-8')])
    for url, func in URL_FUNC_DICT.items():
        ret = re.match(url, file_name)
        # print(ret,"wwwwwwwwwwwwww")
        if ret:
            # print(ret.group(1))
            return func(page_num)
    else:
        return "請求的url(%s)地址不存在..." % file_name
  • step8 在index函數中進行以下修改,獲取info表總共有多少條數據,按照每頁12條數據進行分頁,設定當某一頁數據大於總數據量時,則返回提示給用戶
page_num = int(page_num)
with open("./templates/index.html", encoding="utf-8") as f:
    content = f.read()
total_page = connect_db("select count(*) from info;")

if (page_num-1)*12 > total_page[0][0]:
    html_data = "<h1 style='color:red'>沒有更多的數據了.....</h1>"
    content = re.sub(r"\{%content%\}", html_data, content)
    content = re.sub(r"\{%page%\}", "", content)
    return content
info_data = connect_db("select * from info limit %d,%d;" %((page_num-1)*12,12))
  • step9 在js中進行邏輯編寫,當前頁爲第一頁時不顯示上一頁,當前頁爲最後一頁時不顯示下一頁,當點擊某頁時,此頁面沒法點擊,而且該頁碼爲激活狀態,JS代碼不予展現

  • step10 對比上圖於數據庫分頁查詢一致

五丶在框架中實現添加股票功能

1.在index.html中編寫如下JS,點擊添加按鈕則向http://127.0.0.1:7979/add/code.html地址發送請求,其中code爲股票代碼,經過js中的attr方法獲取模板中systemIdvalue的值也就是股票代碼

<script type="text/javascript">
        $(document).ready(function(){
        	 $("input[name='toAdd']").each(function(){
                    var currentAdd = $(this);
                    var code = $(this).attr("systemIdVaule");          
                    currentAdd.click(function(){
                        code = $(this).attr("systemIdVaule");
                        // alert("/add/" + code + ".html");
                        $.get("/add/" + code + ".html", function(data, status){
                            alert("數據: " + data + "\n狀態: " + status);
                            window.location.reload();
                        });
                    });
                });
    });

2.定義add_focus方法,用於數據庫處理操做,在js中經過ajax方法向http://127.0.0.1:7979/add/code.html地址發送請求,因此在application函數中須要將正則匹配成功後的對象,傳遞到add_foucs函數中,由於以前在作分頁時,傳遞了頁碼參數,因此該函數須要定義兩個形參來接收

@route(r"/add/(\d+)\.html")
def add_focus(page_num, ret):
    pass

3.獲取股票代碼,並經過股票代碼的值向數據庫中focus表中添加此股票數據

# 1. 獲取添加按鈕觸發的ajax請求地址中的股票代碼
stock_code = ret.group(1)
# print(stock_code)
# 2.判斷獲取到的股票代碼是否存在
res = connect_db("select * from info where code = '%s'" % stock_code)
if not res:
    return "非法請求<This stock does not exist>"
# 3.判斷該股票是否已經關注
res = connect_db("select * from info as i inner join focus as f on i.id = f.info_id where i.code = '%s'" % stock_code)
if res:
    return "該股票已經關注過了,請勿重複關注...."
# 4.關注股票
connect_db("insert into focus(info_id) select id from info where code = '%s'" % stock_code)
return "關注股票成功"

4.測試點擊第一支股票進行添加,顯示關注成功,查看數據庫focus表最後一條數據爲info_id爲1的,肯定添加成功

5.博主這裏以爲當某支股票添加成功後,在該股票信息上不該該出現添加的功能按鈕,因此須要在數據庫info表中添加一個字段is_add字段,默認爲0(未添加)反之爲1,添加成功後將focus表中的股票在info表中將is_add字段修改成1

6.在index函數中將input標籤中的id字段的值修改成toAdd_%s,並在最後面添加isadd字段,之因此要將id字段的值修改成非相同的值,是由於id不容許重複

info_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_%s" name="toAdd" systemidvaule="%s" isadd=%s>
                </td>
            </tr>
            """
html_data += info_template % (temp[0],temp[1],temp[2],temp[3],temp[4],temp[5],temp[6],temp[7],temp[1],temp[1],temp[8])

7.在add_focus函數中在添加股票信息到focus以後須要將該股票對應的info表的is_add字段的值修改成1

connect_db("update info set is_add = 1 where code = '%s'" % stock_code)

8.回到index.html文件中js中,獲取模板中isadd的值,若是該值爲1表示已經關注,因此設置添加按鈕不顯示

var is_add = $(this).attr("isadd");
if(is_add == 1){
    document.getElementById("toAdd_"+code).style.display = "none"
}

9.進行添加測試,當點擊添加成功後,如不顯示添加按鈕表示成功

六丶在框架中實現刪除股票功能

1.在index頁面添加股票成功後,隨後點擊進入我的中心center頁面,則顯示全部關注的股票信息,包括剛纔關注的股票

2.在我的中心頁面中,每支股票都有一個刪除按鈕,當用戶點擊刪除按鈕後則在此頁面中再也不顯示出該支股票,對應咋數據庫focus表中該支股票也直接被物理刪除

  • step1 在center.html文件中編寫以下JS,這個跟添加功能中index.html文件中的JS幾乎如出一轍,當用戶點擊刪除按鈕後,則獲取模板文件中systemIdValue的值,這個值就是在center函數中使用字符串替換的股票代碼的值,而後向http://127.0.0.1:7979/del/000036.html地址發送請求,最終打印響應數據
<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("數據: " + data + "\n狀態: " + status);
                         window.location.reload()
                    });

                });
            });
    });
</script>
  • step2 定義視圖函數del_focus,用於完成數據庫focus表物理刪除操做,正則匹配獲取股票請求地址中的股票代碼
@route(r"/del/(\d+)\.html")
def del_focus(page_num, ret):
    pass
  • step3 獲取股票代碼,判斷股票是否存在以及判斷股票是不是關注的股票,最後根據股票代碼刪除數據庫focus表數據,而且根據該股票代碼修改對應info表中is_add的值爲0,使這支股票被取消關注後顯示在股票信息表上
# 1. 獲取添加按鈕觸發的ajax請求地址中的股票代碼
stock_code = ret.group(1)
# print(stock_code)
# 2.判斷獲取到的股票代碼是否存在
res = connect_db("select * from info where code = '%s'" % stock_code)
if not res:
    return "非法請求<This stock does not exist>"
# 3.判斷該股票是否已經關注,未關注表示非法刪除請求
res = connect_db("select * from info as i inner join focus as f on i.id = f.info_id where i.code = '%s'" % stock_code)
if not res:
    return "該股票未關注...."
# 4.取關股票,並修改info表中is_add字段爲0表示在頁面顯示添加按鈕
connect_db("delete from focus where info_id = (select id from info where code = '%s')" % stock_code)
connect_db("update info set is_add = 0 where code = '%s'" % stock_code)
return "取關股票成功"

3.進行測試

  • step1 刷新網頁,點擊最後一個000050代碼的股票進行刪除測試

  • step2  回到股票信息頁面,查看000050是否有添加功能,有表示邏輯正確

  • step3 查看數據focus表是否沒有該只股票,以及info表該只股票is_add字段是否爲0

 七丶在框架中實現修改股票功能

1.在templates模板目錄下建立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>首頁 - 我的選股系統 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>
  </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">選股系統</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>

2.在update.html文件中編寫以下js,當用戶點擊修改按鈕,首先當用戶在我的中心頁面對股票點擊修改時,向http://127.0.0.1:7979/update/300320.html地址發送請求進入到修改界面,而後在修改界面點擊修改時,向http://127.0.0.1:7979/update/000581/備註信息.html地址發送請求,獲取響應內容最後跳轉到我的中心網頁

<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("數據: " + data + "\n狀態: " + status);
                self.location='/center.html';
            });
        });
    });
</script>

3.在框架文件中建立show_update_page視圖函數,用於顯示修改備註信息頁面

  • step1 在center函數中nfo_template模板文件中修改按鈕使用超連接進行跳轉,沒有使用ajax方式發送請求
<a type="button" class="btn btn-default btn-xs" href="/update/%s.html"> <span aria-hidden="true">⇋</span> 修改 </a>
  • step2 在show_update_page視圖函數中編寫以下代碼,從路由匹配成功後獲取到要修改的股票代碼,根據該股票代碼獲取focus表中的備註信息,而後將此備註信息替換update.html中的{%note_info%}字段,以及將獲取的股票代碼替換update.html文件中的{%code%}字段,最後返回給服務器
@route(r"/update/(\d+)\.html")
def show_update_page(page_num, ret):
    """顯示修改界面"""
    # 1.讀取修改界面代碼
    with open("./templates/update.html", encoding="utf-8") as f:
        content = f.read()
    # 2. 獲取要修改的股票代碼
    stock_code = ret.group(1)
    # 3. 獲取該關注股票的備註信息
    res = connect_db("select f.not_info from info as i inner join focus as f on i.id = f.info_id where i.code = '%s'" % stock_code)
    not_info = res[0][0]
    # print(not_info, "wwwwwwwwwwwww")
    # 4. 替換html中的空缺兩次依次替換
    content = re.sub(r"\{%note_info%\}", not_info, content)
    content = re.sub(r"\{%code%\}", stock_code, content)
    return content
  • step3 測試,在我的中心頁面點擊股票修改按鈕,是否成功跳轉到修改頁面,並在修改頁面顯示正確的股票代碼以及該只股票的備註信息

4.在框架文件中定義save_update_data視圖函數用於保存股票修改後的備註信息

  • step1 在sava_update_data視圖函數中進行以下編寫,須要注意的是路由規則中第二個參數爲用戶修改的備註信息,因此這裏須要使用(.*)來進行匹配
@route(r"/update/(\d+)/(.*)\.html")
def save_update_data(page_num, ret):
    """將用戶修改的備註信息保存到數據庫"""
    # 1. 獲取股票代碼以及備註信息
    stock_code = ret.group(1)
    comment = ret.group(2)
    # 2. 因以前添加關注功能對股票信息進行了驗證,因此在我的中心頁面的已關注的股票信息爲合法信息,因此直接寫入數據庫便可
    connect_db("update focus set not_info = '%s' where info_id = (select id from info where code = %s)" %(comment, stock_code))
    return """修改備註信息成功"""
  • step2 修改備註信息測試,在我的中心頁面中點擊最後一條股票002311,進行修改,在修改頁面輸入hahhahahhaha後,點擊修改提示修改爲功,並跳轉顯示到我的中心頁面,說明代碼邏輯正確

  • step3 查看focus表數據,已成功在數據庫進行對應修改

  • step4 有個bug就是當用戶輸入中文時,則提示亂碼,對剛纔那支股票繼續進行修改,輸入(好好好 )這三個字,點擊修改按鈕後,提示修改爲功,但跳轉到我的中心頁面則顯示亂碼,

  •  step5 以上出現亂碼是由於當瀏覽器向服務器發送請求數據時,會對數據進行url編碼,因此填完備註信息後直接保存到數據庫的數據時進行url編碼後的數據,那麼此時就須要在數據保存到數據庫以前,進行url解碼操做,須要導入python庫中urllib.parse模塊,在save_update_data函數中添加如下代碼
comment = urllib.parse.unquote(comment)
  • step6 再次進行測試,bug已解

5.這個項目有不少須要優化以及不足的地方,全部的Web項目大同小異都是這個流程,代碼不重要,重要的是明白瀏覽器服務器以及程序框架之間數據是怎麼傳遞的

相關文章
相關標籤/搜索