上一节咱们实现了静态web服务器。 本节咱们来尝试实现动态web服务器
一. 动态web服务器
#1.什么是动态 web 服务器
#答: 静态web服务器,就是只能显示出,咱们事先写好的页面。恒古不变,除非咱们手动的去修改html
文件
动态web服务器,在我们没有修改html代码的等情况的前提下,页面的内容会随着时间的改变而改
变。 比如,网页上的时间,咱们刷新网页,你会发现,时间发生了改变。
#2.动态 web服务器处理的过程
#答: 1.浏览器发送请求给服务器。
2.首先,服务器进行解析请求,
其次,通过某种方法,处理请求,并将此时的页面信息放入到body中。
3.服务器将响应发送给浏览器。
4.浏览器呈现出此刻的页面
其实 动态web服务器 和 静态web服务器 区别就在于
动态web服务器需要通过某种方法去生成动态的网页
#3.如何生成动态的页面?
#答: 1.我们要理解,这个动态的意思,并不是你的页面会动。而是你的数据会根据时间的不同而改变。
说是动态,其实在那一瞬间其实还是静态的数据。 比如,在网页上显示此时的时间,会随时间的
改变而改变。
2.以显示时间为例,如果咱们服务器有个函数,可以实时的显示此刻的时间,并发送给浏览器显示
出来。作为浏览器,显示出来的页面,是不是随着时间的变化,显示的时间也在改变?
3.没错,生成动态的页面,实际上可以粗略的认为,服务器运行了一个程序,并把结果给放在body
中,发送给了浏览器。
#好的,简单的生成动态页面的原理知道了,过程也知道了,咱们现在来实现一下。
#
#4.第一次实现动态web服务器(以显示此刻时间为例)
# coding:utf-8
import socket
import time
from multiprocessing import Process
#设置服务器路径
HTML_ROOT_PATH = "./static"
class HTTPServer(object):
""""""
def __init__(self):
"""构造方法"""
self.webS = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
def start(self):
"""用于开启服务器的方法"""
self.webS.listen(5)
while True:
conn, addr = self.webS.accept()
client_p = Process(target = self.dealwithclient, args=(conn, addr))
client_p.start()
conn.close()
def dealwithclient(self, conn, addr):
"""实现显示用户指定的网页的方法"""
#1.接受客户端发来的请求
request_header = conn.recv(1024).decode("utf-8").splitlines()
for lines in request_header:
print(lines)
#2.进行筛选出请求的路径
path = request_header[0].split()[1]
# path ==> "/index.html"
#如果路径后缀为.py结尾,说明请求的是动态页面
if path.endswith(".py"):
#调用处理相应的函数,并获取数据
response_body = time.ctime()
response_header = "HTTP/1.1 200 OK \r\n"
response = response_header + "\r\n" + response_body
else:
#3.对路径进行判断
if path == "/":
path = HTML_ROOT_PATH+"/index.html"
else:
#先对路径切割再拼接
path = HTML_ROOT_PATH + path
response_body = ""
try:
#4.打开文件
with open(path, "r", encoding="utf-8") as f:
#设置响应头
response_header = "HTTP/1.1 200 OK \r\n"
for date in f:
response_body += date
except FileNotFoundError:
response_header = "HTTP/1.1 404 not found\r\n"
response_body = "
====sorry ,file not found====
"finally:
#进行响应头和body的拼接
response = response_header + "\r\n" + response_body
conn.send(response.encode("utf-8"))
#4.关闭套接字
conn.close()
def bind(self, ipaddr, port):
"""绑定服务器的IP和端口"""
self.webS.bind((ipaddr,port))
def main():
"""主函数"""
http_server = HTTPServer()
http_server.bind("localhost", 9999)
http_server.start()
if __name__ == "__main__":
main()
#进行思考:
# 1. 首先上面这个代码,当咱们访问的页面是localhost:9999/xxx.py 都只会触发到这个IF语句,
然后显示时间,这就有很大的局限性。需要进行改进,使得能呈现出咱们指定的程序返回的结果。
# 2. 新增加的代码,都放在服务器类中,一旦咱们实现第一步后,服务器类中代码太长,不适宜维护
咱们要将程序放在别的文件中,然后进行调用即可。
#5.改进上述两个问题后,第二次实现动态 web服务器
# coding:utf-8
import socket
import time
import sys
from multiprocessing import Process
#设置服务器路径
HTML_ROOT_PATH = "./static"
DYNAMIC_PATH = "./dynamic"
class HTTPServer(object):
""""""
def __init__(self):
"""构造方法"""
self.webS = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
def start(self):
"""用于开启服务器的方法"""
self.webS.listen(5)
while True:
conn, addr = self.webS.accept()
client_p = Process(target = self.dealwithclient, args=(conn, addr))
client_p.start()
conn.close()
def dealwithclient(self, conn, addr):
"""实现显示用户指定的网页的方法"""
#1.接受客户端发来的请求
response_body = ""
request_header = conn.recv(1024).decode("utf-8").splitlines()
for lines in request_header:
print(lines)
#2.进行筛选出请求的路径
path = request_header[0].split()[1]
# path ==> "/index.html"
#如果路径后缀为.py结尾,说明请求的是动态页面
if path.endswith(".py"):
#调用处理相应的函数,并获取数据
#咱们将每个新的程序都单独放在一个模块中
try:
#1.获取模块名,然后导入模块, 咱们需要使用一个方法 __import__()
m = __import__(path[1:-3])
response_body = m.application()
#假如中文乱码,那么需要在这儿设置content-type中设置charset为utf-8
response_header = "HTTP/1.1 200 OK \r\n" + "Content-Type:text/html;charset=utf-8 \r\n"
except ModuleNotFoundError:
#当没有这个程序的时候,走这儿,显示404
response_header = "HTTP/1.1 404 not found \r\n" + "Content-Type:text/html;charset=utf-8 \r\n"
response_body = "not found"
else:
#3.对路径进行判断
if path == "/":
path = HTML_ROOT_PATH+"/index.html"
else:
#先对路径切割再拼接
path = HTML_ROOT_PATH + path
try:
#4.打开文件
with open(path, "r", encoding="utf-8") as f:
#设置响应头
response_header = "HTTP/1.1 200 OK \r\n"
for date in f:
response_body += date
except FileNotFoundError:
response_header = "HTTP/1.1 404 not found\r\n"
response_body = "
====sorry ,file not found====
"#进行响应头和body的拼接
response = response_header + "\r\n" + response_body
conn.send(response.encode("utf-8"))
#4.关闭套接字
conn.close()
def bind(self, ipaddr, port):
"""绑定服务器的IP和端口"""
self.webS.bind((ipaddr,port))
def main():
"""主函数"""
#添加导入模块的路径
sys.path.insert(1,"./dynamic")
http_server = HTTPServer()
http_server.bind("localhost", 9999)
http_server.start()
if __name__ == "__main__":
main()
#在上述代码中,咱们使用了 __import__()这个魔术方法
# __import__(模块名) : 这个方法是用来动态的调用模块,为啥是动态的呢,因为模块名是不固定的
有可能是time 也有可以是sys 。
__import__(sys) 相当于 import sys
并且返回一个值,用一个变量进行接收,之后对模块的操作,就是通过这个
变量。 比如a = __import__(sys) a.path 相当于 sys.path
#还有一点,为什么咱们写的两个程序,里面的函数名都要为相同的名字呢?
#答:这是为了主程序更方便的去调用他们,相同的函数名,就不用担心不同模块中的函数名不同而要进行
判断。
#粗略的看上述代码,发现已经能够很好的实现了需求,但是,如果这就满意了,那么你将不是一个合格
程序猿。 因为,上面的程序,只是咱们随心临时写出来的,书写规范等都还不是很好,所以,咱们要
严格的按照协议去进行修改。
#那么这个协议是什么协议呢?
二.WSGI协议
WSGI:(Web Server Gateway Interface)web服务器网关接口,他是服务器和web应用程序或者框架
的一种接口。 在早期的时候,咱们使用的框架不同,服务器的实现也有可能不同,这样使得开发
非常的艰难,因此为了让服务器和框架能够混合匹配,就产生了WSGI这个东西。
别看他称为web服务器网关接口,实际上,他在设计之初并未用严格的代码实现,他更像是一种协议
你只要满足他的协议,那么你的WSGI引用就能够在任何服务器上运行。
#那么我们如何来实现这个WSGI接口呢?
#解答: 它只要求Web开发者实现一个函数即可,函数的具体格式如下
def application(environ, start_response):
start_response("200 ok", [("Content-Type","text/html")])
return "response_body"
首先函数名 最好都设置成 application
其次两个参数:1. environ这个参数代表着 浏览器发送过来的请求头数据。值的格式为字典(dict)类型
2. start_response 这个参数是一个函数的引用,是用来发送服务器响应的函数。
然后start_response这个函数接收两个参数: 1. 第一个为状态码和状态的说明
2.第二个为响应头,值的格式为[(header1,header1_value),
(header2,header2_value)
...]
最后,application函数的返回值是作为响应的body。也就是常说的response_body
特别的,那么这个application函数由谁调用呢? 肯定是让WSGI服务器来调用了,因为还要传请求头
和发送响应的函数引用。
那么WSGI服务器哪里找呢? 其实我们可以进行实现,WSGI服务器有很多,咱们只要好好的按照协议,让
WSGI接口完成任务即可。
#按照wsgi协议进行修改代码:
# coding:utf-8
import socket
import time
import sys
from multiprocessing import Process
#设置服务器路径
HTML_ROOT_PATH = "./static"
DYNAMIC_PATH = "./dynamic"
class HTTPServer(object):
""""""
def __init__(self):
"""构造方法"""
self.webS = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
def start(self):
"""用于开启服务器的方法"""
self.webS.listen(5)
while True:
conn, addr = self.webS.accept()
client_p = Process(target = self.dealwithclient, args=(conn, addr))
client_p.start()
conn.close()
def dealwithclient(self, conn, addr):
"""实现显示用户指定的网页的方法"""
#1.接受客户端发来的请求
response_body = ""
request_header = conn.recv(1024).decode("utf-8").splitlines()
for lines in request_header:
print(lines)
#2.进行筛选出请求的路径
path = request_header[0].split()[1]
# path ==> "/index.html"
#如果路径后缀为.py结尾,说明请求的是动态页面
if path.endswith(".py"):
#调用处理相应的函数,并获取数据
#咱们将每个新的程序都单独放在一个模块中
try:
#1.获取模块名,然后导入模块, 咱们需要使用一个方法 __import__()
m = __import__(path[1:-3])
response_body = m.application({},self.start_response)
except ModuleNotFoundError:
#2.当没有这个程序的时候,走这儿,显示404
self.response_header = "HTTP/1.1 404 not found \r\n" + "Content-Type:text/html;charset=utf-8 \r\n"
response_body = "not found"
finally:
#3.拼接整个响应头
response = self.response_header + "\r\n" + response_body
else:
#3.对路径进行判断
if path == "/":
path = HTML_ROOT_PATH+"/index.html"
else:
path = HTML_ROOT_PATH + path
try:
#4.打开文件
with open(path, "r", encoding="utf-8") as f:
#设置响应头
response_header = "HTTP/1.1 200 OK \r\n"
for date in f:
response_body += date
except FileNotFoundError:
response_header = "HTTP/1.1 404 not found\r\n"
response_body = "
====sorry ,file not found====
"finally:
#进行响应头和body的拼接
response = response_header + "\r\n" + response_body
conn.send(response.encode("utf-8"))
#4.关闭套接字
conn.close()
def bind(self, ipaddr, port):
"""绑定服务器的IP和端口"""
self.webS.bind((ipaddr,port))
def start_response(self, status, response_header):
"""status : 状态码
respense_header : [(,),()...]
"""
#第一行响应头
self.response_header = "HTTP/1.1 " + status + "\r\n"
#拼接后面的响应头
for header, value in response_header:
self.response_header += header + ":" + value + "\r\n"
def main():
"""主函数"""
#添加导入模块的路径
sys.path.insert(1,"./dynamic")
http_server = HTTPServer()
http_server.bind("localhost", 9999)
http_server.start()
if __name__ == "__main__":
main()
#通过上面的实现,已经符合了WSGI协议了。 但是这样依旧不太完美,因为,如果想要把这个程序打包
给他人使用的话:
首先,实现动态页面的程序,必须放置在dynamic这个目录下。
其次,当用户有新的需求,就得写一个新的py程序,来生成实时数据,而且格式都像ctime2.py这个文件
一样。这就造成了重复累赘。
最后,咱们应该将程序代码进行优化,除去重复代码。
#所以呢,咱们综上所述,咱们可以开发一个框架,来解决上述的问题。
#框架(利用面向对象)代码的实现:
#首先是HTTPServer服务器:
------------------------
dynamic_web_server.py
------------------------
# coding:utf-8
import socket
import sys
import my_web_framework
from multiprocessing import Process
#设置服务器路径
HTML_ROOT_PATH = "./static"
DYNAMIC_PATH = "./dynamic"
class HTTPServer(object):
""""""
def __init__(self):
"""构造方法"""
self.webS = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
def start(self):
"""用于开启服务器的方法"""
self.webS.listen(5)
while True:
conn, addr = self.webS.accept()
client_p = Process(target = self.dealwithclient, args=(conn, addr))
client_p.start()
conn.close()
def dealwithclient(self, conn, addr):
"""实现显示用户指定的网页的方法"""
#1.接受客户端发来的请求
response_body = ""
request_header = conn.recv(1024).decode("utf-8").splitlines()
for lines in request_header:
print(lines)
#2.进行筛选出请求的路径
path = request_header[0].split()[1]
# path ==> "/index.html"
response_body = my_web_framework.application({"path": path}, self.start_response)
response = self.response_header + "\r\n" + response_body
conn.send(response.encode("utf-8"))
#4.关闭套接字
conn.close()
def bind(self, ipaddr, port):
"""绑定服务器的IP和端口"""
self.webS.bind((ipaddr,port))
def start_response(self, status, response_header):
"""status : 状态码
respense_header : [(,),()...]
"""
#第一行响应头
self.response_header = "HTTP/1.1 " + status + "\r\n"
#拼接后面的响应头
for header, value in response_header:
self.response_header += header + ":" + value + "\r\n"
def main():
"""主函数"""
#添加导入模块的路径
sys.path.insert(1,"./dynamic")
http_server = HTTPServer()
http_server.bind("localhost", 9999)
http_server.start()
if __name__ == "__main__":
main()
接下来是框架代码:
----------------------
my_web_framework.py
----------------------
import time
#当用户有新的需求,那么只要在url中添加新的映射关系, 路径名 : 函数名
#以及在本程序中添加新的函数即可。
HTML_ROOT_PATH = "./static"
class Application(object):
"""框架的主体"""
def __init__(self, url):
""""""
self.url = url
def __call__(self, environ, start_response):
"""
environ: 值为请求头,dict类型数据
start_response: 返回响应头的函数
return: 返回的是response_body
"""
#获取请求的路径名
path = environ.get("path")
#对请求静态Or动态页面进行判断
if self.url.get(path) != None:
#如果在框架中,说明是动态页面,首先拼接响应头
start_response("200 ok ",[("Content-Type","text/html;charset=utf-8")])
#最后是获取响应body
filename = self.url.get(path)
return filename()
#如果能执行到这,说明是静态页面 或者页面不存在
response_body = ""
if path == "/":
path = HTML_ROOT_PATH+"/index.html"
else:
path = HTML_ROOT_PATH + path
try:
#4.打开文件
with open(path, "r", encoding="utf-8") as f:
#设置接受数据的变量
for date in f:
response_body += date
except FileNotFoundError:
start_response("404 not found",[("Content-Type","text/html;charset=utf-8")])
return "找不到相应页面----not found"
else:
start_response("200 ok ",[("Content-Type","text/html;charset=utf-8")])
return response_body
def ctime2():
""""""
return "ctime2显示的时间为:" + time.ctime()
def ctime():
""""""
return "ctime显示的时间为:" + time.ctime()
#url映射,框架中有的函数,都在这url中能找到
url = {
"/ctime2.py" : ctime2,
"/ctime.py" : ctime,
}
application = Application(url)
#特别说明: 在框架中还使用到了一个 __call__()内置方法。
__call__(): 当我们实例出一个类对象,如果将对象当成函数一样使用,那么实际上是调用__call__方法
比如 上图中的 Application类。
application = Application()
application() ---> 就是调用 application.__call__()
#WSGI这个协议,在以后咱们学习django 模块的时候还能够接触到,实际的原理就是咱们上面写的代码。
三.总结:
浏览器请求动态页面的过程: