当前位置:   article > 正文

python web服务框架-精析Python3实现动态web服务(附服务端源码)

windows下python3语言选择web服务框架

实现一个简单的静态web网站,只需将写好的html页面上传到特定的web服务器软件即可,但静态网页其实和图片没什么区别,每次更新网站内容,都需要重新制作html页面,然后上传给提供web服务的软件,替换原来的html页面,也就完成了更新,以一个正常人的思维方式,每次更新内容都要重新生成html的工作实在太蛋疼了!那么能不能让程序自己生成html呢?当然可以,程序就是为将人类从重复繁杂的工作中解放出来而生的!

如果我们提供一个动态网站服务,至少应考虑以下四点:

1.要有稳定的web服务程序(可以使用知名的apache,nginx,这里为了探究原理,我们自己用多进程写一个简单的web服务);

2.要有可用的web网页模板(网络上web模版的数量堪比ppt模板,当然我们可以自己画一张, 10分钟后...算了-_-///,作为后端人员,我们这里用朴素的信息显示就好)

3.要有可填充html模板的内容(内容一般从自己的数据库里取,篇幅所限,我们这里用time.ctime()函数,模拟数据库动态数据);

4.要有处理填写内容的逻辑(这个就是我们要今天主要研究的按照wsgi标准实现的简单的web框架);

一个优秀的动态web框架应该是这样的:

1.web框架要和 web服务器软件 分离开;如果把大量的逻辑处理语句,和html放到一个文件中,后期会难以维护(这也是现在多人开发推崇MVC的原因);

2.一个优秀的web框架要和web服务器软件 有良好的交互通信;这时就需要一个数据交互的标准WSGI(python为自身web框架制定了WSGI标准);

3.一个优秀的web框架要实现,和数据库有良好的读写通信方法;

关于WSGI标准

为了方便表述,先举一个栗子:import?timedef?app(environ,?start_response):

status?=?'200?OK'

response_headers?=?[('Content-Type',?'text/plain')]

start_response(status,?response_headers)????return?str(environ)?+?'==Hello?world?from?a?simple?WSGI?application!--->%s '?%?time.ctime()

WIGS模型的要点:

1.在web框架模块,以上面的栗子为例,web服务器软件会向web框架传递一个列表(environ)和一个函数(函数体在web服务器软件中实现)的引用(start_response),然后web框架要实现一个app函数,并将 "一个列表"和"一个函数的引用",作为两个参数;

2.传递过来的列表内部存储了N个元组,这些元组包含了web服务器接收到的客户端浏览器的请求信息, 传递过来的函数参数的引用,可以用来返回请求资源的状态反馈(如果请求的资源可以访问,就会返回200,如果资源无法访问,就返回404或502之类的错误;

3.传递过来的函数引用的调用比return更靠前,这样可以在返回正式的网页之前的这段时间,让web服务器软件做好接收数据的准备;(其实可以将函数的引用作为web框架与web服务器软件传递数据的的一种快捷方式);

扩展:其实双重返回的设计思路很常见,比如在tcp四次挥手的过程中,第二次和第三次挥手都是服务器发送数据,客户端接收数据;

第二次服务端向客户端说("客户端,我收到你主动关闭本次连接的消息了!"),第三次服务端向客户端说("客户端,我已经关闭了这次的发送连接,不会给你发数据了,收到了记得回我个消息哈!");

也许有人会认为,既然第二次和第三次都是服务端向客户端发送数据,那应该可以将两条消息一起发送,但实际上,服务器关闭发送数据的通道是需要一定的时间的,如果第二次和第三次一起发送,客户端浏览器就不能在发送第一次消息后,及时确认消息是否送达,而在tcp连接中,及时"确认送达"是一件非常重要的事情!

在web服务器软件模块,至少要实现三个功能:

1.创建 包含客户端请求头消息的列表(作为第一个参数传递);

2.创建一个可以解析返回状态信息的函数(作为第二个参数传递);

3.接收web框架内app函数返回的body,并将body与作为第二个参数的引用的函数的返回状态值组合,一同发送给客户端浏览器;实现源码

1.作者自己编写小型web服务器(以上篇 gevent实现静态web服务器为基础改写)web_server.pyimport?socketfrom?sys?import?argvimport?geventfrom?gevent?import?monkeyimport?timeimport?randomimport?re#?服务器类class?WISG(object):

def?__init__(self,?port,?app):

self.port?=?port

self.root_dir?=?"./HTML"

#?创建主套接字

self.server_socket?=?socket.socket(socket.AF_INET,?socket.SOCK_STREAM)????????#?允许端口重用

self.server_socket.setsockopt(socket.SOL_SOCKET,?socket.SO_REUSEADDR,?1)????????#?主套接字绑定端口

self.server_socket.bind(("",?self.port))????????#?主套接字转为被动模式

self.server_socket.listen(128)????????#?获取web框架中的函数引用

self.app?=?app????????pass

#?启动服务器对象的入口函数

def?run_forever(self):

self.create_new_socket()????????pass

#?创建新的套接字,使用gevent,使新的套接字以消耗少量资源的协程方式运行

def?create_new_socket(self):

while?True:

new_client_socket,?new_client_socket_addr?=?self.server_socket.accept()

gevent.spawn(self.deal_accept_data,?new_client_socket)????#?处理接收到的数据

def?deal_accept_data(self,?new_client_socket):

recv_data?=?new_client_socket.recv(1024)????????#?接收到的请求为utf-8格式,解析数据

recv_data?=?recv_data.decode("utf-8")????????#?如果收到客户端发送的空字符,则关闭连接

if?not?recv_data:????????????return

#?将接收到的数据转换为列表

recv_data_list?=?recv_data.splitlines()????????#?获取请求头信息

the_request_header?=?recv_data_list[0]

file_name?=?self.get_file_name(the_request_header)

if?file_name.endswith(".html"):

print("请求的文件名为%s"%(file_name))

#?向客户端发送文件

self.send_html(file_name,?new_client_socket)

new_client_socket.close()????????else:

print("进入动态选择模块...")

print("请求的文件名为%s"%(file_name))????????????#?得到web框架返回的数据

#?创建一个字典

environ?=?dict()

environ["PATH_INFO"]?=?file_name????????????#获得内容

dynamic_content?=?self.app(environ,?self.set_response_header)

new_client_socket.send(self.dynamic_response_headers_info?+?dynamic_content.encode("utf-8"))

new_client_socket.close()????#?根据请求头信息,获得本地对应以.html或.py尾缀的文件名

def?get_file_name(self,?the_request_header):

"""GET?/index.html?HTTP/1.1"""

file_name?=?re.match(r"[^/]+([^?]+).*",?the_request_header).group(1)????????if?file_name?==?"/":

file_name?=?"/index.html"

return?file_name????????pass

#?发送静态文件的html到客户端

def?send_html(self,?file_name,?new_client_socket):

try:

f?=?open(self.root_dir+file_name,?"rb")????????except?Exception?as?res:

print(res)

print("无法找到网页404")????????else:

content?=?f.read()

respond_body?=?content

respond_header?=?"HTTP/1.1?200?OK? "

respond_header?+=?"Content-Type:?text/html;?charset=utf-8 "

respond_header?=?respond_header?+?" "

#?发送回应

new_client_socket.send(respond_header.encode("utf-8"))

new_client_socket.send(respond_body)

print("内容发送成功!")????????pass

def?set_response_header(self,?status,?headers):

#将从web框架收到的状态码,和返回的头信息存储到一个列表里面

self.dynamic_respond_header?=?[status,?headers]????????#?组建返回头信息

dynamic_respond_header?=?"HTTP/1.1?%s? "

dynamic_respond_header?+=?"%s:%s "%(headers[0][0],?headers[0][1])

dynamic_respond_header?+=?" "

#?将列表中的数据进行整理,转为可直接使用的"返回头"信息,然后存到类变量dynamic_response_headers_info

self.dynamic_response_headers_info?=??dynamic_respond_header.encode("utf-8")????????passdef?main():

monkey.patch_all()????#?创建web服务器

if?len(argv)?==?3:

port?=?int(argv[1])????????#?web框架名称

frame_name?=?re.match(r"([^:]+):(.+)",?argv[2]).group(1)????????#?web框架中主调函数的名称

app_name?=?re.match(r"([^:]+):(.+)",?argv[2]).group(2)????????#?动态导入框架函数app

web_frame_module?=?__import__(frame_name)????????#?获得框架中的主调函数

app?=?getattr(web_frame_module,?app_name)????#?传入端口号,和来自web框架的函数app

web_server?=?WISG(port,?app)

print("app的名字为%s,框架的名字为%s,端口号为%s"%(frame_name,?app_name,?port))

print("请在地址栏访问?127.0.0.1:%d"%(port))????#?启动web服务器

web_server.run_forever()????passif?__name__?==?"__main__":

main()

2.按照wsgi标准实现的web框架web_frame.pyimport?timeimport?reimport?codecs

template_root?=?"./HTML"file_name?=?Nonedef?read_file(file_name):

try:

file_name?=?template_root+file_name

f?=?codecs.open(file_name,?"r",?"utf-8")????except?Exception?as?e:

print(e)

print("无法打开%s"%file_name)????else:

content?=?f.read()????????#?这里我们假装从mysql获得了数据

data_from_mysql?=?"我是来自数据库的动态数据......"+'当前的时间为--->%s '?%?time.ctime()????????#?将模板中的{content}换成我们的"动态数据"

content?=?str(re.sub(r"{content}",?data_from_mysql,?content))

f.close()????????return?content#?web框架入口?def?app(environ,?start_response):

"""environ包含需要访问的.py文件(模板)的名称,?start_response代表来自web框架的函数的引用"""

#?设置返回的状态码信息

status?=?"200?OK"

#?设置返回的网页类型

response_headers?=?[('Content-Type',?'text/html;charset=utf-8')]????#?向web框架中定义的函数start_response中传入头信息(状态码,网页类型)

start_response(status,?response_headers)

file_name?=?environ["PATH_INFO"]

content?=?read_file(file_name)????#?将动态的数据返回给服务器框架

return?contentREADME终端运行:

python3 web_server.py 8888 web_frame:app目录样式

AAffA0nNPuCLAAAAAElFTkSuQmCC

AAffA0nNPuCLAAAAAElFTkSuQmCC

小结:

生成动态网页的本质,其实是让程序去替换html中特定部分的内容,换句话说,就是把html页面当成一个没有实际内容的模板,而当用户通过网址访问网页的时候,web框架就把动态的内容填到html模板里面,这样动态生成了带有内容的html网页,web服务器把带有内容的html网页发送给用户浏览器,最后用户收到了含有完整内容的网页,搞定!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/55771
推荐阅读
相关标签
  

闽ICP备14008679号