Mini_Web
常用单词
- request 英 [rɪ'kwest] 请求;需要
- response 英 [rɪ'spɒns] 响应;反应;
- route 英 [ruːt] 按某路线发送 ; 路由
- template 英 ['templeɪt; -plɪt] 模板,样板
- source 英 [sɔːs] 来源;水源;
Web应用概述
1. Web开发流程
前面已经学习过Web服务器, 我们知道Web服务器主要是接收用户的http请求,根据用户的请求返回不同的资源数据,但是之前我们开发的是静态Web服务器,返回的都是静态资源数据,假如我们想要Web服务器返回动态资源那么该如何进行处理呢?
关系说明:
- Web服务器接收浏览器发起的请求,如果是动态资源请求找Web应用
- Web应用负责处理浏览器的动态资源请求,把处理的结果发送给Web服务器
- Web服务器再把响应结果发生给浏览器
2. 静态资源
不需要经常变化的资源,这种资源Web服务器可以提前准备好,比如: png/jpg/css/js等文件。
3. 动态资源
和静态资源相反, 这种资源会经常变化,比如: 我们在京东浏览商品时经常会根据条件进行筛选,选择不同条件, 浏览的商品就不同,这种资源Web服务器无法提前准备好,需要Web应用来帮Web服务器进行准备。
4. WSGI协议
它是Web服务器和Web应用之间进行协同工作的一个规则,WSGI协议规定Web服务器把动态资源的请求信息传给Web应用处理,Web应用把处理好的结果返回给Web服务器。
5. 小结
- Web应用是专门为Web服务器处理动态资源请求的一个程序
- Web应用和Web服务器的关系是Web应用专门服务于Web服务器,给Web服务器提供处理动态资源请求的服务。
Web应用开发
1. Web应用职责介绍
- 接收web服务器的动态资源请求,给web服务器提供处理动态资源请求的服务。
2. 动态资源判断
- 根据请求资源路径进行判断
web服务器程序(web.py)代码:
import socket
import threading
import dynamic.frame
# 获取用户请求资源的路径
# 根据请求资源的路径,读取指定文件的数据
# 组装指定文件数据的响应报文,发送给浏览器
# 判断请求的文件在服务端不存在,组装404状态的响应报文,发送给浏览器
class HttpWebServer:
def __init__(self):
# 1.编写一个TCP服务端程序
# 创建socekt
self.tcp_server_socekt = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置端口复用
self.tcp_server_socekt.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定地址 ip地址替换为自己电脑的ip
self.tcp_server_socekt.bind(("192.168.99.117", 8000))
# 设置监听
self.tcp_server_socekt.listen(128)
def handle_client_request(self, client_socekt, client_addr):
# 获取浏览器的请求信息
client_request_data = client_socekt.recv(1024).decode()
# 判断客户端是否关闭
if len(client_request_data) == 0:
print('客户端关闭')
client_socekt.close()
return
# 获取用户请求信息
request_data = client_request_data.split("\r\n")
# 获取请求行数据
request_line = request_data.pop(0) # 使用pop将请求行数据从列表中取出
request_line_data = request_line.split(' ')
# 获取请求头部数据
request_header = {}
for data in request_data:
if data == '':
continue
request_header[data.split(": ")[0]] = data.split(": ")[1]
# 将客户端ip地址添加到header中
request_header['client_addr'] = client_addr
# 请求行中获取请求资源的路径
request_path = request_line_data[1]
# 判断是否有查询字符串数据
request_query = request_path.split('?')
if len(request_query) > 1:
request_path = request_query[0]
query_str = request_query[1]
else:
query_str = None
# 构造请求数据
requests = {
'path': request_path,
'query_str': query_str,
'header': request_header,
}
"""动态资源"""
# 符合wsgi协议的参数
env = {
"request_path": request_path,
"requests": requests
}
# 应答行
response_line = "HTTP/1.1 200 OK\r\n"
# 应答头
response_header = "Server:pwb\r\nAccess-Control-Allow-Credentials:true\r\nAccess-Control-Allow-Origin:*\r\nAccess-Control-Allow-Methods:GET, POST, PUT\n\rAccess-Control-Allow-Headers:X-Custom-Header"
# 应答体
response_body = dynamic.frame.application(env)
# 应答数据
response_data = response_line + response_header + "\r\n\r\n" + response_body
# 发送数据给到浏览器
client_socekt.send(response_data.encode())
# 关闭和浏览器通讯的socket
client_socekt.close()
def start(self):
while True:
# 2.获取浏览器发送的HTTP请求报文数据
# 建立链接
client_socekt, client_addr = self.tcp_server_socekt.accept()
# 创建子线程
sub_thread = threading.Thread(target=self.handle_client_request, args=(client_socekt, client_addr))
sub_thread.start()
if __name__ == '__main__':
# 创建服务器对象
my_web_server = HttpWebServer()
# 启动服务器
my_web_server.start()
Copy
3. 处理客户端的动态资源请求
- 创建web应用程序
- 接收web服务器的动态资源请求
- 处理web服务器的动态资源请求并把处理结果返回给web服务器
- web服务器把处理结果组装成响应报文发送给浏览器
web应用程序(framework.py)代码:
"""miniweb,负责处理动态资源请求"""
import re
from pymysql import *
import json
# ajax请求的数据,获取首页数据
def index(env):
# 创建链接
conn = connect(host='localhost', port=3306, database='booksite', user='root', password='mysql', charset='utf8')
# 创建游标
cursor = conn.cursor()
# 执行sql语句
sql = "select * from bookinfo;"
cursor.execute(sql)
# 获取数据 元组 ((),())
stock_data = cursor.fetchall()
# 把元组转化成列表
center_data_list =[]
for data in stock_data:
center_data_list.append({
"id":data[0],
"name":data[1],
"auth":data[2],
"img_url":data[3],
"rank":data[4]
})
# 把列表转化成json字符串
# ensure_ascii = False 控制台中可以显示中文
json_str = json.dumps(center_data_list, ensure_ascii=False)
# 关闭连接
cursor.close()
conn.close()
return json_str
# 处理动态资源请求
def handle_request(env):
# 获取动态请求资源路径
request_path = env["request_path"]
print("接收到的动态资源请求:", request_path)
if request_path == "/index":
# 获取首页数据
result = index(env)
return result
else:
# 没有找到动态资源
return "404 not found..."
Copy
4. 小结
- 动态资源的判断通过请求资源路径来完成
- 处理客户端的动态资源请求
- 接收web服务器的动态资源请求
- Web应用程序处理动态资源请求并把处理结果返回给web服务器
- web服务器把处理结果组装成响应报文发送给浏览器
路由列表功能开发
1. 路由的介绍
接着上面程序的判断场景,假如咱们再处理一个图书详情的动态资源请求非常简单,再添加一个函数和更加一个分支判断就可以实现了。
framework.py 示例代码:
# 获取首页数据
def index(env):
# 创建链接
conn = connect(host='localhost', port=3306, database='booksite', user='root', password='mysql', charset='utf8')
# 创建游标
cursor = conn.cursor()
# 执行sql语句
sql = "select * from bookinfo;"
cursor.execute(sql)
# 获取数据 元组 ((),())
stock_data = cursor.fetchall()
# 把元组转化成列表
center_data_list =[]
for data in stock_data:
center_data_list.append({
"id":data[0],
"name":data[1],
"auth":data[2],
"img_url":data[3],
"rank":data[4]
})
# 把列表转化成json字符串
# ensure_ascii = False 控制台中可以显示中文
json_str = json.dumps(center_data_list, ensure_ascii=False)
# 关闭连接
cursor.close()
conn.close()
return json_str
# 获取详情页
def detail(env):
# 获取前端传递数据
data = env['requests']['query_str']
header = env['requests']['header']
# 创建链接
conn = connect(host='localhost', port=3306, database='booksite', user='root', password='mysql', charset='utf8')
# 创建游标
cursor = conn.cursor()
# 执行sql语句 按照指定条件查询
sql = "select * from bookinfo where %s;"%data
cursor.execute(sql)
# 获取数据 元组 ((),())
stock_data = cursor.fetchone()
data_dict = {
"id":stock_data[0],
"name":stock_data[1],
"auth":stock_data[2],
"img_url":stock_data[3],
"read":stock_data[5],
"comment":stock_data[6],
"score":stock_data[8],
"content":stock_data[7],
"synopsis":stock_data[9]
}
# 将阅读量更新后写入数据库
read = stock_data[5] + 1
sql = 'update bookinfo set bread=%d where id=%d;'%(read,stock_data[0])
cursor.execute(sql)
conn.commit()
json_str = json.dumps(data_dict, ensure_ascii=False)
return json_str
# 处理动态资源请求
def handle_request(env):
# 获取动态请求资源路径
request_path = env["request_path"]
print("接收到的动态资源请求:", request_path)
if request_path == "/index":
# 获取首页数d
result = index()
return result
elif request_path == "/detail":
# 获取详情页数据
result = detail(env)
return result
else:
# 没有找到动态资源
return "404 not found..."
Copy
那如果咱们的应用处理的页面请求路径再多一些,比如:5个路径判断,大家可能感觉条件分支完全可以胜任,如果是40个甚至更多呢? 如果这是还是用普通的条件分支简直无法忍受。
解决办法: 可以使用路由
什么是路由?
路由就是请求的URL到处理函数的映射,也就是说提前把请求的URL和处理函数关联好。
路由列表
这么多的路由如何管理呢, 可以使用一个路由列表进行管理,通过路由列表保存每一个路由。
请求路径 | 处理函数 |
---|---|
/login | login函数 |
/index | index函数 |
/detail | detail函数 |
2. 在路由列表添加路由
framework.py 示例代码:
# 定义路由列表
route_list = [
("/index", index),
("/detail", detail)
]
Copy
3. 根据用户请求遍历路由列表处理用户请求
framework.py 示例代码:
# 处理动态资源请求
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:
# 没有找到动态资源
return "404 not found..."
# if request_path == "/index":
# # 获取首页数据
# result = index()
# return result
# elif request_path == "/detail":
# # 获取个人中心数据
# result = detail()
# return result
# else:
# # 没有找到动态资源
# return "404 not found..."
Copy
小结
- 路由是请求的URL到处理函数的映射
- 路由列表是用来保存每一个设置好的路由
- 用户的动态资源请求通过遍历路由列表找到对应的处理函数来完成。
logging日志
1. logging日志的介绍
在现实生活中,记录日志非常重要,比如:银行转账时会有转账记录;飞机飞行过程中,会有个黑盒子(飞行数据记录器)记录着飞机的飞行过程,那在咱们python程序中想要记录程序在运行时所产生的日志信息,怎么做呢?
可以使用 logging 这个包来完成
记录程序日志信息的目的是:
- 可以很方便的了解程序的运行情况
- 可以分析用户的操作行为、喜好等信息
- 方便开发人员检查bug
2. logging日志级别介绍
日志等级可以分为5个,从低到高分别是:
- DEBUG
- INFO
- WARNING
- ERROR
- CRITICAL
日志等级说明:
- DEBUG:程序调试bug时使用
- INFO:程序正常运行时使用
- WARNING:程序未按预期运行时使用,但并不是错误,如:用户登录密码错误
- ERROR:程序出错误时使用,如:IO操作失败
- CRITICAL:特别严重的问题,导致程序不能再继续运行时使用,如:磁盘空间为空,一般很少使用
- 默认的是WARNING等级,当在WARNING或WARNING之上等级的才记录日志信息。
- 日志等级从低到高的顺序是: DEBUG < INFO < WARNING < ERROR < CRITICAL
3. logging日志的使用
在 logging 包中记录日志的方式有两种:
- 输出到控制台
- 保存到日志文件
日志信息输出到控制台的示例代码:
import logging
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
Copy
运行结果:
WARNING:root:这是一个warning级别的日志信息
ERROR:root:这是一个error级别的日志信息
CRITICAL:root:这是一个critical级别的日志信息
Copy
说明:
- 日志信息只显示了大于等于WARNING级别的日志,这说明默认的日志级别设置为WARNING
logging日志等级和输出格式的设置:
import logging
# 设置日志等级和输出日志格式
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
Copy
运行结果:
2019-02-13 20:41:33,080 - hello.py[line:6] - DEBUG: 这是一个debug级别的日志信息
2019-02-13 20:41:33,080 - hello.py[line:7] - INFO: 这是一个info级别的日志信息
2019-02-13 20:41:33,080 - hello.py[line:8] - WARNING: 这是一个warning级别的日志信息
2019-02-13 20:41:33,080 - hello.py[line:9] - ERROR: 这是一个error级别的日志信息
2019-02-13 20:41:33,080 - hello.py[line:10] - CRITICAL: 这是一个critical级别的日志信息
Copy
代码说明:
- 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',
filename="log.txt",
filemode="w")
logging.debug('这是一个debug级别的日志信息')
logging.info('这是一个info级别的日志信息')
logging.warning('这是一个warning级别的日志信息')
logging.error('这是一个error级别的日志信息')
logging.critical('这是一个critical级别的日志信息')
Copy
运行结果:
4. logging日志在mini-web项目中应用
web.py 程序使用logging日志示例:
-
程序入口模块设置logging日志的设置
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")
Copy -
INFO级别的日志输出,示例代码:
# 判断是否是动态资源请求 if request_path.endswith(".html"): """这里是动态资源请求,把请求信息交给框架处理""" logging.info("动态资源请求:" + request_path) ... else: """这里是静态资源请求""" logging.info("静态资源请求:" + request_path) ...
Copy -
WARNING级别的日志输出,示例代码:
# 获取命令行参数判断长度 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
Copy
framework.py 程序使用logging日志示例:
-
ERROR级别的日志输出,示例代码:
# 处理动态资源请求 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) # 没有找到动态资源 return "404 not fo
Copy
说明:
- logging日志配置信息在程序入口模块设置一次,整个程序都可以生效。
- logging.basicConfig 表示 logging 日志配置操作
5. 小结
- 记录python程序中日志信息使用 logging 包来完成
-
logging日志等级有5个:
- DEBUG
- INFO
- WARNING
- ERROR
- CRITICAL
-
打印(记录)日志的函数有5个:
- logging.debug函数, 表示: 打印(记录)DEBUG级别的日志信息
- logging.info函数, 表示: 打印(记录)INFO级别的日志信息
- logging.warning函数, 表示: 打印(记录)WARNING级别的日志信息
- logging.error函数, 表示: 打印(记录)ERROR级别的日志信息
- logging.critical函数, 表示: 打印(记录)CRITICAL级别的日志信息