手写简易版web框架

Web框架

Web应用框架(Web application framework)是一种开发框架,用来支持动态网站、网络应用程序及网络服务的开发。Web应用框架有助于减轻网页开发时共通性活动的工作负荷,例如许多框架提供数据库访问接口、标准样板以及会话管理等,可提升代码的可再用性。

Python web框架比较

下面对常见的三种Python web框架进行简单的介绍:

Django

Django已经成为Python最广泛部署的用于创建Web应用程序的框架之一。 Django配备了可能需要的大部分组件,因此它倾向于构建大型应用程序而不是小型应用程序。
Django的socket用的是wsgiref模块,路由与视图函数、模板渲染都是自己的写的。

Flask

关于Python中的Web框架的大多数讨论都是从Flask开始提到的。 Flask是一个成熟的,易于理解的框架,广泛使用且非常稳定,短小精悍,自带的功能模块特别少,大部分都是依赖于第三方模块。
Flask的socket用的是wsgiref模块,路由与视图函数是自己的写的,模板渲染用的是第三方模块jinjia2。

Tornado

Tornado是针对特定用例的另一个小框架,天生异步,性能强悍。Tornado专为构建异步网络应用程序而设计,非常适合创建同时打开大量网络连接并使其保持活动状态的服务,即涉及WebSockets或长轮询的任何内容。
socket、路由与视图函数、模板渲染都是自己的。

Web框架本质

Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以按照HTTP协议来实现一个简单版的Web框架了。

import socket
server = socket.socket()  # 不传参数默认就是TCP协议
server.bind(('127.0.0.1',8080)) # 绑定IP和端口
server.listen(5)

while True:
    conn, addr = server.accept()  # 阻塞 等待客户端链接
    data = conn.recv(1024)
    # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
    # 手动处理http数据获取用户访问的路径,url是我们从浏览器发过来的消息中分离出的访问路径
    current_path = data.decode('utf-8').split('\r\n')[0].split(' ')[1]
    if current_path == '/index':
        # 路由匹配上之后返回index
        # conn.send(b'<h1>index</h1>')
        with open('index.html','rb') as f:
            # TCP对于发送间隔时间较短和较少的内容会一次发送过去
            conn.send(f.read())
    else:
        # 当匹配不上的时候统一返回404
        conn.send(b'404 not found!')
    conn.close()

这网页能够显示出来了,但是都是静态的啊。页面的内容都不会变化的,我想要的是动态网站。

import socket
import time
sk = socket.socket()
sk.bind(("127.0.0.1", 8080))  # 绑定IP和端口
sk.listen(5)  # 监听
# 将返回不同的内容部分封装成函数
def index(url):
    with open("index.html", "r", encoding="utf8") as f:
        s = f.read()
        now = time.strftime('%Y-%m-%d %X')
        res = s.replace("@@time@@", now)  # 在网页中定义好特殊符号,用动态的数据去替换提前定义好的特殊符号
    return res
def home(url):
    with open("home.html", "r", encoding="utf8") as f:
        res = f.read()
    return res
# 定义一个url和实际要执行的函数的对应关系
list1 = [
    ("/index", index),
    ("/home", home),
]
while True:
    # 等待连接
    conn, add = sk.accept()
    data = conn.recv(8096)  # 接收客户端发来的消息
    # 从data中取到路径
    data = str(data, encoding="utf8")  # 把收到的字节类型的数据转换成字符串
    # 按\r\n分割
    data1 = data.split("\r\n")[0]
    url = data1.split(" ")[1]  # url是我们从浏览器发过来的消息中分离出的访问路径
    conn.send(b'HTTP/1.1 200 OK\r\n\r\n')  # 因为要遵循HTTP协议,所以回复的消息也要加状态行
    # 根据不同的路径返回不同内容
    func = None  # 定义一个保存将要执行的函数名的变量
    for i in list1:
        if i[0] == url:
            func = i[1]
            break
    if func:
        response = func(url)
    else:
        response = "404 not found!"
    # 返回具体的响应消息
    conn.send(response.encode('utf-8'))
    conn.close()

WSGI

PythonWeb服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)是Python应用程序或框架和Web服务器之间的一种接口,已经被广泛接受, 它已基本达成它的可移植性方面的目标。WSGI 没有官方的实现, 因为WSGI更像一个协议. 只要遵照这些协议,WSGI应用(Application)都可以在任何服务器(Server)上运行。

