wsgi-mini-web框架的实现-2

上一节已经介绍了一些关于wsgi的一些相关的知识,这节我们手动实现一下我们的web框架。
首先看一下我的项目结构:


项目结构图.png

其中web_server.py文件是我们的服务器文件,而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服务器的类'''

    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)

        #设定静态资源文件的路径
        self.documents_root = documents_root
        #设定web框架可以调用的函数
        self.app = app  #application


    def startup(self):
        '''运行服务器'''
        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):
        '''
        以短连接的方式,为这个浏览器服务
        注意:如果是长连接,频繁的关闭socket会导致错误:1--->[Errno 9] Bad file descriptor
        while True:
        '''

        rev_data = client_sock.recv(4096)
        if not rev_data:
            print('客户端已经下线了')
            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结尾的文件,都认为是普通的文件
        # 如果是.html结尾的文件,就让web框架进行处理
        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'
                '''注意:不能设置Content-Type,会导致浏览器不能解析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结尾的文件,就认为是浏览器需要动态的页面
        else:
            env = dict(PATH_INFO=file_name)

            # 保存 web返回的数据
            response_body = self.app(env, self.set_response_headers)  # 第2个参数传递的是函数的引用,由web框架来执行。

            # 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默认编码是gbk,不设置成gbk的话浏览器无法解析符号

            client_sock.close()



    def set_response_headers(self,status,headers):
        '''这个方法会在web框架中被默认调用。'''

        # response_header_default = [
        #     ('Data',time.ctime()),
        #     ('Server','ItCast-python mini web server')
        # ]
        #将web框架设置的状态码,头信息存储起来
        # self.headers = [status, response_header_default + headers]

        self.status = status
        self.headers = headers



#设置静态资源访问路径
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("运行方式如: python3 xxx.py 7890 my_web_frame_name:application")
        return

    # print('http服务器使用的端口号是:%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进行解码。

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')  # 相当于执行 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'>暂时没有关注信息,请先关注哦<h4>",content)
            return content

@route(r'/update/(\d*)\.html')  # 底层实现: update = route(r'/update/(\d*)\.html')(update)
def update(file_name, url):
    '''显示更新页面的内容'''
    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):
    '''进行数据的真正更新'''
    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):
    '''添加关注'''
    # print("hahahahahahahaha"*10)

    ret = re.match(url, file_name)
    if ret:
        stock_code = ret.group(1)
        # print('=xxxxxxxxx=',stock_code)

    # 判断是否已经关注
    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 '已经关注了股票%s,请不要重复关注' % stock_code

    # 没有关注,就进行关注
    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 '关注股票%s成功了' % stock_code


@route(r'/del/(\d*)\.html')
def delete(file_name, url):
    '''取消关注'''

    ret = re.match(url, file_name)
    if ret:
        stock_code = ret.group(1)

    cur, conn = connect_mysql()
    # 如果已经关注,就取消关注
    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 '取消关注股票%s成功了' % stock_code


def application(environ, start_response):  # start_response是服务器传过来的函数的引用,由web框架来执行这个函数
    # 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:字典里保存的有正则表达式的字符串,也有普通的路径字符串
        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>首页 - 个人选股系统 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("数据: " + data + "\n状态: " + 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">选股系统</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>个人中心 - 个人选股系统 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("数据: " + data + "\n状态: " + 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">选股系统</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>首页 - 个人选股系统 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("数据: " + data + "\n状态: " + 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">选股系统</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里面的静态css和js文件可以在网上下载。

运行此服务器需要在终端运行,具体运行命令格式如下:
python3 web_server.py 8080 Application:application

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,951评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,606评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,601评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,478评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,565评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,587评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,590评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,337评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,785评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,096评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,273评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,935评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,578评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,199评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,440评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,163评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,133评论 2 352

推荐阅读更多精彩内容