-
web静态服务器 – 只能处理静态文件请求
1. http/https协议 HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信 HTTPS基于HTTP协议,通过SSL和TSL提供加密处理数据、验证对方身份以及数据完整性保护 浏览器和服务器之间的通信就是通过http协议,request和response有不同的格式 response协程http协议要求的格式即可实现服务器和浏览器的通信 2. 静态web服务器 -- 服务器只提供在本地的静态文件 1. 浏览器发送给服务器的request请求 request请求首行 GET /favicon.ico HTTP/1.1 # GET+请求文件+HTTP/1.1,GET是请求方式,也可以是POST 2. 服务器回复浏览器的请求 -- response response的格式 -- 头部信息(显示是否访问成功)+空格(\r\n)+body信息(服务器发送的内容) # 准备头部信息 response = 'HTTP/1.1 404 not found\r\n' / response = 'HTTP/1.1 200 OK\r\n' # 准备空格 0 response += '\r\n' # 准备body信息 content = '-----file not found-----' content = content.encode('utf-8')
-
mini_web框架 – 可以处理动/静态文件请求
1. WSGI协议 -- 处理动态文件请求 服务器和框架都遵循WSGI协议,所以服务器可以调用框架。 WSGI协议简单来说就是在框架中定义一个application函数,传入相关信息,返回html内容。 2. 工作原理 1. 客户端和服务器三次握手后建立连接,客户端向服务器发出request请求; 2. 服务器读取request请求中请求的文件,确认请求页面类型,是静态页面还是动态页面; 静态页面就是图片,css,js之类的,动态请求就是.py的请求(java是.jsp) 当然有伪静态文件以.html结尾,通过框架将.html处理成.py文件; 3. 静态文件继续访问本地static下的静态文件,动态或伪静态文件访问本地template下的文件; template下的文件时模板,可以在框架中修改模板文件,但是静态文件是不会进行修改的。 4. 服务器确认请求页面是动态文件,会给application函数传入字典,和start_response函数; application函数通过字典获取文件名,然后去访问template下的对应文件,获取内容后返回; application函数使用start_response函数给服务器返回response_header 3. 解耦 1. 服务器端口,使用sys模块,通过sys.argv获取终端输入的参数; 2. 调用的框架,使用__import__方法,以及getattr()方法,获取application方法的指向 3. 静态文件和动态文件的保存目录,可以使用配置文件写入目录地址,并且可以直接在配置文件修改,配置文件以是以 .cnf 为后缀的文件 4. 执行时可以使用shell脚本,将执行语句写入脚本中, python3 02-服务器与动静态文件夹解耦.py 8080 mini_web_frame_v2:application, 脚本后缀为 .sh,此处可以直接在linux系统中直接执行, ./(.sh文件名) 5. 使用readme.txt,进行备忘录。 4. mini_web框架构成思想 1. 服务器将请求的文件名发送给mini_web框架; 2. 框架通过路由确定调用哪个视图函数; 3. 视图函数主要是从数据库获取数据调整模板文件内容; 4. 然后将模板文件内容+response_header一起发给服务器; 5. 服务器再将获取的内容发送给浏览器 5. django框架工作简单说明 -- server + frame 1. 浏览器访问网址 2. 获取请求信息后进行url匹配 -- urls.py 3. 执行与url对应的view函数 4. 视图函数通过调用数据库来调整template模板,并将模板内容返回给浏览器 views.py 调用 models.py来操作数据库,返回变量修改template, 最终将template内容返回给浏览器
# mini_web -- 传入 response_header, response_content
def application(env, start_response):
start_response(status_code, response_h)
return body_content
# server -- 获取response_header
def start_response(status_code, response_h):
status_code = status_code
response_header = [("Server", "mini_web_frame")]
response_header += response_h
# 解耦 -- server
# 端口,服务器,动静态文件夹 解耦
# 终端输入 python xxx.py, 8080, mini_web_frame_v2:application
# 使用sys.argv,返回的是一个列表[xxx.py, 8080, mini_web_frame_v2:application],然后通过索引获取值
# 然后取出端口和框架文件以及对应的函数名
import sys, re
if len(sys.argv) == 3:
# sys.argv传地进来的参数是[xxx.py, 8080, mini_web_frame_v2:application]
try:
port = int(sys.argv[1])
frame_app_name = sys.argv[2]
except Exception as ret:
print("请按 'python3 xxx.py 8080 mini_web_frame_v2:application' 格式重新输入")
return
names = re.search(r"(.+?):(.+)", frame_app_name)
if names:
frame_name = names.group(1)
app_name = names.group(2)
else:
print("请按 'python3 xxx.py 8080 mini_web_frame_v2:application' 格式重新输入")
return
# 取出函数,传入实例对象中。
# 使用__import__(frame_name)就是查找frame_name.py,会返回指向
# 因此需要将frame_name.py路径加入sys.path
with open("server.conf", "r") as fp:
conf_info = eval(fp.read()) # eval将字符串转成相应的对象(此处将字符串转换为字典)
sys.path.append(conf_info['dynamic_path']) # 将frame_name.py所在文件夹加入sys.path
static_path = conf_info['static_path']
frame = __import__(frame_name) # 调用frame_name.py里面的application函数
app = getattr(frame, app_name) # 此时app就指向了 dynamic/mini_frame模块中的application这个函数
server = WSGIServer(port, app, static_path) # WSGIServer是服务器的类,此处不做说明,下面另有说明
- mini_web框架代码实现 – 仿django框架
# server
import sys
import re
import socket
import multiprocessing
class WSGIServer(object):
'''遵循WSGI协议的服务器'''
def __init__(self, port, app, static_path):
self.app = app
self.static_path = static_path
# 创建套接字,绑定端口号,被动监听
self_tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STRAM)
self_tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 允许服务器端口一被释放就可以被复用,没有等待时间
self.tcp_socket.bind(("", port))
self.tcp_socket.listen(128)
def run(self):
'''并发服务器,并发处理服务客户端的套接字'''
# 循环获取连接产生服务套接字,多任务处理服务套接字
while True:
server_socket, server_addr = self.tcp_socket.accept()
p = multiprocessing.Process(target= self.get_file_name, args=(server.socket, ))
p.start()
server_socket.close()
def get_file_name(server_socket):
'''获取request中请求文件的名字,分为动静态文件处理,获取response内容'''
# 获取request
request = server_socket.recv(1024) # byte类型
request = request.decode("utf-8")
# 获取请求的文件名,并分类为静态文件名和动态文件名
file_name_re = re.search(r'.+?/(.+?)HTTP', request) # (.+?)中包含空格,需要去空格
if file_name_re:
# 动态文件名,即获取的文件名
file_name_d = file_name_re.group(1).strip()
# 静态文件名,需要加上静态文件夹的路径
static_dir = '%s/'%self.static_path
if file_name_d == "":
file_name_s = static_dir + 'index.html'
else:
file_name_s = static_dir + file_name_d
# 动/静态文件分别处理
# 静态文件处理,返回response
if not file_name_d endswith('.html'):
# 注意,文件名可能有误,所以使用try...except...else
try:
fp = open(file_name_s, 'r', encoding= 'utf-8')
except Exception as e:
print(e)
response_header = 'HTTP 1.1 404 NOT FOUND \r\n'
response_body = 'file not found'
# 网站应该用短连接,此处测试,使用长连接知识点
response_header += 'Content-Length: %d \r\n'%len(response_body)
response = response.header + '\r\n' + response_body # 返回给客户端的内容
else:
response_body = fp.read()
fp.close()
response_header = 'HTTP 1.1 200 OK \r\n'
# 网站应该用短连接,此处测试,使用长连接知识点
response_header += 'Content-Length: %d \r\n'%len(response_body) # 长连接
response = response.header + '\r\n' + response_body
# 动态文件处理,返回response
# 使用application函数,此处是self.app
else:
env = {}
env[PATH_INFO] = file_name_d
response_body = self.app(env, self.start_response)
response_header = 'HTTP/1.1 %s OK \r\n'%self.status_code
for temp in self.response_header:
response_header += '%s: %s \r\n'%(temp[0], temp[1])
# 网站应该用短连接,此处测试,使用长连接知识点
response_header += 'Content-Length: %d \r\n'%len(response_body)
response = response_header + '\r\n' + response_body
# 将response发送给客户端
server_socket.send(response.encode('utf-8'))
# server_socket.close() # 使用长连接时,不需要close语句
def start_response(self, status_code, response_h):
self.status_code = status_code
self.response_header = [('Server', 'mini_web_frame')]
self.response_header += response_h
def main():
'''解耦,需要获取port, app, static_path三个参数'''
# 终端输入格式:python3 xxx.py 8080 mini_web_frame_v2:application
# 此处代码为上文的解耦代码块
if len(sys.args) == 3:
try:
port = int(sys.args[1])
frame_app_name = sys.args[2]
except:
print("请按 'python3 xxx.py 8080 mini_web_frame_v2:application' 格式重新输入")
return
# 获取 frame name, 获取 app name
frame_app_name_re = re.search(r'(.+?):(.+)', frame_app_name) # 如果有边界就是非贪婪,内有边界就贪婪
if frame_app_name_re:
frame_name = frame_app_name_re.group(1)
app_name = frame_app_name_re.group(2)
else:
print("请按 'python3 xxx.py 8080 mini_web_frame_v2:application' 格式重新输入")
return
# 获取static_path
# 使用conf配置文件,来修改静态文件夹/动态文件夹内部的位置
# conf配置文件中是一个字典{'static_path':xxx, 'dynamic_path':xxx}
with open('server.conf', 'r', encodind= 'utf-8') as fp:
conf_info = fp.read()
conf_info = eval(conf_info)
static_path = conf_info[static_path]
# 将框架文件所在的文件夹导入sys搜索路径
sys.path.append(conf_info[dynamic_path])
# 调用框架文件
frame = __import__(frame_name)
# 调用application函数,获取app
app = getattr(frame, app_name)
server = WSGIServer(port, app, static_path)
server.run()
if __name__ == '__main__':
main()
# mini_web_frame
import re
import logging
import pymysql
# 配置日志
logging.basicConfig(
level=logging.INFO,
filename= '../06log.txt',
filemode= 'a',
format= '%(asctime)s - %(filename)s[line: %(lineno)d] - %(levelname)s: %(message)s'
)
# 路由
name_func_dict = dict()
def route(file_name):
def handle(func):
name_func_dict[file_name] = func
def wrapped(*args, **kwargs):
return func(*args, **kwargs)
return wrapped
return handle
def select(sql, *args):
'''实现查询功能'''
con = pymysql.connect(host= '', port= 3306, user= 'root', password= 'root', database= 'stock_db', charset= 'utf8')
cur = con.cursor()
cur.execute(sql, args) # args是元组类型
res = cur.fetchall()
cur.close()
con.close()
return res
def sql_func(sql, *args):
'''实现插入,删除,更新数据表功能'''
con = pymysql.connect(host= '', port= 3306, user= 'root', password= 'root', database= 'stock_db', charset= 'utf8')
cur = con.cursor()
cur.execute(sql, args) # args是元组类型
con.commit()
cur.close()
con.close()
# 这些函数就好比django框架中的views.py
@route(r'index.html')
def index(param):
'''显示首页'''
# 读取对应的HTML文件
try:
fp = open('./templates/index.html', 'r', encoding= 'utf-8')
except Exception as e:
print('error: %s'%str(e))
logging.error("error: %s" % str(e))
else:
response_body = fp.read()
fp.close()
# index.html模板文件中的数据需要从数据库中读取
sql = "select * from info"
res = select(sql)
# 遍历数据库数据(列表),拼接成想要的样子
display_content = ''
# line表示数据库中的一条数据
for line in res:
per_display_content = ''
# col表示一条数据中一个字段对应的数据
for col in line:
# 将内容显示成<td>col</td>\n,即表示一个单元格内容
col_element = "<td>" + str(col) + "</td>\n"
per_display_content += col_element
# print("display_content: ", display_content)
# <td><input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="000007"></td>
# 一条数据内容拼接完成后,还有一个添加按钮,占一个单元格
per_display_content += """<td><input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="%s"></td>""" % line[1]
# 一条数据表示一行
per_display_content = "<tr>" + per_display_content + "</tr>"
display_content += per_display_content
# 将数据库中获取到的数据替换模板中的变量
response_body = re.sub(r"\{%content%\}", display_content, response_body)
return response_body
@route(r'center.html')
def center(param):
'''显示用户中心页'''
try:
fp = open('./templates/center.html', 'r', encoding= 'utf-8')
except Exception as e:
print('error: %s'%str(e))
logging.error("error: %s" % str(e))
else:
response_body = fp.read()
fp.close()
# index.html模板文件中的数据需要从数据库中读取
sql = "select i.code, i.short, i.chg, i.turnover, i.price, i.highs, f.note_info from focus as f inner join info as i on f.info_id=i.id"
res = select(sql)
# 遍历数据库数据(列表),拼接成想要的样子
display_content = ''
# line表示数据库中的一条数据
for line in res:
per_display_content = ''
# col表示一条数据中一个字段对应的数据
for col in line:
# 将内容显示成<td>col</td>\n,即表示一个单元格内容
col_element = "<td>" + str(col) + "</td>\n"
per_display_content += col_element
# print("display_content: ", display_content)
# <td><input type="button" value="添加" id="toAdd" name="toAdd" systemidvaule="000007"></td>
# 一条数据内容拼接完成后,还有一个添加按钮,占一个单元格
per_display_content += """<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>
""" % (line[0], line[0])
# 一条数据表示一行
per_display_content = "<tr>" + per_display_content + "</tr>"
display_content += per_display_content
# 将数据库中获取到的数据替换模板中的变量
response_body = re.sub(r"\{%content%\}", display_content, response_body)
return response_body
@route(r'add/(.+?)\.html')
def add_focus(param):
'''添加关注,网址add/000007.html'''
# 点击index页面的添加,最终目的是在center页面显示
# 点击添加,center对应的数据库增加数据记录
# 获取股票代码
stock_code = param.group(1)
# 确认stock_code在不在index的info表中
sql_si = 'select * from info where code=%s'
res_info = select(sql_si, stock_code) # stock_code参数会自动变为元组类型
# 在info表中
if res_info:
# 确认center的focus表中有没有,没有就添加
sql_sf = 'select * from focus as f inner join info as i on f.info_id=i.id where i.code=%s'
res_focus = select(sql_sf, stock_code)
if not res_focus:
sql_if = 'insert into focus(info_id) select id from info where code=%s'
sql_func(sql_if, stock_code)
return '关注成功'
else:
logging.info("已关注,请勿重复关注")
return "已关注,请勿重复关注"
else:
return "没有这只股票。。。"
@route(r'del/(.+?)\.html')
def del_focus(param):
'''取消关注,网址del/000007.html'''
# 在center的focus表中去除掉这条数据
# 获取股票代码
stock_code = param.group(1)
# 确认stock_code在不在index的info表中
sql_si = 'select * from info where code=%s'
res_info = select(sql_si, stock_code) # stock_code参数会自动变为元组类型
# 在info表中
if res_info:
# 确认center的focus表中有没有,有就删除
sql_sf = 'select * from focus as f inner join info as i on f.info_id=i.id where i.code=%s'
res_focus = select(sql_sf, stock_code)
if res_focus:
sql_df = 'delete from focus where info_id=(select id from info where code=%s)'
sql_func(sql_df, stock_code)
return '取消关注成功'
else:
logging.info("未关注,请先关注")
return "未关注,请先关注"
else:
return "没有这只股票。。。"
# 框架中必备的application函数
def application(env, start_response):
start_response(200, [('Content-Type', 'text/html;charset=utf-8')])
file_name = env[PATH_INFO]
# 此处需要使用路由,相当于django框架中的urls.py文件
# 需要通过字典映射创建路由
try:
# k是匹配file_name的正则表达式,v是file_name对应的函数名
# 将服务器传过来的文件名和正则表达式进行匹配,
for k,v in name_func_dict.items():
param = re.search(k, file_name)
if param:
return v(param)
else:
logging.warning("请求的内容是没有的。。。")
return "请求的内容是没有的。。。"
except Exception as ret:
logging.error("error: %s" % str(ret))
return "----->网址有误<-----"