web 应用框架
Web 应用本质上就是一个 socket 服务端, 用户的浏览器是一个 socket 客户端, 基于此, 可自定义一个简易版 Web 框架
- '''
- 自定义的简易版 Web 框架
- '''
- import socket
- from socket import SOL_SOCKET
- from socket import SO_REUSEADDR
- # 获取 socket 对象 server
- server = socket.socket()
- # 允许该端口可以多次运行
- server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
- # 设置服务端的 IP 和端口
- server.bind((
- '127.0.0.1',
- 9527
- ))
- # 设置半连接池
- server.listen(5)
- while True:
- # 等待客户端访问, 当前是阻塞状态, 直到有客户端访问
- conn, addr = server.accept()
- # 接收客户端发送的数据
- data = conn.recv(1024)
- # 打印客户端发送的请求数据
- print(data)
- # 向客户端发送响应的数据
- conn.send(b'hello world')
- # 关闭连接
- conn.close()
自定义的 Web 框架服务端已完成, 通过浏览器访问 127.0.0.1:9527, 会发现这个页面根本不能正常工作.
我们都知道 B/S 架构基于浏览器访问服务端的时候, 需要遵循 HTTP 协议, 因为 HTTP 协议规定了客户端和服务端之间的通信格式, 那么用户在浏览器输入 https:\\127.0.0.1:9527, 服务端接收到的请求数据什么
在此之前, 首先学习下 HTTP 协议
了解了 HTTP 协议后, 如果想要 server 端给予响应, 必须让 server 端在给客户端回复消息的时候按照 HTTP 协议的规则加上响应状态行
- while True:
- # 等待客户端访问, 当前是阻塞状态, 直到有客户端访问
- conn, addr = server.accept()
- # 接收客户端发送的数据
- data = conn.recv(1024)
- # 在向客户端发送响应数据前, 必须加上响应状态行
- # HTTP/1.1: 协议版本号 200 OK: 状态码, 200 表示客户端请求成功 \r\n\r\n: 空行
- conn.send(b'HTTP/1.1 200 OK \r\n\r\n')
- # 向客户端发送响应的数据, 即响应体
- conn.send(b'hello world')
- # 关闭连接
- conn.close()
加上响应状态行后, 重新访问 https:\\127.0.0.1:9527, 则浏览器可以收到服务端返回的 hello world
第一次优化
如果想要 Web 服务根据用户请求的 URL 不同, 返回不同的内容, 实现思路: 首先得拿到这个 URL, 然后根据 URL 判断, 进而返回不同的内容给浏览器
- while True:
- # 等待客户端访问, 当前是阻塞状态, 直到有客户端访问
- conn, addr = server.accept()
- # 接收客户端发送的数据
- data = conn.recv(1024)
- print(data)
- # 在向客户端发送响应数据前, 必须加上响应状态行
- conn.send(b'HTTP/1.1 200 OK \r\n\r\n')
- path_info = data.decode('utf-8').split('\r\n')[0].split()[1]
- print(path_info)
- # 根据不同的路径向客户端发送响应的数据
- if path_info == '/index':
- conn.send(b'from index')
- elif path_info == '/login':
- conn.send(b'from login')
- else:
- conn.send(b'404 error')
- # 关闭连接
- conn.close()
函数版
- # 访问 index 页面调用的函数
- def index(url):
- return f'from [{url}]file'
- # 访问 login 页面调用的函数
- def login(url):
- return f'from [{url}]file'
- # 创建访问页面路径和函数的对应关系
- url_list = [
- ('/index/', index),
- ('/login/', login)
- ]
- while True:
- # 等待客户端访问, 当前是阻塞状态, 直到有客户端访问
- conn, addr = server.accept()
- # 接收客户端发送的数据
- data = conn.recv(1024)
- # 在向客户端发送响应数据前, 必须加上响应状态行
- conn.send(b'HTTP/1.1 200 OK \r\n\r\n')
- path_info = data.decode('utf-8').split('\r\n')[0].split()[1]
- # 根据不同的路径向客户端发送响应的数据
- # if path_info == '/index':
- # conn.send(b'from index')
- # elif path_info == '/login':
- # conn.send(b'from login')
- # else:
- # conn.send(b'404 error')
- # 函数版
- func = None
- # 遍历访问页面路径是否在列表中
- for line in url_list:
- if line[0] == path_info:
- func = line[1]
- # 请求页面路径不在列表中, 返回 404 响应
- if not func:
- res = '404 error'
- else:
- res = func(path_info)
- conn.send(res.encode('utf-8'))
- # 关闭连接
- conn.close()
第二次优化
如果想要根据用户请求的 URL 不同, 返回不同的 html 页面
- # 访问 index 页面调用的函数
- def index():
- # 读取 index.HTML 页面中的内容
- with open(r'E:\Oldboy\python3\200103 自定义 Web 框架 \ templates\index.html',
- 'r', encoding='utf-8') as f:
- data = f.read()
- # return f'from [{url}]file'
- return data
- # 访问 login 页面调用的函数
- def login():
- with open(r'E:\Oldboy\python3\200103 自定义 Web 框架 \ templates\login.html',
- 'r', encoding='utf-8') as f:
- data = f.read()
- # return f'from [{url}]file'
- return data
- # 创建访问页面路径和函数的对应关系
- url_list = [
- ('/index/', index),
- ('/login/', login)
- ]
- while True:
- # 等待客户端访问, 当前是阻塞状态, 直到有客户端访问
- conn, addr = server.accept()
- # 接收客户端发送的数据
- data = conn.recv(1024)
- # 在向客户端发送响应数据前, 必须加上响应状态行
- conn.send(b'HTTP/1.1 200 OK \r\n\r\n')
- path_info = data.decode('utf-8').split('\r\n')[0].split()[1]
- # 根据不同的路径向客户端发送响应的数据
- # if path_info == '/index':
- # conn.send(b'from index')
- # elif path_info == '/login':
- # conn.send(b'from login')
- # else:
- # conn.send(b'404 error')
- # 函数版
- func = None
- # 遍历访问页面路径是否在列表中
- for line in url_list:
- if line[0] == path_info:
- func = line[1]
- # 请求页面路径不在列表中, 返回 404 响应
- if not func:
- res = '404 error'
- else:
- res = func()
- conn.send(res.encode('utf-8'))
- # 关闭连接
- conn.close()
第三次优化, 不再自己手动创建套接字服务端, 使用 wsgiref 模块
- from wsgiref.simple_server import make_server
- import urls
- import views
- def run(env, response):
- """
- :param env: 请求相关的所有数据
- :param response: 响应相关的所有数据
- :return: 浏览器能够接受的内容
- """ response('200 OK', [])
- # print(env) # 是一个字典, 其中 PATH_INFO 这个键值对存储的就是用户请求的 url
- target_url = env.get('PATH_INFO')
- # 定义变量, 存储可能匹配的函数, 不直接调用是因为有可能请求的路径不存在
- func = None
- for line in urls.url_list:
- if line[0] == target_url:
- # 符合条件, 说明后端已设置对应的页面数据, 赋给 func
- func = line[1]
- # 找到后, 跳出循环, 没必要继续查找
- break
- # 最后都没有找到, 说明请求无效
- if not func:
- # 调用 404 页面
- res = views.error(env)
- else:
- res = func(env)
- # 将响应页面的数据返回给浏览器, 展示给用户
- return [res.encode('utf-8')]
- if __name__ == '__main__':
- # 监听 host:port, 一旦有客户端 (浏览器) 访问, 立即执行第三个参数(可以是类)
- server = make_server('127.0.0.1', 9527, run)
- # 启动服务端
- server.serve_forever()
了解一下
参照:
- https://www.leiue.com/what-is-wsgi
- https://cizixs.com/2014/11/09/dive-into-wsgiref/
WSGI 和 wsgiref
WSGI(Web Server Gateway Interface) Web 服务器网关接口, 是专门为 Python 语言定义的 Web 服务器与 Web 应用程序或框架之间的一种简单而通用的接口
wsgiref 是一个实现了 WSGI 标准的范例实现(用于演示的简单 python 内置库), 里面的功能包含了: 1. 操作 wsgi 的环境变量; 2. 应答头部的处理; 3. 实现简单的 HTTP server;4. 简单的程序端和服务端校验函数
第四次优化 基于 jinja2 模块, 实现动态网页
- # 导入 jinja2 模块, 使用模板语法
- from jinja2 import Template
- import pymysql
- # 使用 jinja2 中的模板语法实现动态网页
- def userinfo(env):
- # 调用数据库中的记录
- conn = pymysql.connect(
- host='127.0.0.1',
- port=3306,
- user='root',
- password='Ad123',
- database='django_test',
- charset='utf8',
- autocommit=True
- )
- cursor = conn.cursor(pymysql.cursors.DictCursor)
- sql = 'select * from userinfo'
- cursor.execute(sql)
- # 返回结果集:[{},{},{}]
- data = cursor.fetchall()
- # 该函数返回一个 HTML 页面
- with open(r'E:\Oldboy\python3\200103 自定义 Web 框架 \ templates\userinfo.html',
- 'r', encoding='utf-8') as f:
- res = f.read()
- # 利用 jinja2
- tmp = Template(res)
- # 利用对象的 render 方法, 将从数据库的结果集传给 HTML 页面
- res = tmp.render(xxx=data)
- return res
- <table class="table table-striped table-hover">
- <thead>
- <tr>
- <th > 序号</th>
- <th>username</th>
- <th>password</th>
- </tr>
- </thead>
- <tbody>
- <!-- 通过 jinja2 的模板语法, 可以直接在 html 文档中使用 python 语法 -->
- <!-- 获取到数据集[{},{},{}], 遍历结果集 -->
- {%for user_dict in xxx %}
- <!-- 一个字典 (数据库的中一条数据) 就是一行记录 -->
- <tr>
- <!-- 字典中的键值对就是一列 -->
- <td>{{ user_dict.id }}</td>
- <td>{{ user_dict.username }}</td>
- <td>{{ user_dict.password }}</td>
- </tr>
- {% endfor %}
- </tbody>
- </table>*
了解一下
参照:
- http://docs.jinkan.org/docs/jinja2/
- https://www.jianshu.com/p/f04dae701361
jinja2: 基于 python 的模板引擎, 其设计思想来源于 django 的模板引擎, 并扩展了其语法和一系列强大的功能. 其中最显著的一个是增加了沙箱执行功能和可选的自动转义功能.
特点:
沙箱中执行
强大的 HTML 自动转义系统, 可以有效地组织跨站脚本攻击(XSS, 利用网站漏铜从用户那里恶意盗取信息)
模板继承机制, 此机制可以使得所有的模板都具有相似一致的布局, 方便了开发人员对模板的修改和管理
高效的执行效率, Jinja2 引擎在模板第一次加载时就把源码转换成 Python 字节码, 加快模板执行时间.
可选的预编译模式
易于调试. 异常的行数直接指向模板中的对应行
可配置的语法
语法
控制结构 {% %}
变量取值 {{ }}
jinja2 模板中使用{{ }} 语法表示一个变量, 是一种特殊的占位符. 当利用 jinja2 进行渲染的时候, 它会把这些特殊的占位符进行填充 / 替换, jinja2 支持 python 中所有数据类型, 如列表, 字段, 对象等
注释 {# #}
来源: https://www.cnblogs.com/xiaodan1040/p/12152236.html