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

猜你喜欢

转载自blog.csdn.net/chenzheng_blog/article/details/80636830