本篇在上篇的基础上为其增设路由功能,同时将上篇中的数据库中数据备份添加进去。
一、装饰器
在之前有介绍过为一个函数不改变源代码不改变原函数的调用方式下为其增设附加功能,需要用到装饰器,而在该上篇的web-mini框架中每当服务器发送动态资源请求过来时,我们需要做if判断,那么我们可不可以省去这繁琐的步骤呢?
1.1 通过闭包来实现装饰器:
#为函数附加新功能 - 计算运算时长
importtimedeftimmer(func):def wrapper(*args,**kwargs):
start_time=time.time()
ret= func(*args,**kwargs)
end_time=time.time()print("spend time --> %s"%(end_time -start_time))returnretreturnwrapper
@timmer#等价于 ==》test = timmer(test)
deftest(num):
time.sleep(1)print("in the test --> %s"%num )return num*num +1ret= test(8)print(ret)
注1:装饰器在原函数在调用之前就以及开始装饰了。即写上@timmer时test1 = timmer(test)就开始执行。下面我们介绍一种由类实现的装饰器:
1.2 由类实现的装饰器
classTest(object):"""定义一个装饰器类"""
def __init__(self,func):
self.func=funcdef __call__(self,*args,**kwargs):print("在此为函数附加新功能")
ret= self.func(*args,**kwargs)returnret
@Test#get_str = Test(get_str),相当于将get_str传入创建实例对象
defget_str(num):print("in the get_str-->%s"%num)return "hello world!"get_str()#Test(get_str)() ,必须有call魔法方法,实则运行实例的__call__方法
这种由类实现的装饰器,其效果与由闭包实现的装饰器效果几乎一样,但是由于进行一次装饰,需创建一个实例对象,即每次需要开辟一个内存空间,存放着实例属性、方法以及类的中方法的指针等,比较浪费资源,即 杀鸡用牛刀;
1.3 带参数的装饰器
def set_level(level_num): #用来接收参数
def set_func(func): #装饰器函数
def call_func(*args,**kwargs):if level_num = 1:print("设置权限1")elif level_num = 2:print("设置权限2")else:print("你个瓜皮,没有这个权限验证")
ret= func(*args,**kwargs)returnretreturncall_funcreturnset_func
@setlevel(1)deftest1():print("hello world")
@setlevel(2)deftest1():print("妈的个巴子哟")#setlevel(para) --- test1 = set_level(1)#1、首先调用set_level,并且传入参数1, --- test1 = set_level(1)#2、启动装饰器set_func,装饰函数,--- test1 = set_func(test1)
带参数的装饰器:
1、其最外层函数set_level,相当于一个容器用来封装,存储装饰器和一些变量等;而真正的装饰器部分实则为set_func装饰器;
2、进行装饰时:@setlevel(1)实则执行两步操作:
①、首先调用set_level,并且传入参数1, --- test1 = set_level(1) ;
②、启动装饰器set_func,装饰函数,--- test1 = set_func(test1);
二、静态、动态、伪静态URL
目前开发的网站其实真正意义上都是动态网站,只是URL上有些区别,一般URL分为静态URL、动态URL、伪静态URL,他们的区别是什么?
静态URL
静态URL类似 域名/news/2012-5-18/110.html 我们一般称为真静态URL,每个网页有真实的物理路径,也就是真实存在服务器里的。
动态URL
动态URL类似 域名/NewsMore.asp?id=5 或者 域名/DaiKuan.php?id=17,带有?号的URL,我们一般称为动态网址,每个URL只是一个逻辑地址,并不是真实物理存在服务器硬盘里的。
伪静态URL
伪静态URL类似 域名/course/74.html 这个URL和真静态URL类似。他是通过伪静态规则把动态URL伪装成静态网址。也是逻辑地址,不存在物理地址。
三者的优缺点:
1、静态URL:网页打开速度快,SEO最好,但是对于大中型网站而言页面多,修改起来不方便,不便管理;
2、动态URL:由于需要调用框架从数据库中读取数据,故网页打开速度不如静态URL,SEO不如静态URL,但是适合中大型网站,修改页面很方便,因为是逻辑地址,所以占用硬盘空间要比纯静态网站小。
3、伪静态URL:其输入浏览器的形式与静态URL相同,但是确实调用框架读取数据库中的数据来实现的,是相对于前两种的折中方案;URL比较友好,利于记忆;修改页面也十分方便,但是设置麻烦,服务器要支持重写规则,小企业网站或者玩不好的就不要折腾;
二、案例
该实例是在上篇(web-mini框架的基本实现(一))的基础上对其进行修改的,即为其添加路由功能,实现伪静态网页、以及替换成数据库中的数据;
服务端web_server.py
importsocketimportreimportmultiprocessingimporttime#import dynamic.mini_frame
importsysclassWSGIServer(object):def __init__(self, port, app, static_path):#1. 创建套接字
self.tcp_server_socket =socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)#2. 绑定
self.tcp_server_socket.bind(("", port))#3. 变为监听套接字
self.tcp_server_socket.listen(128)
self.application=app
self.static_path=static_pathdefservice_client(self, new_socket):"""为这个客户端返回数据"""
#1. 接收浏览器发送过来的请求 ,即http请求
#GET / HTTP/1.1
#.....
request = new_socket.recv(1024).decode("utf-8")#print(">>>"*50)
#print(request)
request_lines=request.splitlines()print("")print(">"*20)print(request_lines)#GET /index.html HTTP/1.1
#get post put del
file_name = ""ret= re.match(r"[^/]+(/[^ ]*)", request_lines[0])ifret:
file_name= ret.group(1)#print("*"*50, file_name)
if file_name == "/":
file_name= "/index.html"
#2. 返回http格式的数据,给浏览器
#2.1 如果请求的资源不是以.py结尾,那么就认为是静态资源(html/css/js/png,jpg等)
if not file_name.endswith(".html"):try:
f= open(self.static_path + file_name, "rb")except:
response= "HTTP/1.1 404 NOT FOUND\r\n"response+= "\r\n"response+= "------file not found-----"new_socket.send(response.encode("utf-8"))else:
html_content=f.read()
f.close()#2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"response+= "\r\n"
#2.2 准备发送给浏览器的数据---boy
#response += "hahahhah"
#将response header发送给浏览器
new_socket.send(response.encode("utf-8"))#将response ic.mini_frame.applicationbody发送给浏览器
new_socket.send(html_content)else:#2.2 如果是以.py结尾,那么就认为是动态资源的请求
env= dict() #这个字典中存放的是web服务器要传递给 web框架的数据信息
env['PATH_INFO'] =file_name#{"PATH_INFO": "/index.py"}
#body = dynamic.mini_frame.application(env, self.set_response_header)
body =self.application(env, self.set_response_header)
header= "HTTP/1.1 %s\r\n" %self.statusfor temp inself.headers:
header+= "%s:%s\r\n" % (temp[0], temp[1])
header+= "\r\n"response= header+body#发送response给浏览器
new_socket.send(response.encode("utf-8"))#关闭套接
new_socket.close()defset_response_header(self, status, headers):
self.status=status
self.headers= [("server", "mini_web v8.8")]
self.headers+=headersdefrun_forever(self):"""用来完成整体的控制"""
whileTrue:#4. 等待新客户端的链接
new_socket, client_addr =self.tcp_server_socket.accept()#5. 为这个客户端服务
p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
p.start()
new_socket.close()#关闭监听套接字
self.tcp_server_socket.close()defmain():"""控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行"""
if len(sys.argv) == 3:try:
port= int(sys.argv[1]) #7890
frame_app_name = sys.argv[2] #mini_frame:application
exceptException as ret:print("端口输入错误。。。。。ret:",ret)return
else:print("请按照以下方式运行:")print("python3 xxxx.py 7890 mini_frame:application")return
#mini_frame:application
ret = re.match(r"([^:]+):(.*)", frame_app_name)ifret:
frame_name= ret.group(1) #mini_frame
app_name = ret.group(2) #application
else:print("请按照以下方式运行:")print("python3 xxxx.py 7890 mini_frame:application")returnwith open("./web_server.conf") as f:
conf_info= eval(f.read()) #eval(str)函数很强大,官方解释为:将字符串str当成有效的表达式来求值并返回计算结果
#此时 conf_info是一个字典里面的数据为:
#{
#"static_path":"./static",
#"dynamic_path":"./dynamic"
#}
sys.path.append(conf_info['dynamic_path'])#import frame_name --->找frame_name.py
frame = __import__(frame_name) #返回值标记这 导入的这个模板
app = getattr(frame, app_name) #此时app就指向了 dynamic/mini_frame模块中的application这个函数
#print(app)
wsgi_server= WSGIServer(port, app, conf_info['static_path'])
wsgi_server.run_forever()if __name__ == "__main__":
main()
web_server.py
基于上篇的web_server.py实现了伪静态URL,即读取的URL中的页面filename是以.html结尾即视为动态资源,则调用框架读取数据库中资源替换模板。相当于欺骗浏览器,输入的是静态URL,确实按照动态URL的方式进行处理的;
web_mini框架 mini_frame.py
importrefrom pymysql importconnect#方式二:通过定义装饰器,自动添加文件名和函数名的映射关系,实现路由效果
URL_FUNC_DICT=dict()#带参数的装饰器
defroute(url):defget_func(func):
URL_FUNC_DICT[url]=funcdef call_func(*args,**kwargs):
ret=func()returnretreturncall_funcreturnget_funcdefselect_date(sql):
conn= connect(host="localhost",port=3306,database="stock_db",user="root",password= "mysql",charset="utf8")
cs=conn.cursor()
cs.execute(sql)
data_content= cs.fetchall() #得到的是一个元组,里面有很多个元组
cs.close()
conn.close()returndata_content#1.index = route("/index.py")即 index = get_func#2.index = get_func(index)即index = call_func
@route("/index.html")defindex():
with open("./templates/index.html") as f:
content=f.read()
sql= "select * from info;"my_stock_info=select_date(sql)
html_template= """
{0}{1}{2}{3}{4}{5}{6}{7}"""html= ""for temp inmy_stock_info:
html+= html_template.format(*temp)#print("----->>>{}<<
content= re.sub(r"\{%content%\}", html, content)returncontent
@route("/center.html")defcenter():
with open("./templates/center.html") as f:
content=f.read()
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;"my_stock_info=select_date(sql)#print(mys)
html =""html_template= """
{0}{1}{2}{3}{4}{5}{6} """for temp inmy_stock_info:
html+= html_template.format(*temp)
content= re.sub(r"\{%content%\}", html, content)returncontent#方式一:这种方式通过映射,手动定义字典添加key为文件名:value为对应函数名;#URL_FUNC_DICT = {#"/index.py":index,#"/center.py":center
#}
print(URL_FUNC_DICT)defapplication(env, start_response):
start_response('200 OK', [('Content-Type', 'text/html;charset=utf-8')])
file_name= env['PATH_INFO']#file_name = "/index.py"
run_func=URL_FUNC_DICT[file_name]returnrun_func()#if file_name == "/index.py":
#return index()
#elif file_name == "/center.py":
#return center()
#else:
#return 'Hello World! 我爱你中国....'
mini_frame.py
基于上篇的 mini_frame.py 实现了添加路由功能、及替换成MySQL数据库中的资源的效果;
over~~~其他部分基本与其他相同~~~