一、web框架概述

  • web框架:为web服务器提供服务的应用程序,专门负责处理用户的动态资源请求
  • WSGI协议:web服务器和web框架之间进行协同工作的一个规则,规定web服务器把动态资源的请求信息传给web框架处理,web框架把处理好的结果返回给web服务器
  • 工作流程
    1. web服务器接收浏览器发起的请求,如果是动态资源请求找web框架来处理
    2. web框架负责处理浏览器的动态资源请求,把处理的结果发生给web服务器
    3. web服务器再把响应结果发生给浏览器

img

二、框架程序开发

1. web服务器-动态资源判断

  • 根据请求资源路径的后缀名进行判断,如果请求资源路径的后缀名是.html,则是动态资源请求,让web框架程序进行处理。否则是静态资源请求,让web服务器程序进行处理。
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
import socket
import threading
import sys
import framework


class HttpWebServer(object):
def __init__(self, port):
# 创建tcp服务端套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口号复用, 程序退出端口立即释放
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定 IP 和端口号,不设置 IP 代表本机的任何一个 IP 都可以连接
tcp_server_socket.bind(("192.168.0.101", port))
# 设置监听
tcp_server_socket.listen(128)

self.tcp_server_socket = tcp_server_socket

# 处理客户端请求
@staticmethod
def handle_client_request(new_socket):
recv_client_data = new_socket.recv(4096)
if len(recv_client_data) == 0:
print('关闭浏览器了')
new_socket.close()
return

# 对二进制数据进行解码
recv_client_content = recv_client_data.decode("utf-8")
print(recv_client_content)

# 提取请求资源
request_list = recv_client_content.split(' ', maxsplit=2)
# 获取请求资源路径
request_path = request_list[1]
# print(request_path)

# 判断请求资源是否为根目录
if request_path == '/':
request_path = '/index.html'

if request_path.endswith('.html'):
'''动态资源请求'''
# 动态资源请求找web框架处理,需要把请求参数给web框架
# 准备给web框架的参数信息,都要放到字典里面
env = {
"request_path":request_path
}
# 获取处理结果
status,headers,response_body = framework.handle_request(env)

# 对框架处理的数据拼接响应文
# 响应行
response_line = 'HTTP/1.1 %s\r\n' % status
# 响应头
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')

# 发送数据
new_socket.send(response_data)
# 关闭socket
new_socket.close()



else:
'''静态资源请求'''
try:
# 动态打开指定文件
with open("static" + request_path, "rb") as file:
# 读取文件数据
file_data = file.read()
except Exception as e:
# 请求资源不存在,返回404数据
response_line = 'HTTP/1.1 404 Not Found\r\n'
response_header = 'Server:PWS1.0\r\n'

with open('static/error.html', 'rb') as file:
file_data = file.read()

response_body = file_data
response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
new_socket.send(response_data)
else:
# 响应行
response_line = "HTTP/1.1 200 OK\r\n"
# 响应头
response_header = "Server: PWS1.0\r\n"
# 响应体
response_body = file_data
# 拼接响应报文
response_data = (response_line + response_header + "\r\n").encode("utf-8") + response_body
# 发送数据
new_socket.send(response_data)
finally:
new_socket.close()

def start(self):
while True:
# 等待接受客户端的连接请求
new_socket, ip_port = self.tcp_server_socket.accept()
# 代码执行到此,说明连接建立成功
sub_thread = threading.Thread(target=self.handle_client_request, args=(new_socket,), daemon=True)
sub_thread.start()


def main():
# # 判断命令行输入命令是否正确
# print(sys.argv)
# if len(sys.argv) != 2:
# print('执行命令如下:python3 xxx.py 9090')
# return
#
# if not sys.argv[1].isdigit():
# print('端口号格式为数字')
# return
#
# # 动态绑定命令行端口号
# port = int(sys.argv[1])
port = 9000
web_server = HttpWebServer(port)
web_server.start()


if __name__ == '__main__':
main()

