Web框架的本质

1. Web请求和响应的过程

简单来说,服务端接收到用户访问网站的请求时,无非就是将用户发来的请求信息进行分析(请求头+请求体)。

再根据用户请求信息中内容在服务端做对应的处理后,将处理后的结果(字符串)作为响应体,再加上响应头后构建成完整的响应报文返回给客户端。

如我们可以根据请求头中的URL来加载对应的文件进行响应,而这个加载对应文件的过程我们可以做成一个函数,从而可以根据请求头中URL信息来执行不同的函数进行处理。

而对于处理的过程,我们可以是直接加载一个html文件后,将整个html文件中的内容作为一个字符串加入响应体中后构建响应报文,再直接发送给客户端。这就是静态资源的请求过程。

而对于变化的数据(动态资源),我们还需要从数据库中拿到对应的数据后,将这些数据填充进html文件中的指定位置,再将填充后的内容作为一个字符串附着进响应体中,再构建响应报文发送给客户端。

这时,不难发现,现在的这个html文件,已然变成了一个模板,我们可以在这个模板上定义特殊的替换规则后,就可以填充我们想要添加的任何内容。这样就可以实现内容的动态显示。

2. 一个简单的静态Web服务器

根据以上基本原理,我们不难写出一个根据请求的URL来响应对应html网页内容的简单的静态资源web服务器。

import socket

def f1(request):
    # request中包含了用户请求的所有内容:请求头+请求体
    f = open('index.html', 'rb')
    data = f.read()
    f.close()
    return data

def f2(request):
    f = open('article.html', 'rb')
    data = f.read()
    f.close()
    return data

routers = [
    ('/f1', f1),
    ('/f2', f2),
]

