认识Web应用框架

Web应用框架

Web应用框架(Web application framework)是一种开发框架,用来支持动态网站、网络应用程序及网络服务的开发。类型可以分为基于请求(request-based)的和基于组件(component-based)的两种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,服务端接收到的请求数据什么

1578050012856

在此之前,首先学习下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://blog.csdn.net/laughing2333/article/details/51288660

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

    {%for user_dict in xxx %}
    
    {% endfor %}
    
序号 username password
{{ user_dict.id }} {{ user_dict.username }} {{ user_dict.password }}
*

了解一下

参照: https://baike.baidu.com/item/jinja2/8911090?fr=aladdin

http://docs.jinkan.org/docs/jinja2/

https://www.jianshu.com/p/f04dae701361

jinja2:基于python的模板引擎,其设计思想来源于django的模板引擎,并扩展了其语法和一系列强大的功能。其中最显著的一个是增加了沙箱执行功能和可选的自动转义功能。

特点:

  • 沙箱中执行
  • 强大的HTML自动转义系统,可以有效地组织跨站脚本攻击(XSS,利用网站漏铜从用户那里恶意盗取信息)
  • 模板继承机制,此机制可以使得所有的模板都具有相似一致的布局, 方便了开发人员对模板的修改和管理
  • 高效的执行效率,Jinja2引擎在模板第一次加载时就把源码转换成Python字节码,加快模板执行时间。
  • 可选的预编译模式
  • 易于调试。异常的行数直接指向模板中的对应行
  • 可配置的语法

语法

  • 控制结构 {% %}
  • 变量取值 {{ }}
jinja2模板中使用{{ }} 语法表示一个变量,是一种特殊的占位符。当利用jinja2进行渲染的时候,它会把这些特殊的占位符进行填充/替换,jinja2支持python中所有数据类型,如列表、字段、对象等
  • 注释 {# #}

你可能感兴趣的:(认识Web应用框架)