手撸web框架

手撸web框架

web框架是前端与数据库的中介。

在我们所常见的web中,一般情况下,一个应用不会只有一个首页界面,当顶级域名后有其他后缀时,会有其他的web界面,也就是将我们的网页应用拆分开来,通过不同的后缀可以访问不同的界面。如图:

博客园首页https://www.cnblogs.com/

image

随意点击网页中的一篇博客查看:

image

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>
posted @ 2022-12-08 18:39  leethon  阅读(44)  评论(0编辑  收藏  举报