自定义搭建简易web框架

1. web框架介绍

我们可以将所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。

2. HTTP协议

而服务端和客户端通信时都需要符合一个统一的规则,就是HTTP协议(超文本传输协议),其是一个简单的请求-响应协议,它通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。HTTP是基于B/S架构进行通信的,其客户端的实现程序主要是Web浏览器

通信流程
(1)客户与服务器建立连接;
(2)客户向服务器提出请求;
(3)服务器接受请求,并根据请求返回相应的文件作为应答;
(4)客户与服务器关闭连接。

请求的格式
在这里插入图片描述
响应的格式
在这里插入图片描述

3. 自定义框架

框架雏形

在这里主要实现的是将数据发送到浏览器中,而服务端要响应的话必须要符合响应的格式。

示例
		import socket
		
		sk = socket.socket()
		sk.bind(("127.0.0.1", 8888))
		sk.listen(5)
		
		while True:
		    sock, addr = sk.accept()
		    data = sock.recv(1024)
		    # 打印浏览器请求相关数据
		    print(data)
		    # 给响应的消息加上响应状态行
		    sock.send(b"HTTP/1.1 200 OK\r\n\r\n")
		    sock.send(b'hello')
		    sock.close()


此时在浏览器中输入 http://127.0.0.1:8888/ 就能看到 hello 了。类似的,不仅仅能响应字符串,也能将HTML网页
响应过去,使用with语句。

进阶路由

这里主要实现的是在http://127.0.0.1:8888/后加入不同的路由,可以展示不同的内容

在初版示例中打印了浏览器请求的相关数据,其是一串二进制数据,大致如下所示

1. 访问 http://127.0.0.1:8888/ 打印的请求数据:
		b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8888\r\n.....

2. 访问 http://127.0.0.1:8888/login 打印的请求数据:
		b'GET /login HTTP/1.1\r\nHost: 127.0.0.1:8888\r\n.....

3. 访问 http://127.0.0.1:8888/register 打印的请求数据: 
		b'GET /register HTTP/1.1\r\nHost: 127.0.0.1:8888\r\n.....


很明显,不同访问路径都会在url后展示出来,我们可以对其进行如下示例的操作
示例

		import socket
		
		sk = socket.socket()
		sk.bind(("127.0.0.1", 8888))
		sk.listen(5)
		
		# 创建登录时执行的函数
		def login():
		    return 'login'
		
		# 创建注册时执行的函数
		def register():
		    return 'register'
		
		# 创建资源不存在时执行函数
		def error():
		    return '404 error'
		
		
		while True:
		    sock, addr = sk.accept()
		    data = sock.recv(1024)
		    # 将请求数据转为字符串格式
		    str_data = data.decode('utf8')
		    # 使用切割获取字符串数据中用户输入的访问路径
		    data_url = str_data.split(' ')[1]
		    # 给响应消息添加响应状态码
		    sock.send(b"HTTP/1.1 200 OK\r\n\r\n")
		    # 分别判断用户输入的访问路径并调用不同的函数处理
		    if data_url == '/login':
		        res = login()
		    elif data_url == '/register':
		        res = register()
		    else:
		        res = error()
			
			# 将函数的返回值发送给浏览器
		    sock.send(res.encode('utf8'))
		    sock.close()


现在就能根据用户输入的不同访问路径去展示不同的界面与内容了,但是可以对其代码进行优化

优化代码

这里的优化是将函数与路由对应关系给整合起来,现在只是在同一个py文件,其实可以将函数、关系、HTML文件等等单独开辟出来解耦合

示例

		import socket
		
		sk = socket.socket()
		sk.bind(("127.0.0.1", 8888))
		sk.listen(5)
		
		def login():
		    return 'login'
		def register():
		    return 'register'
		def error():
		    return '404 error'
		
		# 将函数与用户访问地址的关系整合成列表
		fun_list = [
		    ('/login', login),
		    ('/register', register),
		]
		
		while True:
		    sock, addr = sk.accept()
		    data = sock.recv(1024)
		    str_data = data.decode('utf8')
		    data_url = str_data.split(' ')[1]
		    sock.send(b"HTTP/1.1 200 OK\r\n\r\n")
		    res = None
		    # for 循环判断
		    for i in fun_list:
		        if data_url == i[0]:
		            res = i[1]()
		            # 找到后即可退出循环节省资源
		            break
		        else:
		        	# 若不存在调用error函数
		            res = error()
		    sock.send(res.encode('utf8'))
		    sock.close()



4. wsgiref 模块

除了在上面自己使用socket来编写,还可以使用 wsgiref 模块来编写代替。

示例
		
		from wsgiref import simple_server
		
		
		def login():
		    return 'login'		
		def register():
		    return 'register'		
		def error():
		    return '404 error'
		
		# 对应的关系		
		fun_list = [
		    ('/login', login),
		    ('/register', register),
		]
				
		def run(request, response):
		    """
		    :param request:     请求相关的数据
		    :param response:    响应相关的数据
		    :return:            返回给客户端的展示数据
		    """
		    # print(request) 字典类型的数据,模块自动处理HTTP请求数据,便于后续数据获取
		    response('200 OK', [])  # 固定编写,响应状态码
		    current_path = request.get("PATH_INFO")  # 获取用户输入的url
		    func_name = None
		    for i in fun_list:
		        if current_path == i[0]:
		            func_name = i[1]
		            break
		    if func_name:
		        res = func_name()
		    else:
		        res = error()
		    return [res.encode('utf8')]
				
		if __name__ == '__main__':
		    server = simple_server.make_server('127.0.0.1', 8888, run)
		    '''监听本机8888端口,一旦有请求访问,自动触发run方法的执行'''
		    server.serve_forever()