2. web框架开发

  • 接收web服务器的动态资源请求,处理请求并把处理结果返回给web服务器
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
'''web 框架的职责:专门负责处理动态资源请求'''
import time


def index():
# 响应状态
status = '200 OK'
# 响应头
response_header = [('Server','PWS2.0')]
# 处理后的数据
data = time.ctime()

return status, response_header,data


def not_found():
# 响应状态
status = '404 Not Found'
# 响应头
response_header = [('Server', 'PWS2.0')]
# 处理后的数据
data = 'not found'

return status, response_header, data

# 处理动态资源请求
def handle_request(env):
# 获取动态请求资源路径
request_path = env['request_path']
print('接收到的动态资源请求:',request_path)

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

3. 模版替换功能开发

  • 把web框架-模板文件中的模板变量{%content%}替换为请求时间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 获取首页数据
def index():
# 响应状态
status = "200 OK";
# 响应头
response_header = [("Server", "PWS2.0")]

# 打开模板文件,读取数据
with open("template/index.html", "r") as file:
file_data = file.read()

data = time.ctime()
result = file_data.replace('{%content%}',data)

return status, response_header, result

4. 路由列表功能开发

  • 路由:请求的URL到处理函数的映射,也就是说提前关联处理请求的URL和处理函数

(1)使用一个路由列表进行管理,通过路由列表保存每一个路由

请求路径 处理函数
/login.html login函数
/index.html index函数
/center.html center函数
1
2
3
4
5
# 定义路由列表
route_list = [
("/index.html", index),
("/center.html", center)
]

(2)根据用户请求遍历路由列表,处理用户请求

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
'''web 框架的职责:专门负责处理动态资源请求'''
import time


# 获取首页数据
def index():
# 响应状态
status = "200 OK";
# 响应头
response_header = [("Server", "PWS2.0")]

# 打开模板文件,读取数据
with open("template/index.html", "r") as file:
file_data = file.read()

data = time.ctime()
result = file_data.replace('{%content%}',data)

return status, response_header, result

# 获取个人中心数据
def center():
# 响应状态
status = "200 OK";
# 响应头
response_header = [("Server", "PWS2.0")]

# 打开模板文件,读取数据
with open("template/center.html", "r") as file:
file_data = file.read()

data = time.ctime()
result = file_data.replace('{%content%}',data)

return status, response_header, result

def not_found():
# 响应状态
status = '404 Not Found'
# 响应头
response_header = [('Server', 'PWS2.0')]
# 处理后的数据
data = 'not found'

return status, response_header, data

# 处理动态资源请求
def handle_request(env):
# 获取动态请求资源路径
request_path = env['request_path']
print('接收到的动态资源请求:',request_path)

# 遍历路由列表,选择执行函数
for path,func in route_list:
if request_path == path:
result = func()
return result

# 没有找到动态资源
result = not_found()
return result

5. 装饰器方式添加路由

  • 优点:完成路由的自动添加
  • 装饰器类型:带参数的装饰器(需要接收一个url参数)
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
'''web 框架的职责:专门负责处理动态资源请求'''
import time

# 定义路由列表
route_list = []

# 定义带有参数的装饰器
def route(path):
# 装饰器
def decorator(func):
# 当执行装饰器装饰指定函数的时候,把路由和函数添加到路由列表
route_list.append((path,func))

def inner():
# 执行指定函数
return func

return inner
# 返回装饰器
return decorator


# 获取首页数据
@route('/index.html')
def index():
# 响应状态
status = "200 OK";
# 响应头
response_header = [("Server", "PWS2.0")]

# 打开模板文件,读取数据
with open("template/index.html", "r") as file:
file_data = file.read()

data = time.ctime()
result = file_data.replace('{%content%}',data)

return status, response_header, result

# 获取个人中心数据
@route('/center.html')
def center():
# 响应状态
status = "200 OK";
# 响应头
response_header = [("Server", "PWS2.0")]

# 打开模板文件,读取数据
with open("template/center.html", "r") as file:
file_data = file.read()

data = time.ctime()
result = file_data.replace('{%content%}',data)

