当前位置:   article > 正文

Python之简易Web框架搭建_python搭建web网站

python搭建web网站


Web框架介绍

我们已经知道web服务器主要的作用是,接收用户的http请求,根据用户的请求返回不同的资源数据。但是之前我们学习的都是静态web服务器,返回的都是静态资源数据

静态资源:不需要经常变化的资源,这种资源web服务器可以提前准备好,如: html页面模板、png、jpg、css、js等文件。)

动态资源:html页面模板+数据

为了让服务器返回动态html页面文件,我们需要一个Web框架

web框架专门负责处理用户的动态资源请求,根据数据库更新页面数据,将模板+数据组合成动态网页返回。

Web框架其实就是一个为web服务器提供服务的应用程序。

在这里插入图片描述

WSGI协议

WSGI全称Web Server Gateway Interface(Web服务器网关接口),或Python Web Server Gateway Interface

它是web服务器和web框架(或Python应用程序)之间进行协同工作的一个接口协议。

WSGI协议规定,web服务器需把动态资源的http请求报文转化WSGI协议格式,再传给web框架进行处理。

web框架处理好请求之后,再把html文件与数据进行组装,返回给web服务器。

web服务器得到动态页面的结果后,再封装http响应报文,返回给浏览器。


Web框架开发

项目结构

在这里插入图片描述

  • static内含css,fonts,image,js,html等静态文件
  • MyWebServer.py提供服务器程序
  • framework.py提供web框架程序
  • template内提供模板html文件
MyWebServer.py
之前的静态服务器代码
import socket
import threading


class MyWebServer(object):
    def __init__(self, port):
        """初始化:创建套接字"""
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        self.server_socket.bind(("localhost", port))
        self.server_socket.listen(128)

    def start(self):
        """启动:建立连接,并开启子线程"""
        while True:
            new_socket, address = self.server_socket.accept()
            print("已连接", address, sep=" from ")

            sub_thread = threading.Thread(target=self.handle, args=(new_socket,), daemon=True)
            sub_thread.start()

    @staticmethod
    def handle(handle_socket):
        """利用子线程收发http格式数据"""
        request_data = handle_socket.recv(4096)

        if len(request_data) == 0:
            print("浏览器已断开连接...")
            handle_socket.close()
            return

        request_content = request_data.decode("utf-8")
        print(request_content)
        # 以\r\n分割各项信息
        request_list = request_content.split("\r\n")
        # 提取请求的资源路径
        request_line = request_list[0]
        request_line_list = request_line.split(" ")
        request_method, request_path, request_version = request_line_list

        # 首页
        if request_path == "/":
            request_path = "/index.html"

        # 响应行与响应头信息置空
        response_line = response_header = ""

        # 根据请求路径准备好响应行和响应体
        try:
            with open("." + request_path, "rb") as request_file:
                response_body = request_file.read()

        except (FileExistsError, FileNotFoundError):
            response_line += f"{request_version} 404 Not Found\r\n"
            with open("./error.html", "rb") as request_file:
                response_body = request_file.read()

        else:
            response_line += f"{request_version} 200 OK\r\n"

        finally:
            # 准备好响应头信息
            response_header += "Server: MyWebServer2.0\r\n"
            # 向浏览器发送响应报文
            response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
            handle_socket.send(response_data)
            # 断开与浏览器的连接
            handle_socket.close()


def main():
    port = 8888
    my_web_server = MyWebServer(port)
    my_web_server.start()


if __name__ == "__main__":
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
WSGI协议的要求

如果请求的资源是html文件,我们就可以认为这是动态资源请求。需要把这个请求封装成WSGI协议要求的格式,并传给web框架处理。

WSGI协议要求服务器将请求报文的各项信息组合成一个字典environ,再传给web框架

environ={
    "request-path":"..."  #请求路径
    #...(以键值对形式传传入其他请求头信息)
}
  • 1
  • 2
  • 3
  • 4

为了追求简单,我们就这里就只传请求路径

更新代码

暂时先忽略framework的实现,我们假设在framework.py中,有一个Framework类。

