WEB
框架的本质
最基本的需求实现
我们在浏览器中输入一个
url
,就可以访问到数据。这件事情的本质是什么?本质上,就是基于
socket
实现的数据交互,浏览器是socket
的客户端,发送一个请求给指定的socket
服务端
我们已经学习过了python
的网络编程,那我们就利用python
实现一个socket
的服务端
创建一份叫做manage.py
的文件,在其中书写代码
import socket
# 1、买手机
server = socket.socket()
# 2、绑定手机卡
server.bind(('127.0.0.1', 8000)) # 0-65535:0-1024给操作系统使用
# 3、开机
server.listen()
while True:
# 4、等电话链接
conn, client_addr = server.accept()
# 6、收,发消息
data = conn.recv(1024) # 1、单位:bytes 2、1024代表最大接收1024个bytes
print('客户端的数据', data)
conn.send(b'HELLO')
# 7、挂电话
conn.close()
# # 8、关机
server.close()
通过python
运行文件的方式将这个程序运行起来
python manage.py
在浏览器的导航栏中输入127.0.0.1:8000
发现出现问题
出现问题的原因分析
用户的浏览器一输入网址,会给服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,这互联网还能玩么?
所以,必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。
这个规则就是
HTTP
协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来每个
HTTP
请求和响应都遵循相同的格式,一个HTTP
包含Header
和Body
两部分,其中Body
是可选的
HTTP
请求的格式
HTTP
响应的格式
浏览器本质上是socket
的客户端,会对接无数的socket
服务端,所以制定了一系列的规则,如HTTP
协议,出现错误的原因是我们的socket
服务端返回数据没有遵守HTTP
协议的规范,所以浏览器不识别才报错,因此我们需要改写一下我们的代码,让socket
服务端遵守HTTP
协议的规定,让浏览器能够正常的识别和展示
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen()
while True:
conn, client_addr = server.accept()
data = conn.recv(1024)
print('客户端的数据', data)
# 服务端响应的数据需要符合HTTP响应格式
conn.send(b'HTTP1.1 200 OK\r\n\r\n') # 新增一行代码
conn.send(b'HELLO')
conn.close()
server.close()
此时,浏览器正常展示内容
到此为止,我们实现了一个最基本版本的需求
根据不同的URL路径返回不同的内容
最基本的需要实现了,但我们看到的网站都是url
路径不同,返回的内容也不一样的,接下来就要实现这个需求
思路:我们可以利用客户端发送过来的数据做文章,从请求相关数据中心拿到请求的
url
,再做判断
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen()
while True:
conn, client_addr = server.accept()
data = conn.recv(1024)
data = data.decode().split('\r\n') # 进行切割
url = data[0].split(' ')[1] # 拿到了url
conn.send(b'HTTP1.1 200 OK\r\n\r\n')
# 根据请求的url不同做判断,返回不同的内容
if url == '/index':
response = 'index'
elif url == '/test':
response = 'test'
else:
response = '404 Not Found!'
# 返回指定的内容
conn.send(response.encode())
conn.close()
server.close()
当我们的URL
请求越来越多,要写的判断越来越多,我们可以利用函数做优化
import socket
# url对应的请求方法
def index(request):
return 'index'
def test(request):
return 'test'
# 定义一个方法 当所有的请求都找不到的时候,就返回错误
def handler404(request):
return '404 Not found!'
# 定义一个对应关系
urlpatterns = [
('/index',index),
('/test',test),
]
server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen()
while True:
conn, client_addr = server.accept()
data = conn.recv(1024)
data = data.decode().split('\r\n') # 进行切割
url = data[0].split(' ')[1] # 拿到了url
conn.send(b'HTTP1.1 200 OK\r\n\r\n')
# 遍历列表,根据请求的url来执行对应的处理函数
for line in urlpatterns:
if url == line[0]:
response = line[1](data)
break
else:
response = handler404(data)
# 返回指定的内容
conn.send(response.encode())
conn.close()
server.close()
随着url
和处理方法越来越多,我们这份文件也变得越来越大,我们可以利用模块对功能进行拆分,放到不同的模块中去,实现解耦合
# manage.py
import socket
from urls import urlpatterns # 导入urls模块中的urlpatterns
# 定义一个方法 当所有的请求都找不到的时候,就返回错误
def handler404(request):
return '404 Not found!'
server = socket.socket()
server.bind(('127.0.0.1', 8000))
server.listen()
while True:
conn, client_addr = server.accept()
data = conn.recv(1024)
data = data.decode().split('\r\n') # 进行切割
url = data[0].split(' ')[1] # 拿到了url
conn.send(b'HTTP1.1 200 OK\r\n\r\n')
# 遍历列表,根据请求的url来执行对应的处理函数
for line in urlpatterns:
if url == line[0]:
response = line[1](data)
break
else:
response = handler404(data)
# 返回指定的内容
conn.send(response.encode())
conn.close()
server.close()
# urls.py
import views # 导入views.py
# 定义一个对应关系
urlpatterns = [
('/index',views.index),
('/test',views.test),
]
# views.py
# url对应的请求方法
def index(request):
return 'index'
def test(request):
return 'test'
到目前为止,我们发现,以后我们需要添加更多的请求路径就去urls.py
中添加一个对应关系,然后去views.py
中添加处理的函数
服务器程序和应用程序
目前我们发现一个问题,我们自己写的socket
部分是不完善的,很多问题没解决,如针对HTTP
请求数据没有完善的处理方式。
对于真实开发中的web
程序来说,一般会分为两部分:服务器程序和应用程序
服务器程序负责对socket
服务器进行封装,并在请求到来时,对请求的各种数据进行整理
应用程序则负责具体的逻辑处理,为了方便应用程序的开发,就出现了众多的Web框架,不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务
这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。
这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI(Web Server Gateway Interface)
就是一种规范,它定义了使用Python
编写的web
应用程序与web
服务器程序之间的接口格式,实现web
应用程序与web
服务器程序间的解耦。
常用的WSGI
服务器有uwsgi
、Gunicorn
。而Python
标准库提供的独立WSGI
服务器叫wsgiref
我们利用wsgiref
模块来替换我们自己写的web
框架的socket server
部分
from wsgiref import simple_server
def run(request, response):
"""
:param request: 请求相关的数据
:param response: 响应相关的数据
:return: 返回给客户端的展示数据
"""
response('200 OK', []) # 固定编写 无需掌握
return [b'hello']
if __name__ == '__main__':
server = simple_server.make_server('127.0.0.1', 8080, run)
'''监听本机8080端口 一旦有请求访问 自动触发run方法的执行'''
server.serve_forever()
# 模块封装了socket代码并将请求数据处理成诸多k:v键值对
# manage.py
from wsgiref.simple_server import make_server
from urls import urlpatterns
def handler404(request):
return '404 Not found!'
def run(data,start_response):
# 设置HTTP响应的状态码和头信息
start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])
url = data['PATH_INFO']
for line in urlpatterns:
if url == line[0]:
response = line[1](data)
break
else:
response = handler404(data)
return [response.encode()]
if __name__ == '__main__':
server = make_server('127.0.0.1',8000,run)
server.serve_forever()
实现返回HTML
页面
返回html
页面,本质就是打开一个html
文件,读取文件内容并返回
# views.py
def index(request):
# 为了方便管理,我们将所有的html页面放在一个叫做templates的文件夹里面
with open('templates/index.html','r',encoding='utf-8') as f:
data = f.read()
return data
创建一个叫做templates
的文件夹,在其中创建index.html
文件即可!
动态渲染数据
但是我们的数据是写死的,怎么才能在html
页面中实现动态的数据展示呢
思路: 读取
HTML
文件就是字符串,利用字符串的替换功能即可实现
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>我是index.html页面!</h1>
<h2>现在的随机数是:{{ a }}</h2>
</body>
</html>
# views.py
import random
def index(request):
with open('templates/index.html','r',encoding='utf-8') as f:
data = f.read()
# 定义一个随机数
num = str(random.randint(1,99))
# 利用Python字符串的替换,将指定的符号换成我们真实要展示的数据
return data.replace('{{ a }}',num)
上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html
中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML
模板渲染数据。 本质上就是HTML
内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2
pip install jinja2
# views.py
import random
from jinja2 import Template
def index(request):
with open('templates/index.html','r',encoding='utf-8') as f:
data = f.read()
template = Template(data) # 生成模板文件
str1 = '我是字符串'
list1 = ['我','爱','你']
dict1 = {'name':'老杨','age':18}
res = template.render(locals()) # 渲染数据
return res
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>我是index.html页面!</h1>
<h2>{{ str1 }}</h2>
<h2>{{ dict1['name'] }}</h2>
<h2>{{ dict1.age }}</h2>
{% for x in list1 %}
<p>{{ x }}</p>
{% endfor %}
</body>
</html>
总结
至此,我们已经实现了一个简易的web
框架,我们只需要在urls.py
文件中书写请求路径和处理函数的对应关系,并在views.py
中书写处理函数,利用字符串的替换可以实现动态的数据渲染效果
manage.py
是我们程序的入口文件,利用这个文件来运行整个程序urls.py
是我们的路由文件,里面书写url
和处理函数的对应关系views.py
是我们的视图文件,里面书写url
的处理函数,必须接受一个形参,这个形参里面是请求相关数据