wsgiref模块

最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,都由自己来实现很浪费时间和精力。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口协议来实现这样的服务器软件,让我们专心用Python编写Web业务。这个接口就是WSGI:Web Server Gateway Interface。而wsgiref模块就是python基于wsgi协议开发的服务模块。

我们利用wsgiref模块来替换我们自己写的web框架的socket server部分:

from wsgiref.simple_server import make_server
import time

def index(env):
    with open("index.html", "r", encoding="utf8") as f:
        s = f.read()
        now = time.strftime('%Y-%m-%d %X')
        res = s.replace("@@time@@", now)  # 在网页中定义好特殊符号,用动态的数据去替换提前定义好的特殊符号
    return res
def home(env):
    with open("home.html", "r", encoding="utf8") as f:
        res = f.read()
    return res

def error(env):
    return '404 error'

urls = [
    ('/index',index),
    ('/home',home),
]

def run(env,response):
    '''
    :param env:请求相关的信息
    :param response:响应相关的信息
    :return:
    '''
    response('200 OK',[('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息
    # env是一个大字典 里面装了一堆处理好了的键值对数据
    url = env.get('PATH_INFO') # 取到用户输入的url
    func = None
    for url_map in urls:
        if url == url_map[0]:
            func = url_map[1]
            break
    if func:
        res = func(env)
    else:
        res = error(env)
    return [res.encode('utf-8')]

if __name__ == '__main__':
    server = make_server('127.0.0.1',8080,run)
    server.serve_forever()

jinja2

上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。 本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。其实模板渲染有个现成的工具: jinja2

jinja2是Flask作者开发的一个模板系统,起初是仿django模板的一个模板引擎,为Flask提供模板支持,由于其灵活,快速和安全等优点被广泛使用。jinja2模块中有一个名为Enviroment的类,这个类的实例用于存储配置和全局对象,然后从文件系统或其他位置中加载模板。

jinja2之所以被广泛使用是因为它具有以下优点:

    1. 相对于Template,jinja2更加灵活,它提供了控制结构,表达式和继承等。
    2. 相对于Mako,jinja2仅有控制结构,不允许在模板中编写太多的业务逻辑。
    3. 相对于Django模板,jinja2性能更好。
    4. Jinja2模板的可读性很棒。

安装

由于jinja2属于第三方模块,首先需要对其进行安装

pip3 install jinja2

使用jinja2渲染html文件:

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <link href="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <table class="table table-hover table-striped table-bordered">
                <thead>
                    <tr>
                        <th>id</th>
                        <th>name</th>
                        <th>password</th>
                    </tr>
                </thead>
                <tbody>
                    {% for user in user_dict %}  <!--[{},{},{},{}]-->
                        <tr>
                            <td>{{ user.id }}</td>
                            <td>{{ user.name }}</td>
                            <td>{{ user.password }}</td>
                        </tr>
                    {% endfor %}
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>

py代码

from wsgiref.simple_server import make_server
from jinja2 import Template
import pymysql

def index(env):
    # 连接数据库 获取数据 渲染到前端页面
    conn = pymysql.connect(
        host = '127.0.0.1',
        port = 3306,
        user = 'root',
        password = 'mysql',
        database = 'jinja',
        charset = 'utf8',
        autocommit = True
    )
    cursor = conn.cursor(pymysql.cursors.DictCursor)
    cursor.execute('select * from userifo')
    user_dict= cursor.fetchall()  # [{},{},{},{}]
    with open(r'templates/index.html','r',encoding='utf-8') as f:
        data = f.read()
    tmp = Template(data)
    return tmp.render(user_dict=user_dict)

def error(env):
    return '404 error'
    
urls = [
    ('/index',index),
]

def run(env,response):
    '''
    :param env:请求相关的信息
    :param response:响应相关的信息
    :return:
    '''
    response('200 OK',[('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息
    # env是一个大字典 里面装了一堆处理好了的键值对数据
    url = env.get('PATH_INFO') # 取到用户输入的url
    func = None
    for url_map in urls:
        if url == url_map[0]:
            func = url_map[1]
            break
    if func:
        res = func(env)
    else:
        res = error(env)
    return [res.encode('utf-8')]

if __name__ == '__main__':
    server = make_server('127.0.0.1',8080,run)
    server.serve_forever()

猜你喜欢

转载自blog.csdn.net/linwow/article/details/90921053