更新服务器的初始化代码

from framework import FrameWork

def __init__(self, port):
        """初始化:创建套接字;初始化web框架"""
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        self.server_socket.bind(("localhost", port))
        self.server_socket.listen(128)
        
        self.framework=Framework()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

self.framework负责处理动态资源请求(主要是html)。

如果请求的是html文件,就将请求信息分解为environ字典,并传给self.frameworkhandle_request处理。

处理完成后,web框架需要返回响应状态(status),响应头信息(headers,元组列表形式),响应体数据(response_body)。

服务器再根据这些信息拼接成http响应报文返回给浏览器。

如果请求的不是html文件,就让服务器行使其静态功能。

		#........        
    	if request_path == "/":
            request_path = "/index.html"

        # 动态资源html交给web框架处理
        if request_path.endswith(".html"):
            environ = dict()
            environ["request_path"] = request_path
            status, headers, response_body = self.framework.handle_request(environ)

            response_line = f"{request_version} {status}\r\n"

            response_header = ""
            for header in headers:
                response_header += "%s: %s\r\n" % header
            response_data = (response_line + response_header + "\r\n" + response_body).encode("utf-8")
            handle_socket.send(response_data)
            # 断开与浏览器的连接
            handle_socket.close()

        # 静态资源静态web服务器处理
        else:
            response_line = response_header = ""
			.......
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
framework.py

前面我们看到,framework的主要功能就是接受environ请求字典,返回对应响应状态(status),响应头信息(headers,元组形式),响应体数据(response_body)。

返回时间

第一步,为了寻求简单,我们就让请求index.html时返回现在的时间,请求其他网页时404 not Found

import time


class FrameWork(object):

    @staticmethod
    def __index():
        """主页处理函数"""
        # 响应状态
        status = "200 OK"
        # 响应头信息
        response_header = [("Server", "MyWebServer2.0")]
        # 处理后的数据
        response_data = time.ctime()

        return status, response_header, response_data

    @staticmethod
    def __not_found():
        """未找到资源处理函数"""
        # 响应状态
        status = "404 Not Found"
        # 响应头
        response_header = [("Server", "PWS2.0")]
        # 处理后的数据
        response_data = "not found"

        return status, response_header, response_data

    def handle_request(self, environ):
        """framework框架主处理函数"""
        # 获取动态请求资源路径
        request_path = environ["request_path"]

        if request_path == "/index.html":
            # 获取首页数据
            result = self.__index()
            return result
        else:
            # 没有找到动态资源
            result = self.__not_found()
            return result

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

在这里插入图片描述

模板文件

动态html=静态html(模板)+数据库数据

在html中,我们可以用{%content%}这种形式的变量来标识需要动态替换的数据。这样的html,就可以称为模板。

如:

<!--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>            

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
用数据替换模板文件变量

我们可以用数据库中的数据更换html中的{%content%}变量。

为了简单,这里直接用时间代替数据库数据来试试效果。

@staticmethod
    def __index():
        """主页处理函数"""
        # 响应状态
        status = "200 OK"
        # 响应头信息
        response_header = [("Server", "MyWebServer2.0")]
        # 处理后的数据
        with open("../template/index.html", "r",encoding="utf-8") as file:
            file_data = file.read()
        time_data = time.ctime()
        response_data = file_data.replace("{%content%}", time_data)

        return status, response_header, response_data
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

在这里插入图片描述

路由列表

目前我们只有处理请求index.html的函数。为了处理请求center.html的函数,我们可以再写一个center函数。

	@staticmethod
    def __center():
        """个人中心处理函数"""
        # 响应状态
        status = "200 OK"
        # 响应头信息
        response_header = [("Server", "MyWebServer2.0")]
        # 处理后的数据
        with open("../template/center.html", "r", encoding="utf-8") as file:
            file_data = file.read()
        time_data = time.ctime()
        response_data = file_data.replace("{%content%}", time_data)

        return status, response_header, response_data
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
    def handle_request(self, environ):
        """framework框架主处理函数"""
        # 获取动态请求资源路径
        request_path = environ["request_path"]

        if request_path == "/index.html":
            # 获取首页数据
            result = self.__index()
            return result
        elif request_path == "/center.html":
            # 获取首页数据
            result = self.__center()
            return result
        else:
            # 没有找到动态资源
            result = self.__not_found()
            return result
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