5. 优化结构

在上面的基础上,可以将其拆分,便于管理。

可以创建以下文件

		urls.py 			用于对应关系的存储
		views.py			用于业务逻辑的编写
		templates文件夹		用于存储项目所需的html文件
		static文件夹			用于存储项目所需的静态文件

如果要新增功能,只需要在urls.py中添加对应关系,view.py中编写函数即可,同时也可以将浏览器请求的数据当作参数
传给视图函数		
urls.py
		后缀与函数名对应关系
		后缀专业名词称之为'路由'
		函数名专业名词称之为'视图函数'
		urls.py专业名词称之为'路由层'

views.py
		专门编写业务逻辑代码
		可以是函数 也可以是类
		函数专业名词称之为'视图函数'
		类专业名词称之为'视图类'
		views.py文件专业名词称之为'视图层'

templates文件夹
		专门存储html文件
		html文件专业名词称之为'模板文件'
		templates文件夹专业名词称之为'模板层'
		

6. jinja2模块

该模块需要手动下载,pip install jinja2,其模板中的特殊占位符允许编写类似于 Python 语法的代码。然后向模板传递数据以呈现最终文档。简单说就是可以在HTML文档中使用类似后端的代码

基本使用

1. 最基本的方式就是通过 Template() 创建一个模板并渲染它。
2. 创建了 Template 后会得到模板对象,使用一个名为 render() 的方法,该方法在有字典或关键字参数时调用扩充模
   板使其被传递到模板,即模板“上下文”

示例

'启动文件'
		
		from wsgiref import simple_server
		import urls
		from views import error
		
		
		def run(request, response):
		    """
		    :param request:     请求相关的数据
		    :param response:    响应相关的数据
		    :return:            返回给客户端的展示数据
		    """
		    # print(request) 字典类型的数据,模块自动处理HTTP请求数据,便于后续数据获取
		    response('200 OK', [])  # 固定编写,响应状态码
		    current_path = request.get("PATH_INFO")  # 获取用户输入的url
		    func_name = None
		    for i in urls.fun_list:
		        if current_path == i[0]:
		            func_name = i[1]
		            break
		    if func_name:
		        res = func_name()
		    else:
		        res = error()
		    return [res.encode('utf8')]
		
		
		if __name__ == '__main__':
		    server = simple_server.make_server('127.0.0.1', 8888, run)
		    '''监听本机8888端口,一旦有请求访问,自动触发run方法的执行'''
		    server.serve_forever()

'view.py 文件'

		from jinja2 import Template
		import pymysql
		
		
		def index():
			# 创建MySQL套接字对象
		    conn_obj = pymysql.connect(
		        host='127.0.0.1',
		        port=3306,
		        user='root',
		        password='xwx668428',
		        database='d3',
		        charset='utf8'
		    )
		    # 创建游标对象
		    cursor = conn_obj.cursor(
		        cursor=pymysql.cursors.DictCursor
		        # 括号内不写参数时展示的数据是元组类型,不够精确,可以添加参数使其将数据处理成字典
		    )
		    # SQL语句
		    sql1 = 'select * from student;'
		
		    # 执行SQL语句,其中返回值 affect_rows 代表的是受影响的行数
		    affect_rows = cursor.execute(sql1)
		    # 获取结果集
		    sql_data = cursor.fetchall()
			# 创建字典于列表
		    user_dict = {'name': 'xxx', 'pwd': 123, 'age': 12}
		    user_list = [1, 2, 3, 4, 5]
		    # 打开HTML页面
		    with open('first.html', 'r', encoding='utf8') as f:
		        data = f.read()
		    # 将HTML对象创建成模板
		    temp_obj = Template(data)
		    # 将参数传递给模板
		    res = temp_obj.render({'user': user_dict, 'list': user_list, 'sql': sql_data})
		    return res
		
		
		def error():
		    return 'error'

'urls.py 文件'
		
		from views import *
		# 表示视图函数与路由的关系
		fun_list = [
		    ('/index', index),
		]

'first.html 文件'

		<!DOCTYPE html>
		<html lang="en">
		<head>
		    <meta charset="UTF-8">
		    <title>Title</title>
		    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">
		</head>
		<body>
		<div class="container">
		    <p>传入的字典: {{ user }}</p>
		    <p>名字: {{ user.name }}</p>
		    <p>密码: {{ user['pwd'] }}</p>
		    <p>年龄: {{ user.get('age') }}</p>
		    <p>传入的列表: {{ list }}</p>
		    <p>循环列表取值:
		        {% for i in list %}
		        <span>{{ i }}</span>
		        {% endfor %}
		    </p>
		
		    <div class="col-md-8 col-md-offset-02">
		        <table class="table table-hover table-striped">
		            <thead>
		            <tr>
		                <th>学号</th>
		                <th>性别</th>
		                <th>班级号</th>
		                <th>名字</th>
		            </tr>
		            </thead>
		            <tbody>
		            {% for j in sql%}
		            <tr>
		                <td>{{ j.sid }}</td>
		                <td>{{ j.gender }}</td>
		                <td>{{ j.class_id }}</td>
		                <td>{{ j.sname }}</td>
		                <td>
		                    <a href="" class="btn btn-xs btn-primary">编辑</a>
		                    <a href="" class="btn btn-xs btn-danger">删除</a>
		                </td>
		            </tr>
		            {% endfor %}
		            </tbody>
		        </table>
		    </div>
		</div>
		</body>
		</html>


在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值