mini-web框架 -- 基于wsgi协议

  1. 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')
    
  2. 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是服务器的类,此处不做说明,下面另有说明
  1. 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 "----->网址有误<-----"		
	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值