但这样的话,handle_request的代码未免太冗杂了。尤其是页面很多的情况下。

我们可以创建一个路由列表,它由二元组组成,二元组代表(页面请求路径,对应处理函数)的映射。

	def __init__(self):
        self.route_list=[
            ("/index.html",self.__index),
            ("/center.html",self.__center)
        ]
  • 1
  • 2
  • 3
  • 4
  • 5
    def handle_request(environ):
        """framework框架主处理函数"""
        request_path = environ["request_path"]

        # 遍历路由列表,选择执行的函数
        for path, func in self.route_list:
            if request_path == path:
                result = func()
                return result
        else:
            # 没有找到动态资源
            result = self.__not_found()
            return result
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

这样的话,代码就简洁多了,再多页面也是一样。

我们只需创建处理不同页面请求的函数,并将请求路径与处理函数添加到路由列表中即可。

路由装饰器

通过含参装饰器的相关知识,我们可以实现,在定义处理函数的时候,自动向路由列表内添加二元映射。

route_list=[]

def route(request_path):
    def decorator(func):
        route_list.append((request_path, func))  #这里在@时自动执行

        def inner(func, *args, **kwargs):
            result = func(*args, **kwargs)
            return result

        return inner

    return decorator
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
class FrameWork(object):
    def __init__(self):
        self.route_list = route_list    #由于是可变对象,self.route_list和全局变量route_list保持一致
  • 1
  • 2
  • 3
    @staticmethod
    @route("/index.html")   #自动添加映射("./index.html",Framework.__index)
    def __index():
        """主页处理函数"""
        #...
        
    @staticmethod
    @route("/center")
    def __center():
        """主页处理函数"""
        #...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
利用数据库显示股票信息
create database gupiao;
use database gupiao;

CREATE TABLE `info` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `code` varchar(6) NOT NULL COMMENT '股票代码',
  `short` varchar(10) NOT NULL COMMENT '股票简称',
  `chg` varchar(10) NOT NULL COMMENT '涨跌幅',
  `turnover` varchar(255) NOT NULL COMMENT '换手率',
  `price` decimal(10,2) NOT NULL COMMENT '最新价',
  `highs` decimal(10,2) NOT NULL COMMENT '前期高点',
  `time` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=95 DEFAULT CHARSET=utf8;


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