return status, response_header, result

def not_found():
# 响应状态
status = '404 Not Found'
# 响应头
response_header = [('Server', 'PWS2.0')]
# 处理后的数据
data = 'not found'

return status, response_header, data

# 处理动态资源请求
def handle_request(env):
# 获取动态请求资源路径
request_path = env['request_path']
print('接收到的动态资源请求:',request_path)

# 遍历路由列表,选择执行函数
for path,func in route_list:
if request_path == path:
result = func()
return result

# 没有找到动态资源
result = not_found()
return result

6. 显示信息页面的开发

  • 根据sql语句查询数据,然后替换模板变量
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
'''web 框架的职责:专门负责处理动态资源请求'''
import time
import pymysql

# 定义路由列表
route_list = []

# 定义带有参数的装饰器
def route(path):
# 装饰器
def decorator(func):
# 当执行装饰器装饰指定函数的时候,把路由和函数添加到路由列表
route_list.append((path,func))

def inner():
# 执行指定函数
return func

return inner
# 返回装饰器
return decorator


# 获取首页数据
@route('/index.html')
def index():
# 响应状态
status = "200 OK";
# 响应头
response_header = [("Server", "PWS2.0")]

# 打开模板文件,读取数据
with open("template/index.html", "r") as file:
file_data = file.read()

# 创建连接对象
conn = pymysql.connect(host='192.168.241.128',
port=3306,
user='root',
password='123456',
database='test',
charset='utf8')

# 获取游标对象
cursor = conn.cursor()
# 查询sql语句
sql = 'select * from info;'
# 执行sql,返回sql语句在执行过程中影响的行数
cursor.execute(sql)
# 获取所有结果
result = cursor.fetchall()
print(result)

# 关闭游标
cursor.close()
# 关闭连接
conn.close()

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' systemidvaule = '00007'></td>
</tr>''' % row

result = file_data.replace('{%content%}',data)

return status, response_header, result

# 获取个人中心数据
@route('/center.html')
def center():
# 响应状态
status = "200 OK";
# 响应头
response_header = [("Server", "PWS2.0")]

# 打开模板文件,读取数据
with open("template/center.html", "r") as file:
file_data = file.read()

data = time.ctime()
result = file_data.replace('{%content%}',data)

return status, response_header, result

def not_found():
# 响应状态
status = '404 Not Found'
# 响应头
response_header = [('Server', 'PWS2.0')]
# 处理后的数据
data = 'not found'

return status, response_header, data

# 处理动态资源请求
def handle_request(env):
# 获取动态请求资源路径
request_path = env['request_path']
print('接收到的动态资源请求:',request_path)

# 遍历路由列表,选择执行函数
for path,func in route_list:
if request_path == path:
result = func()
return result

# 没有找到动态资源
result = not_found()
return result

7. 数据接口的开发

  • web框架程序还可以开发数据接口,为客户端程序提供数据服务
  • 步骤
    1. 根据sql语句查询数据库
    2. 把数据转成json字符串返回
    3. 浏览器通过指定接口地址获取web框架提供的数据
  • 字典数据转换为json字符串:json.dumps(字符串,ensure_ascii = False)

    • 函数的第一个参数表示要把指定对象转成json字符串
    • 参数的第二个参数表示不使用ascii编码,可以在控制台显示中文
  • 没有文件模版时,响应头需要添加Content-Type表示指定数据的编码格式

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
# 个人中心数据接口开发
@route('/center_data.html')
def center_data():
# 响应状态
status = "200 OK";
# 响应头
response_header = [("Server", "PWS2.0"),
# 因为没有模版文件,所以可以通过响应头指定编码格式
("Content-Type","text/html;charset=utf-8")]

# 创建连接对象
conn = pymysql.connect(host='192.168.241.128',
port=3306,
user='root',
password='123456',
database='test',
charset='utf8')

# 获取游标对象
cursor = conn.cursor()
# 查询sql语句
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;'''
# 执行sql,返回sql语句在执行过程中影响的行数
cursor.execute(sql)
# 获取所有结果
result = cursor.fetchall()

