手撸web框架
web框架是前端与数据库的中介。
在我们所常见的web中,一般情况下,一个应用不会只有一个首页界面,当顶级域名后有其他后缀时,会有其他的web界面,也就是将我们的网页应用拆分开来,通过不同的后缀可以访问不同的界面。如图:
博客园首页https://www.cnblogs.com/
随意点击网页中的一篇博客查看:
socket服务端
我们可以socket写一个服务端,然后通过http协议请求,通过浏览器来访问到服务端的内容。
然后可以通过辨认请求体的请求首行,来得知网址的后缀,通过不同后缀返回给我们的网页不同的内容。
请求体对比:
# 首页 http://127.0.0.1:8080
GET / HTTP/1.1
Host: 127.0.0.1:8080
。。。(一系列键值对)
Accept-Language: zh-CN,zh;q=0.9
/r/n
# 后缀一个/index http://127.0.0.1:8080/index
GET /index HTTP/1.1
。。。(一系列键值对)
Accept-Language: zh-CN,zh;q=0.9
/r/n
只要是同一个域名就可以访问到我的服务端,
而请求首行则会提供给我们请求方式,网址后缀(路由)协议类型
,基于路由做判断,即可分发不同的html内容,达到应用界面划分的效果。
import socket
server = socket.socket() # TCP UDP
server.bind(('127.0.0.1', 8080)) # IP PORT
server.listen(5) # 半连接池
while True:
sock, address = server.accept() # 等待连接
data = sock.recv(1024) # 字节(bytes)
# print(data.decode('utf8')) # 解码打印
sock.send(b'HTTP/1.1 200 OK\r\n\r\n') # 简易响应体,有响应首行和空头、换行
data_str = data.decode('utf8') # 先转换成字符串
target_url = data_str.split(' ')[1] # 按照空格切割字符串并取索引1对应的数据
# print(target_url) # /index /login /reg
if target_url == '/index':
# sock.send(b'index page')
with open(r'myhtml01.html','rb') as f: # 可以发送html文件
sock.send(f.read())
elif target_url == '/login':
sock.send(b'login page') # 可以直接发送二进制
else:
sock.send(b'home page!')
以上,有许多问题叩待解决:
- socket代码过于重复
- 针对请求数据处理繁琐
- 后缀匹配逻辑过于LowB
基于wsgiref模块封装优化
wsgiref是一个内置模块,它帮我们:
- 封装了socket代码
- 处理了请求数据
所有的请求首行、请求头所代表的信息被封装为一个字典,可以通过键值拿信息。
如我们的路由(网址后缀)就可以通过request.get('PATH_INFO')
来获取
关于wsgiref模块需要注意以下功能:
关键字 | 功能 |
---|---|
request | 封装好的请求体数据字典 |
response | 响应相关数据 |
make_server(ip,port,app) | 监听该端口,一旦有请求,会将request、response传给app函数执行 |
.serve_forever() | 将上一个功能返回的服务端对象开启,进行实时监听 |
用wsgiref搭建web
from wsgiref.simple_server import make_server
def run(request, response):
"""
:param request: 请求相关数据
:param response: 响应相关数据
:return: 返回给客户端的真实数据
"""
response('200 OK', []) # 固定格式 不用管它
# print(request) 是一个处理之后的大字典
path_info = request.get('PATH_INFO')
if path_info == '/index':
return [b'index']
elif path_info == '/login':
return [b'login']
return [b'hello wsgiref module']
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, run)
# 实时监听127.0.0.1:8080 一旦有请求过来自动给第三个参数加括号并传参数调用
server.serve_forever() # 启动服务端
仍然没有解决分页逻辑很low的问题。
说到处理if分支过多的问题,我们自然可以想到将路由和要返回的html页面的对应关系存起来,然后为了结构更加清晰,又可以将对应关系存到一个文件中urls.py
,html文件群放到文件夹templates
中。而原本启动服务端的核心功能就可以命名为view.py
视图文件。
分模块代码
# view.py
from wsgiref.simple_server import make_server
from urls import urls
import templates
import os
BASE_PATH = os.path.dirname(__file__)
TEMP_PATH = os.path.join(BASE_PATH, "templates")
def run(request, response):
response("200 OK", [])
path_info = request.get("PATH_INFO")
html_file = os.path.join(TEMP_PATH, "default.html")
for ur in urls:
if ur[0] == path_info:
html_file = ur[1]
html_file = os.path.join(TEMP_PATH, html_file)
print(html_file)
break
with open(html_file, 'rb') as f:
return [f.read()]
if __name__ == '__main__':
server = make_server("127.0.0.1", 8080, run)
server.serve_forever()
# urls.py
urls = [
("/index", "index.html"),
("/login", "login.html"),
]
# templates
default.html
index.html
login.html
# default.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>default</title>
</head>
<body>
<h1>404 not found</h1>
</body>
</html>
动静态网页
在我们平常接触的web中,一般都是动态网页,而我们上述所写的是静态网页
- 网页的html文件数据被写死了,无法通过程序修改即静态网页
- 网页的html文件中的数据来源于后端,每次请求都会让后端的数据组织到html中返回给用户的网页叫做动态网页。
为了做动态网页,我们应该想到,后端的数据来源于数据库,我们可以通过pymysql对接sql和python代码,我们还需要将python数据放到页面中。
jinja2模块处理
那么如何通过python代码向html文件中放入数据呢,这显然是一个复杂且重复性高的动作,我们可以通过第三方模块jinja2来相对方便的将python数据动态的嵌入到我们的html文件中。
接下来的程序中要用到jinja2的一些方法:
关键字 | 功能 |
---|---|
Template() | 获取字符串,可以是html文件内容 |
render | 将python数据以字典形式提交给html |
html中{} | render会识别这些符号进行一些操作。 |
jinja2导入数据到html文件
# view.py
from jinja2 import Template
def get_dict(request, data):
user_dict = {'name': 'jason', 'pwd': 123, 'hobby': 'read'}
new_list = [11, 22, 33, 44, 55, 66]
temp_obj = Template(data)
res = temp_obj.render({'user': user_dict, 'new_list': new_list}) # 将python数据提交给html文件
return res # 将处理好的文本文件返回
def run(request, response):
response("200 OK", [])
path_info = request.get("PATH_INFO")
html_file = os.path.join(TEMP_PATH, "default.html")
for ur in urls:
if ur[0] == path_info:
html_file = ur[1]
html_file = os.path.join(TEMP_PATH, html_file)
print(html_file)
break
with open(html_file, 'r', encoding='utf8') as f:
return [get_dict(request, f.read()).encode('utf8')] # 调用get_dict函数
if __name__ == '__main__':
server = make_server("127.0.0.1", 8080, run)
server.serve_forever()
# get_dict.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>get_dict</title>
</head>
<body>
<h1>字典数据展示</h1>
<p>{{ user }}</p> <!--对提交进来的数据进行应用 -->
<p>{{ user.name }}</p>
<p>{{ user['pwd'] }}</p>
<p>{{ user.get('hobby') }}</p>
<h1>列表数据展示</h1>
<p>
{% for i in new_list%}
<span>元素:{{ i }}</span>
{% endfor %}
</p>
</body>
</html>