CREATE TABLE `focus` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `note_info` varchar(200) DEFAULT '',
  `info_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `info_id` (`info_id`),
  CONSTRAINT `focus_ibfk_1` FOREIGN KEY (`info_id`) REFERENCES `info` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8

INSERT INTO `focus` VALUES (2,'你确定要买这个?',36),(3,'利好',37),(9,'',88),(10,'',89),(13,'',1);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

以上sql语句建立了gupiao数据库和info,focus表

我们可以利用python的pymysql模块,查询数据库并用查询结果替换{%content%}

import pymysql

#......

	@staticmethod
    @route("/index.html")
    def __index():
        """主页处理函数"""
        # 响应状态
        status = "200 OK"
        # 响应头信息
        response_header = [("Server", "MyWebServer2.0")]
        # 处理后的数据
        with open("../template/index.html", "r", encoding="utf-8") as file:
            file_data = file.read()

		db_connect = pymysql.connect(host="localhost",
                                     port=3306,
                                     user="root",
                                     password="123",
                                     database="gupiao",
                                     charset="utf8")

        cursor = db_connect.cursor()
        sql = "select * from info;"
        cursor.execute(sql)
        result = cursor.fetchall()
        
        data = ""
        for row in result:
            data += '''<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="000007">							</td>
                       </tr>
                    ''' % row

        # 替换模板文件中的模板变量
        response_data = file_data.replace("{%content%}", data)

        return status, response_header, response_data

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

在这里插入图片描述

框架为客户端提供个人数据接口

网页开发向来注重前后端分离。

后端提供数据支持,前端负责显示数据。

web框架程序可以开发数据接口,为浏览器提供数据服务。

数据接口一般是json文件

当请求路径是/center_data.html时,返回一个json字符串,为浏览器提供数据接口。

浏览器获得数据接口后,可利用Ajax进行局部刷新。

	@staticmethod
    @route("/center_data.html")
    def center_data():
        """个人中心数据接口开发"""
        status = "200 OK"
        # 响应头
        response_header = [("Server", "MyWebServer2.0"), ("Content-Type", "text/html;charset=utf-8")]
        db_connect = pymysql.connect(host="localhost",
                               port=3306,
                               user="root",
                               password="123",
                               database="gupiao",
                               charset="utf8")

        cursor = db_connect.cursor()
        sql = '''
                    select 
                    info.code, info.short, info.chg, 
                    info.turnover,info.price,info.highs, info.note_info 
                    from info inner join focus
                    on info.id = focus.info_id;
            '''
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        db_connect.close()

        # 个人中心数据列表
        center_data_list = list()
        # 遍历每一行数据转成字典
        for row in result:
            center_dict = dict()
            center_dict["code"] = row[0]
            center_dict["short"] = row[1]
            center_dict["chg"] = row[2]
            center_dict["turnover"] = row[3]
            center_dict["price"] = str(row[4])
            center_dict["highs"] = str(row[5])
            center_dict["note_info"] = row[6]
            center_data_list.append(center_dict)

        # 把列表字典转成json字符串
        json_str = json.dumps(center_data_list, ensure_ascii=False)
        
        return status, response_header, json_str
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

在这里插入图片描述

Ajax获取个人中心接口并渲染页面

一开始我们能够获取到center.html,但这个页面中没有数据。

为了获取数据,我们可以在center.html中加入Ajax代码,让浏览器去请求center_data.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(){
            // 发送ajax请求,获取个人中心数据
            $.get("center_data.html",function (center_data) {

                // 获取table标签
                var $table = $(".table");
                // 如果指定了返回数据的解析方式是json,那么data就是一个js对象
                for(var i = 0; i < center_data.length; i++){

                    //获取每一行的js对象
                    var $row = center_data[i];
                    // 准备每一行数据
                    var $rowStr = '<tr>' +
                                '<td>'+ $row.code +'</td>' +
                                '<td>'+ $row.short +'</td>' +
                                '<td>'+ $row.chg +'</td>' +
                                '<td>'+ $row.turnover +'</td>' +
                                '<td>'+ $row.price +'</td>' +
                                '<td>'+ $row.highs +'</td>' +
                                '<td>'+ $row.note_info +'</td>' +
                                '<td><a type="button" class="btn btn-default btn-xs" href="/update/000007.html"> <span class="glyphicon glyphicon-star" aria-hidden="true"></span> 修改 </a></td>' +
                                '<td><input type="button" value="删除" id="toDel" name="toDel" systemidvaule="000007"></td>' +
                                '</tr>'
                    //table元素内追加数据
                    $table.append($rowStr)

                }
            }, "json");

            $("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="./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 style="color:red">备注信息</th>
                    <th>修改备注</th>
                    <th>del</th>
            </tr>
            {%content%}
        </table>
    </div>
</div>
</body>
</html>            

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
日志logging

日志信息可以记录服务器的运行状态和信息。

记录程序日志信息的目的
  1. 可以很方便的了解程序的运行情况
  2. 可以分析用户的操作行为、喜好等信息
  3. 方便开发人员检查bug,调试程序
logging日志的等级

以下等级依次提高

  • DEBUG:程序调试bug时使用
  • INFO:程序正常运行时使用
  • WARNING:程序未按预期运行时使用,但并不是出现错误,如:用户登录密码错误
  • ERROR:程序出现错误时使用
  • CRITICAL:特别严重的问题,导致程序不能再继续运行时使用,如:磁盘空间为空,一般很少使用
logging包的使用
import logging

logging.debug('调试中...')
logging.info('正常运行中...')
logging.warning('警告!')
logging.error('出现错误,继续运行...')
logging.critical('出现错误,程序奔溃!')

"""
WARNING:root:警告!
ERROR:root:出现错误,继续运行...
CRITICAL:root:出现错误,程序奔溃!
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 默认的日志等级为WARNING.日志信息只显示了大于等于日志等级的日志.
设置日志格式
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
  • 1
  • 2
  • level是日志等级
  • format是日志格式
  • %(levelname)s: 打印日志级别名称
  • %(filename)s: 打印当前执行程序名
  • %(lineno)d: 打印日志的当前行号
  • %(asctime)s: 打印日志的时间
  • %(message)s: 打印日志信息
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
logging.debug('调试中...')
logging.info('正常运行中...')
logging.warning('警告!')
logging.error('出现错误,继续运行...')
logging.critical('出现错误,程序奔溃!')
"""
2022-06-29 16:09:47,604 - web_logging.py[line:5] - DEBUG: 调试中...
2022-06-29 16:09:47,604 - web_logging.py[line:6] - INFO: 正常运行中...
2022-06-29 16:09:47,604 - web_logging.py[line:7] - WARNING: 警告!
2022-06-29 16:09:47,604 - web_logging.py[line:8] - ERROR: 出现错误,继续运行...
2022-06-29 16:09:47,604 - web_logging.py[line:9] - CRITICAL: 出现错误,程序奔溃!
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
设置日志文件
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
                   	filename="log.txt",
                    filemode="w")
  • 1
  • 2
  • 3
  • 4
  • filename是日志保存到的文件
  • filemode是保存模式

在这里插入图片描述

为服务器添加日志
import socket
import threading
import logging
from framework import FrameWork

# logging日志的配置
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
                    filename="log.txt",
                    filemode="w")


class MyWebServer(object):
    def __init__(self, port):
        """初始化:创建套接字;初始化web框架"""
        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
        self.server_socket.bind(("localhost", port))
        self.server_socket.listen(128)

        self.framework = FrameWork()

    def start(self):
        """启动:建立连接,并开启子线程"""
        while True:
            new_socket, address = self.server_socket.accept()
            logging.info(f"接受来自{address}的连接")

            sub_thread = threading.Thread(target=self.handle, args=(new_socket,), daemon=True)
            sub_thread.start()

    def handle(self, handle_socket):
        """利用子线程收发http格式数据"""
        request_data = handle_socket.recv(4096)

        if len(request_data) == 0:
            logging.info("浏览器已断开连接")
            handle_socket.close()
            return

        request_content = request_data.decode("utf-8")
        print(request_content)
        # 以\r\n分割各项信息
        request_list = request_content.split("\r\n")
        # 提取请求的资源路径
        request_line = request_list[0]
        request_line_list = request_line.split(" ")
        request_method, request_path, request_version = request_line_list

        # 首页
        if request_path == "/":
            request_path = "/index.html"

        # 动态资源html交给web框架处理
        if request_path.endswith(".html"):
            logging.info("动态资源请求:" + request_path)
            environ = dict()
            environ["request_path"] = request_path
            status, headers, response_body = self.framework.handle_request(environ)

            response_line = f"{request_version} {status}\r\n"

            response_header = ""
            for header in headers:
                response_header += "%s: %s\r\n" % header
            response_data = (response_line + response_header + "\r\n" + response_body).encode("utf-8")
            handle_socket.send(response_data)
            # 断开与浏览器的连接
            handle_socket.close()

        # 静态资源静态web服务器处理
        else:
            logging.info("静态资源请求:" + request_path)
            response_line = response_header = ""

            # 根据请求路径准备好响应行和响应体
            try:
                with open("." + request_path, "rb") as request_file:
                    response_body = request_file.read()

            except (FileExistsError, FileNotFoundError):
                response_line += f"{request_version} 404 Not Found\r\n"
                with open("./error.html", "rb") as request_file:
                    response_body = request_file.read()

            else:
                response_line += f"{request_version} 200 OK\r\n"

            finally:
                # 准备好响应头信息
                response_header += "Server: MyWebServer2.0\r\n"
                # 向浏览器发送响应报文
                response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
                handle_socket.send(response_data)
                # 断开与浏览器的连接
                handle_socket.close()


def main():
    port = 8888
    my_web_server = MyWebServer(port)
    my_web_server.start()


if __name__ == "__main__":
    main()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
import time
import pymysql
import json
import logging

route_list = []


def route(request_path):
    def decorator(func):
        route_list.append((request_path, func))

        def inner(func, *args, **kwargs):
            result = func(*args, **kwargs)
            return result

        return inner

    return decorator


class FrameWork(object):
    def __init__(self):
        self.route_list = route_list

    @staticmethod
    @route("/index.html")
    def __index():
        """主页处理函数"""
        # 响应状态
        status = "200 OK"
        # 响应头信息
        response_header = [("Server", "MyWebServer2.0")]
        # 处理后的数据
        with open("../template/index.html", "r", encoding="utf-8") as file:
            file_data = file.read()

        db_connect = pymysql.connect(host="localhost",
                                     port=3306,
                                     user="root",
                                     password="s1234567890",
                                     database="gupiao",
                                     charset="utf8")

        # 获取游标
        cursor = db_connect.cursor()
        # 查询sql语句
        sql = "select * from info;"
        # 执行sql
        cursor.execute(sql)
        # 获取结果集
        result = cursor.fetchall()
        data = ""
        for row in result:
            data += '''<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="000007"></td>
                           </tr>''' % row

        # 替换模板文件中的模板遍历
        response_data = file_data.replace("{%content%}", data)

        return status, response_header, response_data

    @staticmethod
    @route("/center.html")
    def __center():
        """个人中心处理函数"""
        # 响应状态
        status = "200 OK"
        # 响应头信息
        response_header = [("Server", "MyWebServer2.0")]
        # 处理后的数据
        with open("../template/center.html", "r", encoding="utf-8") as file:
            file_data = file.read()
        time_data = time.ctime()
        response_data = file_data.replace("{%content%}", time_data)

        return status, response_header, response_data

    @staticmethod
    def __not_found():
        """未找到资源处理函数"""
        # 响应状态
        status = "404 Not Found"
        # 响应头
        response_header = [("Server", "PWS2.0")]
        # 处理后的数据
        response_data = "not found"

        return status, response_header, response_data



    @staticmethod
    @route("/center_data.html")
    def center_data():
        """个人中心数据接口开发"""
        status = "200 OK"
        response_header = [("Server", "MyWebServer2.0"), ("Content-Type", "text/html;charset=utf-8")]

        conn = pymysql.connect(host="localhost",
                               port=3306,
                               user="root",
                               password="s1234567890",
                               database="gupiao",
                               charset="utf8")

        cursor = conn.cursor()
        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 on i.id = f.info_id;
            '''
        cursor.execute(sql)
        result = cursor.fetchall()
        cursor.close()
        conn.close()

        # 个人中心数据列表
        center_data_list = list()
        # 遍历每一行数据转成字典
        for row in result:
            # 创建空的字典
            center_dict = dict()
            center_dict["code"] = row[0]
            center_dict["short"] = row[1]
            center_dict["chg"] = row[2]
            center_dict["turnover"] = row[3]
            center_dict["price"] = str(row[4])
            center_dict["highs"] = str(row[5])
            center_dict["note_info"] = row[6]
            # 添加每个字典信息
            center_data_list.append(center_dict)

        # 把列表字典转成json字符串
        json_str = json.dumps(center_data_list, ensure_ascii=False)
        return status, response_header, json_str

    def handle_request(self, environ):
        """framework框架主处理函数"""
        # 获取动态请求资源路径
        request_path = environ["request_path"]

        for path, func in route_list:
            if request_path == path:
                result = func()
                return result
        else:
            # 没有找到动态资源
            logging.error("没有设置相应的路由:" + request_path)
            result = self.__not_found()
            return result
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160

在这里插入图片描述


声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号