框架,即framework,是解决一个开放性问题而设计的具有一定约束性的支撑结构。
DRP原则:Don't RePeat yourself。
web应用的流程:
- 浏览器发送一个HTTP请求
- 服务器收到请求,生成一个HTML文档
- 服务器把HTML文档作为HTTP响应的Body发送给浏览器
- 浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示
对于所有的Web应用,本质上其实就是一个socket服务端,用户浏览器其实就是一个socket客户端。
python实现上述过程,也就是模拟一个web服务器跟浏览器的交互过程:
import socket
def handle_request(client):
buf = client.recv(1024)
print(buf.decode("utf8"))
client.send("HTTP/1.1 200 OK\r\n\r\n".encode("utf8"))
client.send("<h1 style='color:red;'>Hello,people</h1>".encode("utf8"))
def main():
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.bind(("localhost",8001))
sock.listen(5)
while True:
connection,address = sock.accept()
handle_request(connection)
connection.close()
if __name__ == "__main__":
main()
python程序就是最简单的web服务器,绑定localhost和8001端口,等待客户端的访问,有客户端访问,就将发送HTTP响应头和响应体,这是符合HTTP协议的文本,浏览器取出响应体,在浏览器上呈现。
浏览器发送给服务器的内容:
GET /test/ HTTP/1.1
Host: 127.0.0.1:8001
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: csrftoken=a9rnZVOsgjI4HZjgDYeJnIsxs1DUVCoYrfmmiLWEPN7Mu0C8oDjyPCTrtLFtQRrU
对于这个程序,浏览器端只要主机地址和端口写准确,后面写什么都能访问到,端口后面的部分,这里是/test/,就可以作为不同响应内容的区分标志。
如果将:client.send("<h1 style='color:red;'>Hello,people</h1>".encode("utf8"))这一句,即要响应的内容写在一个文件中,并把这个文件命名为index.html,如下:
import socket
def handle_request(client):
buf = client.recv(1024)
print(buf.decode("utf8"))
client.send("HTTP/1.1 200 OK\r\n\r\n".encode("utf8"))
# client.send("<h1 style='color:red;'>Hello,people</h1>".encode("utf8"))
with open ("index.html","rb") as f:
data = f.read()
client.send(data)
def main():
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.bind(("localhost",8001))
sock.listen(5)
while True:
connection,address = sock.accept()
handle_request(connection)
connection.close()
if __name__ == "__main__":
main()
index.html如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 style='color:red;'>Hello,people in file</h1>
</body>
</html>
浏览器访问:
服务器端接收到的浏览器发送的信息:
GET /index.html HTTP/1.1
Host: 127.0.0.1:8001
Connection: keep-alive
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="99", "Google Chrome";v="99"
sec-ch-ua-mobile: ?0
。。。。。
这就成了一个最简单的web服务器了。可以根据浏览器端发来的第一行的GET或POST后的内容来区分不同的网页返回给浏览器就行了。
上述的过程,进行归纳抽取,可以形成如下几个部分:
1、是服务器部分,就是上面的sock,需要绑定地址跟端口,需要等待,这需要高的性能和高并发性,可以抽取成框架的一部分。
2、接收到的客户端信息即HTTP请求,需要能够解析,即解析浏览器发送的信息,如想获取host信息,想获取资源位置信息等。
3、向客户端发送响应头和响应体,需要符合HTTP协议的内容。
以上都可以抽取成框架的一部分,最后形成整个web服务器。
python有这样的一个接口就是WSGI:Web Server Gateway Interface,实际就是一个最简单的web框架:
from wsgiref.simple_server import make_server
def application(environ,start_response):
# 通过environ封装成一个所有请求信息及本机信息的对象,是字典对象
# start_response可以方便的设置响应头
start_response('200 OK',[('Content-Type','text/html')])
return [b'<h1>hello,web!</h1>']
# 封装socket对象以及准备过程(socket,bind,listen)
httpd = make_server('',8090,application)
print("Server HTTP on port 8090...")
# 开始监听HTTP请求
httpd.serve_forever()
这里make_server就完成了上面的sock的创建、绑定、监听过程,application用来格式化完成响应头的形成和响应体的反馈。
根据不同的请求资源,返回不同的页面内容:
from wsgiref.simple_server import make_server
def application(environ,start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
if environ["PATH_INFO"] == "/":
return [b'<h1>hello,index!</h1>']
elif environ["PATH_INFO"] == "/book":
return [b'<h1>hello,book!</h1>']
else:
return [b'<h1>404!</h1>']
httpd = make_server('',8090,application)
print("Server HTTP on port 8090...")
# 开始监听HTTP请求
httpd.serve_forever()
实际中一个网站会有很多不同页面,所以如果按照上面的写法,会有很长的if。。。else语句,而且,返回给客户端的内容也不会这么简单,所以程序解耦和规范化,如下:
第一步,使返回内容解耦,以一个单独函数进行返回内容的处理:
from wsgiref.simple_server import make_server
def f1(req):
print(req['PATH_INFO'],"根据f1的页面业务进行处理,形成返回内容")
return [b'<h1>hello,index!</h1>']
def f2(req):
print(req['PATH_INFO'], "根据f2的页面业务进行处理,形成返回内容")
return [b'<h1>hello,book!</h1>']
def application(environ,start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
if environ["PATH_INFO"] == "/":
return f1(environ)
elif environ["PATH_INFO"] == "/book":
return f2(environ)
else:
return [b'<h1>404!</h1>']
httpd = make_server('',8090,application)
print("Server HTTP on port 8090...")
# 开始监听HTTP请求
httpd.serve_forever()
第二步,处理if。。。else问题:
from wsgiref.simple_server import make_server
def f1(req):
print(req['PATH_INFO'],"根据f1的页面业务进行处理,形成返回内容")
return [b'<h1>hello,index!</h1>']
def f2(req):
print(req['PATH_INFO'], "根据f2的页面业务进行处理,形成返回内容")
return [b'<h1>hello,book!</h1>']
def f3(req):
print(req['PATH_INFO'], "根据f2的页面业务进行处理,形成返回内容")
return [b'<h1>hello,web!</h1>']
def f4(req):
print(req['PATH_INFO'], "根据f2的页面业务进行处理,形成返回内容")
return [b'<h1>hello,popo!</h1>']
def routers(): #形成访问路径与最终返回内容的映射元组
urlpatterns = (
("/",f1),
("/popo",f2),
("/web",f3),
("/book",f4)
)
return urlpatterns
def application(environ,start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
#下面用一个循环替代所有的if。。。else判断,增加新页面,只需在routers中增加相应元组和对应的处理函数即可
path = environ["PATH_INFO"]
urlpatterns = routers()
func = None
for item in urlpatterns:
if item[0] == path:
func = item[1]
break
if func:
return func(environ)
else:
return [b'<h1>404!</h1>']
# if environ["PATH_INFO"] == "/":
# return f1(environ)
# elif environ["PATH_INFO"] == "/book":
# return f2(environ)
# else:
# return [b'<h1>404!</h1>']
httpd = make_server('',8090,application)
print("Server HTTP on port 8090...")
# 开始监听HTTP请求
httpd.serve_forever()
第三步,关于解耦出的函数,进行文件内容替换,实际就是模板文件内容替换:这里实际上定义了一种新模板语言,用!!中间加变量进行替换
from wsgiref.simple_server import make_server
import time
def f1(req):
cur_time = time.ctime(time.time())
with open("index.html","rb") as f:
data = f.read()
return_data = data.decode("utf8").replace("!cur_time!",str(cur_time))
return [return_data.encode("utf8")]
def f2(req):
return [b'<h1>hello,book!</h1>']
def f3(req):
return [b'<h1>hello,web!</h1>']
def f4(req):
return [b'<h1>hello,popo!</h1>']
def routers(): #形成访问路径与最终返回内容的映射元组
urlpatterns = (
("/",f1),
("/popo",f2),
("/web",f3),
("/book",f4),
)
return urlpatterns
def application(environ,start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
#下面用一个循环替代所有的if。。。else判断,增加新页面,只需在routers中增加相应元组和对应的处理函数即可
path = environ["PATH_INFO"]
print(path)
urlpatterns = routers()
func = None
for item in urlpatterns:
if item[0] == path:
func = item[1]
print(item[1])
break
if func:
return func(environ)
else:
return [b'<h1>404!</h1>']
httpd = make_server('',8090,application)
print("Server HTTP on port 8090...")
# 开始监听HTTP请求
httpd.serve_forever()
f1中的替换动作,可以解耦出来,形成单独的模板渲染替换模块。
将上述文件进行彻底解耦,形成各自文件,并形成特定文件目录结构:
server.py文件是主服务器文件,负责整个服务器的配置与启动;urls.py用于资源路径与对应函数的对应关系配置,是起到路径分配作用,以后增加新的资源路径,在这个文件中增加即可;controller.py负责保存各个资源路径对应的处理函数,叫做控制器文件,控制不同资源路径的逻辑处理业务;model.py是与数据库交互的模块,未来的网页数据一般是来自数据库的;view目录用于保存各个资源路径所对应的页面模板。这样拆分下来,逻辑就非常清晰了。
# server.py 服务器主模块
from wsgiref.simple_server import make_server
from week14.website.urls import routers
def application(environ,start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
#下面用一个循环替代所有的if。。。else判断,增加新页面,只需在routers中增加相应元组和对应的处理函数即可
#===================================================
path = environ["PATH_INFO"]
urlpatterns = routers()
func = None
for item in urlpatterns:
if item[0] == path:
func = item[1]
print(item[1])
break
if func:
return func(environ)
else:
return [b'<h1>404!</h1>']
#==========这一部分实际就是控制器部分,与资源路径分配结合,控制处理的转向====
httpd = make_server('',8090,application)
print("Server HTTP on port 8090...")
# 开始监听HTTP请求
httpd.serve_forever()
# urls.py 资源路径分配模块, 专门负责资源路径与处理函数的对应关系
import week14.website.controller
from week14.website.controller import *
def routers(): #形成访问路径与最终返回内容的映射元组
urlpatterns = (
("/",f1),
("/popo",f2),
("/web",f3),
("/book",f4),
)
return urlpatterns
#controller.py控制器模块,专门用于存放各个资源路径对应的处理函数,起到控制器的作用
import time
def f1(req):
cur_time = time.ctime(time.time())
with open("view/index.html","rb") as f:
data = f.read()
return_data = data.decode("utf8").replace("!cur_time!",str(cur_time))
return [return_data.encode("utf8")]
def f2(req):
return [b'<h1>hello,book!</h1>']
def f3(req):
return [b'<h1>hello,web!</h1>']
def f4(req):
return [b'<h1>hello,popo!</h1>']
# model.py,数据库模块,实现与数据库交互的功能
class Db:
pass
MVC模式和MTV模式:
MVC模式:就是把web应用分为模型(M)、控制器(C)、视图(V)三层,它们之间以一种插件似得,松耦合的方式连接在一起。模型负责业务对象与数据库对象映射(ORM),视图负责与用户的交互(页面),控制器(C)接受用户的输入,调用模型和视图完成用户的请求。
MTV模型与MVC模型没有大的差别,只是定义有些许不同:
Model(模型):负责业务对象与数据库的对象(ORM)
Template(模板):负责如何把页面展示给用户
View(视图):负责业务逻辑,并在适当的时候调用Model和Template
RUL分发器,将一个个的页面请求分发给不同的VIEW处理,VIEW再调用相应的Model和Template
Python提供的Web框架叫做Django,使用的就是MTV模型,根据上面的定义,实际上是把controller叫做了View层,view目录叫做了模板Template层。