wsgi-mini-web框架的實現(xiàn)-2

上一節(jié)已經(jīng)介紹了一些關(guān)于wsgi的一些相關(guān)的知識宾符,這節(jié)我們手動實現(xiàn)一下我們的web框架鱼填。
首先看一下我的項目結(jié)構(gòu):


項目結(jié)構(gòu)圖.png

其中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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末左驾,一起剝皮案震驚了整個濱河市镣隶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌诡右,老刑警劉巖安岂,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異帆吻,居然都是意外死亡域那,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門猜煮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來次员,“玉大人败许,你說我怎么就攤上這事∈缥担” “怎么了市殷?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長束倍。 經(jīng)常有香客問我被丧,道長,這世上最難降的妖魔是什么绪妹? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任甥桂,我火速辦了婚禮,結(jié)果婚禮上邮旷,老公的妹妹穿的比我還像新娘黄选。我一直安慰自己,他們只是感情好婶肩,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布办陷。 她就那樣靜靜地躺著,像睡著了一般律歼。 火紅的嫁衣襯著肌膚如雪民镜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天险毁,我揣著相機(jī)與錄音制圈,去河邊找鬼。 笑死畔况,一個胖子當(dāng)著我的面吹牛鲸鹦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播跷跪,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼馋嗜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了吵瞻?” 一聲冷哼從身側(cè)響起葛菇,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎橡羞,沒想到半個月后熟呛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡尉姨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年庵朝,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡九府,死狀恐怖椎瘟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侄旬,我是刑警寧澤肺蔚,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站儡羔,受9級特大地震影響宣羊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汰蜘,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一仇冯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧族操,春花似錦苛坚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至枷莉,卻和暖如春娇昙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背笤妙。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工冒掌, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人危喉。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓宋渔,卻偏偏與公主長得像州疾,于是被迫代替她去往敵國和親辜限。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容