# 关闭游标
cursor.close()
# 关闭连接
conn.close()

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

# 把列表字典转成json字符串,并在控制台显示
json_str = json.dumps(center_data_list,ensure_ascii=False)
print(json_str)

return status, response_header, json_str

8. 使用ajax请求数据渲染页面

  • 在个人中心模板文件添加ajax请求获取个人中心数据
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
$(document).ready(function(){
// 发送ajax请求,获取个人中心数据
$.get("center_data.html",function (data) {
// ajax 成功回调函数
// 获取table标签
var $table = $(".table");
// 如果指定了返回数据的解析方式是json,那么data就是一个js对象
for(var i = 0; i < data.length; i++){
// 根据下标获取每一个个人中心数据js对象
var oCenterData = data[i];

// 封装后的每一个tr标签
var oTr = '<tr>' +
'<td>'+ oCenterData.code +'</td>' +
'<td>'+ oCenterData.short +'</td>' +
'<td>'+ oCenterData.chg +'</td>' +
'<td>'+ oCenterData.turnover +'</td>' +
'<td>'+ oCenterData.price +'</td>' +
'<td>'+ oCenterData.highs +'</td>' +
'<td>'+ oCenterData.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标签追加每一行tr标签
$table.append(oTr)

}

}, "json");

9. logging日志

  • 相关包:logging

  • 程序日志信息作用

    • 方便了解程序的运行情况
    • 可以分析用户的操作行为、喜好等信息
    • 方便开发人员检查bug
  • logging日志可以分为5个等级,从低到高的顺序是: DEBUG < INFO < WARNING < ERROR < CRITICAL,默认的是WARNING等级,当在WARNING或WARNING之上等级的才记录日志信息

    • DEBUG:程序调试bug时使用
    • INFO:程序正常运行时使用
    • WARNING:程序未按预期运行时使用,但并不是错误,如用户登录密码错误
    • ERROR:程序出错误时使用,如IO操作失败
    • CRITICAL:特别严重的问题,导致程序不能再继续运行时使用,如:磁盘空间为空,一般很少使用
  • 在 logging 包中记录日志的方式有两种

    • 输出到控制台
    • 保存到日志文件
  • logging日志通用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import logging

# 设置日志等级和输出日志格式,并保存到日志文件
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
filename="log.txt",
filemode="w")
# level:设置日志等级
# format:设置日志输出格式,以下是参数说明
# %(levelname)s:打印日志级别名称
# %(filename)s:打印当前执行程序名
# %(lineno)d:打印日志的当前行号
# %(asctime)s:打印日志的时间
# %(message)s:打印日志信息

# 日志信息输出到控制台
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
  • logging日志在mini-web项目中的应用
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
import socket
import threading
import sys
import framework
import logging

# logging日志配置信息在程序入口模块设置一次,整个程序都可以生效
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s',
filename="log.txt",
filemode="w")
# 判断是否是动态资源请求
if request_path.endswith(".html"):
"""这里是动态资源请求,把请求信息交给框架处理"""
logging.info("动态资源请求:" + request_path)
...
else:
"""这里是静态资源请求"""
logging.info("静态资源请求:" + request_path)
...
# 获取命令行参数判断长度
if len(sys.argv) != 2:
print("执行命令如下: python3 xxx.py 9000")
logging.warning("用户在命令行启动程序参数个数不正确!")
return

# 判断端口号是否是数字
if not sys.argv[1].isdigit():
print("执行命令如下: python3 xxx.py 9000")
logging.warning("用户在命令行启动程序参数不是数字字符串!")
return
# 处理动态资源请求
def handle_request(env):
# 获取动态请求资源路径
request_path = env["request_path"]
print("接收到的动态资源请求:", request_path)
# 遍历路由列表,选择执行的函数
for path, func in route_list:
if request_path == path:
result = func()
return result
else:
logging.error("没有设置相应的路由:" + request_path)
# 没有找到动态资源
result = not_found()
return result