首页 > IT职业 > IT职场 > 精析Python3实现动态web服务(附服务端源码)
实现一个简单的静态web网站,只需将写好的html页面上传到特定的web服务器软件即可,但静态网页其实和图片没什么区别,每次更新网站内容,都需要重新制作html页面,然后上传给提供web服务的软件,替换原来的html页面,也就完成了更新,以一个正常人的思维方式,每次更新内容都要重新生成html的工作实在太蛋疼了!那么能不能让程序自己生成html呢?当然可以,程序就是为将人类从重复繁杂的工作中解放出来而生的!
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!--->%sn' % time.ctime()
其实双重返回的设计思路很常见,比如在tcp四次挥手的过程中,第二次和第三次挥手都是服务器发送数据,客户端接收数据;
第二次服务端向客户端说("客户端,我收到你主动关闭本次连接的消息了!"),第三次服务端向客户端说("客户端,我已经关闭了这次的发送连接,不会给你发数据了,收到了记得回我个消息哈!");
也许有人会认为,既然第二次和第三次都是服务端向客户端发送数据,那应该可以将两条消息一起发送,但实际上,服务器关闭发送数据的通道是需要一定的时间的,如果第二次和第三次一起发送,客户端浏览器就不能在发送第一次消息后,及时确认消息是否送达,而在tcp连接中,及时"确认送达"是一件非常重要的事情!
实现源码
1.作者自己编写小型web服务器(以上篇 gevent实现静态web服务器为基础改写)
web_server.py
import 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 rn"
respond_header += "Content-Type: text/html; charset=utf-8rn"
respond_header = respond_header + "rn"
# 发送回应
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 rn"
dynamic_respond_header += "%s:%srn"%(headers[0][0], headers[0][1])
dynamic_respond_header += "rn"
# 将列表中的数据进行整理,转为可直接使用的"返回头"信息,然后存到类变量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.py
import 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 = "我是来自数据库的动态数据......"+'当前的时间为--->%sn' % 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 content
README
终端运行:
python3 web_server.py 8888 web_frame:app
目录样式


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/1873/viewspace-2804106/ ,如需转载,请注明出处,否则将追究法律责任。