def run():
    sock = socket.socket()
    sock.bind(('127.0.0.1', 8080))
    sock.listen(5)

    while True:
        # 等待客户端来连接
        conn, addr = sock.accept()
        # 获取用户发送的数据,收到的数据是Bytes,(二进制格式)
        data = conn.recv(8096)

        """对用户发来的信息进行提取"""

        # 将内容转化成字符串类型
        data = str(data, encoding='utf-8')
        # data = bytes('shit', encoding='utf-8')

        # 将内容分割成请求头和请求体
        headers, bodys = data.split('

')
        # 得到请求头中的每一行数据
        temp_list = headers.split('
')
        # 对请求头中的第一行数据进行提取,取得请求方法、请求URL、请求方法
        method, url, protocol = temp_list[0].split(' ')

        """根据用户请求的URL来返回不同的内容"""
        """思路:
            遍历routers中的每个元组的第一个值和请求头中的url进行比较,
            若匹配成功,则将对应的函数名赋值给func_name,进而去执行该URL对应的函数
        """
        func_name = None
        for item in routers:
            if item[0] == url:
                func_name = item[1]
                break
        if func_name:
            response = func_name(data)  # 把data中的内容传进去
        else:
            response = b"404 not found"

        conn.send(response)
        conn.close()


if __name__ == '__main__':
        run()

3. 动态网站Web服务器

动态web无非就是数据从数据库来提取,所以数据是会变化的,而非像静态页面那样一成不变。

import socket
import pymysql

# 静态页面
def index(request):
    # request中包含了用户请求的所有内容:请求头+请求体
    f = open('index.html', 'rb')
    data = f.read()
    f.close()
    return data

# 静态页面
def article(request):
    f = open('article.html', 'rb')
    data = f.read()
    f.close()
    return data

# 动态页面
def user_info(request):

    # 连接数据库并取得数据
    mysql_conn = pymysql.connect(host='10.0.0.204', port=3306, user='hgzero', passwd='woshiniba', db='my_test')
    conn_cursor = mysql_conn.cursor(cursor=pymysql.cursors.DictCursor)
    ret = conn_cursor.execute("select name,age,gender from first_test")
    print("受影响的行数:%s" % ret)
    user_list = conn_cursor.fetchall()
    conn_cursor.close()
    mysql_conn.close()
    print("打印从数据库中拿到的内容: %s" % user_list)

    # 将从数据库中拿到的内容进行拼接
    content_list = []
    for row in user_list:
        tp = "<tr><th>%s</th><th>%s</th><th>%s</th></tr>" % (row["name"], row["age"], row["gender"])
        content_list.append(tp)
    user_content = "".join(content_list)

    # 将html文件中指定位置的内容替换成数据库中取得后拼接好的内容
    f = open("user_list.html", "r", encoding='utf-8')
    html_template = f.read()
    f.close()

    # 替换为指定内容
    # 这里其实就是最简单的所谓的模板的渲染
    data = html_template.replace("@@content@@", user_content)

    return bytes(data, encoding="utf-8")


routers = [
    ('/index.html', index),
    ('/article.html', article),
    ('/user_info.html', user_info),
]

def run():
    sock = socket.socket()
    sock.bind(('127.0.0.1', 8080))
    sock.listen(5)

    while True:
        # 等待客户端来连接
        conn, addr = sock.accept()
        # 获取用户发送的数据,收到的数据是Bytes,(二进制格式)
        data = conn.recv(8096)

        """对用户发来的信息进行提取"""

        # 将内容转化成字符串类型
        data = str(data, encoding='utf-8')
        # data = bytes('shit', encoding='utf-8')

        # 将内容分割成请求头和请求体
        headers, bodys = data.split('

')
        # 得到请求头中的每一行数据
        temp_list = headers.split('
')
        # 对请求头中的第一行数据进行提取,取得请求方法、请求URL、请求方法
        method, url, protocol = temp_list[0].split(' ')

        """根据用户请求的URL来返回不同的内容"""
        """思路:
            遍历routers中的每个元组的第一个值和请求头中的url进行比较,
            若匹配成功,则将对应的函数名赋值给func_name,进而去执行该URL对应的函数
        """
        func_name = None
        for item in routers:
            if item[0] == url:
                func_name = item[1]
                break
        if func_name:
            response = func_name(data)  # 把data中的内容传进去
        else:
            response = b"404 not found"

        conn.send(response)
        conn.close()


if __name__ == '__main__':
        run()

4. 用Jinja2进行模板渲染

这时不难发现,如果我们手动的对html模板进行内容替换,这将会非常麻烦,然而已经有这样的模板渲染工具了。我们可以按照Jinja2规定要的语法来对我们的html模板进行渲染。

import socket
import pymysql

# 静态页面
def index(request):
    # request中包含了用户请求的所有内容:请求头+请求体
    f = open('index.html', 'rb')
    data = f.read()
    f.close()
    return data

# 静态页面
def article(request):
    f = open('article.html', 'rb')
    data = f.read()
    f.close()
    return data

# 动态页面,手动渲染
def user_info(request):

    # 连接数据库并取得数据
    mysql_conn = pymysql.connect(host='10.0.0.204', port=3306, user='hgzero', passwd='woshiniba', db='my_test')
    conn_cursor = mysql_conn.cursor(cursor=pymysql.cursors.DictCursor)
    ret = conn_cursor.execute("select name,age,gender from first_test")
    print("受影响的行数:%s" % ret)
    user_list = conn_cursor.fetchall()
    conn_cursor.close()
    mysql_conn.close()
    print("打印从数据库中拿到的内容: %s" % user_list)

    # 将从数据库中拿到的内容进行拼接
    content_list = []
    for row in user_list:
        tp = "<tr><th>%s</th><th>%s</th><th>%s</th></tr>" % (row["name"], row["age"], row["gender"])
        content_list.append(tp)
    user_content = "".join(content_list)

    # 将html文件中指定位置的内容替换成数据库中取得后拼接好的内容
    f = open("template_shit.html", "r", encoding='utf-8')
    html_template = f.read()
    f.close()

    # 替换为指定内容
    # 这里其实就是最简单的所谓的模板的渲染
    data = html_template.replace("@@content@@", user_content)

    return bytes(data, encoding="utf-8")

# 动态页面,用jinja2进行模板渲染
def template_shit(request):
    # 连接数据库并取得数据
    mysql_conn = pymysql.connect(host='10.0.0.204', port=3306, user='hgzero', passwd='woshiniba', db='my_test')
    conn_cursor = mysql_conn.cursor(cursor=pymysql.cursors.DictCursor)
    conn_cursor.execute("select name,age,gender from first_test")
    user_list = conn_cursor.fetchall()
    conn_cursor.close()
    mysql_conn.close()

    # 获取html模板的内容
    f = open("template_shit.html", "r", encoding='utf-8')
    html_data = f.read()
    f.close()

    # 用jinja2进行模板渲染
    from jinja2 import Template
    template = Template(html_data)
    # 用从数据库中拿到的user_list来替换模板中的user_list变量
    data = template.render(user_list=user_list)
    # 模板中定义的规则:
    """{% for rwo in user_list %}
                  <tr>
                      <td>{{row.name}}</td>
                      <td>{{row.age}}</td>
                      <td>{{row.gender}}</td>
                  </tr>
              {% endfor %}
    """
    print(data)  # 看看模板生成的内容
    return data.encode("utf-8")


routers = [
    ('/index.html', index),
    ('/article.html', article),
    ('/user_info.html', user_info),
    ('/template_shit.html', template_shit),
]

def run():
    sock = socket.socket()
    sock.bind(('127.0.0.1', 8080))
    sock.listen(5)

    while True:
        # 等待客户端来连接
        conn, addr = sock.accept()
        # 获取用户发送的数据,收到的数据是Bytes,(二进制格式)
        data = conn.recv(8096)

        """对用户发来的信息进行提取"""

        # 将内容转化成字符串类型
        data = str(data, encoding='utf-8')
        # data = bytes('shit', encoding='utf-8')

        # 将内容分割成请求头和请求体
        headers, bodys = data.split('

')
        # 得到请求头中的每一行数据
        temp_list = headers.split('
')
        # 对请求头中的第一行数据进行提取,取得请求方法、请求URL、请求方法
        method, url, protocol = temp_list[0].split(' ')

        """根据用户请求的URL来返回不同的内容"""
        """思路:
            遍历routers中的每个元组的第一个值和请求头中的url进行比较,
            若匹配成功,则将对应的函数名赋值给func_name,进而去执行该URL对应的函数
        """
        func_name = None
        for item in routers:
            if item[0] == url:
                func_name = item[1]
                break
        if func_name:
            response = func_name(data)  # 把data中的内容传进去
        else:
            response = b"404 not found"

        conn.send(response)
        conn.close()


if __name__ == '__main__':
        run()

至此,可以看到,现在它已经基本上实现的一个框架应该实现的功能。

5. Web框架的种类

5.1 Web框架应该实现的功能

  1. Socket服务端
  2. 路由系统:根据URL来返回不同的内容,URL --> 函数
  3. 字符串加入响应体返回给用户:模板引擎渲染,字符串

5.2 Web框架的种类

根据以上的三种主要功能,web框架可以分为以下几类

  • 自己实现了 1,2,3  
    • Tornado
  • 实现了 2,3 ,而使用了第三方的Socket服务端
    • Django
  • 实现了 2 , 而使用了第三方的Socket服务器以及第三方的模板渲染引擎(如Jinja2)
    • flask

原文地址:https://www.cnblogs.com/hgzero/p/13216511.html