• Web框架的原理


    Web框架本质

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

    socket服务端

    #!/usr/bin/env python3
    # _*_ coding:utf-8 _*_
    import  socket
    
    # 实例化socket对象
    sk = socket.socket()
    
    # 绑定IP和端口
    sk.bind(('127.0.0.1',9000))
    
    # 监听
    sk.listen()
    
    while True:
        # 等待连接
        conn,addr = sk.accept()
    
        # 接收数据
        data = conn.recv(1024)
        print(data)
    
        # 发送数据
        conn.send(b'ok')
    
        # 断开连接
        conn.close()
    View Code

    socket客户端(浏览器访问)

    用户在浏览器中输入网址(ip:port),浏览器连接socket服务端 建立连接,发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,那互联网还能玩么?

    所以,必须有一个统一的规则,让大家发送消息、接收消息的时候都有个格式依据,不能随便写。

    这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。

    HTTP协议主要规定了客户端和服务器之间的通信格式,那HTTP协议是怎么规定消息格式的呢?

    让我们首先打印下我们在服务端接收到的消息是什么。

    b'GET / HTTP/1.1
    Host: 127.0.0.1:9000
    Connection: keep-alive
    Cache-Control: max-age=0
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9
    
    '

    将 替换成换行看得更清晰点:

    b'GET / HTTP/1.1
    Host: 127.0.0.1:9000
    Connection: keep-alive
    Cache-Control: max-age=0
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9
    
    '

    然后我们再看一下我们访问博客园官网时浏览器收到的响应信息是什么。

    响应相关信息可以在浏览器调试窗口的Network标签页中看到。

    点击view source之后显示如下图:

    我们发现收发的消息需要按照一定的格式来,这里就需要了解一下HTTP协议了。

    HTTP协议介绍

    HTTP协议对收发消息的格式要求

    每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。

    HTTP响应的Header中有一个 Content-Type表明响应的内容格式。它的值如text/html; charset=utf-8。

    text/html则表示是网页,charset=utf-8则表示编码为utf-8。

    HTTP请求的格式:

    HTTP响应的格式:

    自定义web框架

    上面知道了服务端和客户端传输要遵循HTTP协议,想让浏览器能显示我们web框架返回的信息,那我们就要按照HTTP协议的格式来发送响应。

    基础版

    #!/usr/bin/env python3
    # _*_ coding:utf-8 _*_
    import  socket
    
    # 实例化socket对象
    sk = socket.socket()
    
    # 绑定IP和端口
    sk.bind(('127.0.0.1',9000))
    
    # 监听
    sk.listen()
    
    while True:
        # 等待连接
        conn,addr = sk.accept()
    
        # 接收数据
        data = conn.recv(1024)
    
        # 发送数据
        conn.send(b'HTTP/1.1 200 OK
    
    ')
        conn.send(b'<h1>ok</h1>')
    
        # 断开连接
        conn.close()
    View Code

    根据不同的路径返回不同的内容

    从请求相关数据里面拿到请求URL的路径,然后拿路径做一个判断,返回相应的页面

    #!/usr/bin/env python3
    # _*_ coding:utf-8 _*_
    import  socket
    
    # 实例化socket对象
    sk = socket.socket()
    
    # 绑定IP和端口
    sk.bind(('127.0.0.1',9000))
    
    # 监听
    sk.listen()
    
    while True:
        # 等待连接
        conn,addr = sk.accept()
    
        # 接收数据
        data = conn.recv(1024)
    
        # 获取到要访问的url路径
        data = data.decode('utf-8')
        url = data.split()[1]
        print(url)
    
        # 发送数据
        # 因为要遵循HTTP协议,所以回复的消息也要加状态行 
        conn.send(b'HTTP/1.1 200 OK
    
    ')
    
        # 根据不同的路径返回不同内容
        if url == '/index/':
            conn.send(b'<h1> index </h1>')
        elif url == '/home/':
            conn.send(b'<h1> home </h1>')
        else:
            conn.send(b'<h1> 404 not found </h1>')
    
        # 断开连接
        conn.close()
    View Code

    根据不同的路径返回不同的内容--函数版

    #!/usr/bin/env python3
    # _*_ coding:utf-8 _*_
    import  socket
    
    # 实例化socket对象
    sk = socket.socket()
    
    # 绑定IP和端口
    sk.bind(('127.0.0.1',9000))
    
    # 监听
    sk.listen()
    
    
    # 将返回不同的内容部分封装成函数
    def index(url):
        ret = '<h1>index  %s</h1>' %url
        return ret.encode('utf-8')
    
    def home(url):
        ret = '<h1>index  %s</h1>' %url
        return ret.encode('utf-8')
    
    while True:
        # 等待连接
        conn,addr = sk.accept()
    
        # 接收数据
        data = conn.recv(1024)
    
        # 获取到要访问的url路径
        data = data.decode('utf-8')
        url = data.split()[1]
        print(url)
    
        # 发送数据
        # 因为要遵循HTTP协议,所以回复的消息也要加状态行
        conn.send(b'HTTP/1.1 200 OK
    
    ')
    
        # 根据不同的路径返回不同内容
        if url == '/index/':
            ret = index(url)
        elif url == '/home/':
            ret = home(url)
        else:
            ret = b'<h1> 404 not found </h1>'
    
        conn.send(ret)
        # 断开连接
        conn.close()
    View Code

    根据不同的路径返回不同的内容--函数进阶版

    看起来上面的代码写了一个函数,那肯定可以写多个函数,不同的路径对应执行不同的函数拿到结果,但是我们要一个个判断路径很麻烦。

    下面定义一个函数与路径对应的列表。

    #!/usr/bin/env python3
    # _*_ coding:utf-8 _*_
    import  socket
    
    # 实例化socket对象
    sk = socket.socket()
    
    # 绑定IP和端口
    sk.bind(('127.0.0.1',9000))
    
    # 监听
    sk.listen()
    
    
    # 将返回不同的内容部分封装成函数
    def index(url):
        ret = '<h1>index  %s</h1>' %url
        return ret.encode('utf-8')
    
    def home(url):
        ret = '<h1>index  %s</h1>' %url
        return ret.encode('utf-8')
    
    def web(url):
        ret = '<h1>web  %s</h1>' %url
        return ret.encode('utf-8')
    
    # 定义 路径与函数对应关系的一个列表
    list1 = [
        ('/index/',index),
        ('/home/',home),
        ('/web/',web),
    ]
    
    while True:
        # 等待连接
        conn,addr = sk.accept()
    
        # 接收数据
        data = conn.recv(1024)
    
        # 获取到要访问的url路径
        data = data.decode('utf-8')
        url = data.split()[1]
        print(url)
    
        # 发送数据
        # 因为要遵循HTTP协议,所以回复的消息也要加状态行
        conn.send(b'HTTP/1.1 200 OK
    
    ')
    
        func = None
    
        for f in list1:
            if f[0] == url:
                func = f[1]
                break
    
        # 如果url没匹配上,func变量还是None
        if func:
            ret = func(url)
        else:
            ret = b'<h1>404 not found</h1>'
    
        conn.send(ret)
    
         # 断开连接
        conn.close()
    View Code

    返回具体的HTML文件

    从文件读取,发送给浏览器。

    #!/usr/bin/env python3
    # _*_ coding:utf-8 _*_
    import  socket
    
    # 实例化socket对象
    sk = socket.socket()
    
    # 绑定IP和端口
    sk.bind(('127.0.0.1',9000))
    
    # 监听
    sk.listen()
    
    
    # 将返回不同的内容部分封装成函数
    def index(url):
        with open('index.html',"rb") as f:
            data = f.read()
        return data
    
    def home(url):
        with open('home.html',"rb") as f:
            data = f.read()
        return data
    
    def web(url):
        with open('web.html',"rb") as f:
            data = f.read()
        return data
    
    # 定义 路径与函数对应关系的一个列表
    list1 = [
        ('/index/',index),
        ('/home/',home),
        ('/web/',web),
    ]
    
    while True:
        # 等待连接
        conn,addr = sk.accept()
    
        # 接收数据
        data = conn.recv(1024)
    
        # 获取到要访问的url路径
        data = data.decode('utf-8')
        url = data.split()[1]
        print(url)
    
        # 发送数据
        # 因为要遵循HTTP协议,所以回复的消息也要加状态行
        conn.send(b'HTTP/1.1 200 OK
    
    ')
    
        func = None
    
        for f in list1:
            if f[0] == url:
                func = f[1]
                break
    
        # 如果url没匹配上,func变量还是None
        if func:
            ret = func(url)
        else:
            ret = b'<h1>404 not found</h1>'
    
        conn.send(ret)
    
         # 断开连接
        conn.close()
    View Code

    让网页动态起来

    使用时间戳来模拟动态的数据,使用字符串替换来实现这个需求。

    #!/usr/bin/env python3
    # _*_ coding:utf-8 _*_
    import  socket
    import time
    
    # 实例化socket对象
    sk = socket.socket()
    
    # 绑定IP和端口
    sk.bind(('127.0.0.1',9000))
    
    # 监听
    sk.listen()
    
    
    # 将返回不同的内容部分封装成函数
    def index(url):
        with open('index.html',"rb") as f:
            data = f.read()
        return data
    
    def home(url):
        with open('home.html',"rb") as f:
            data = f.read()
        return data
    
    def web(url):
        with open('web.html',"rb") as f:
            data = f.read()
        return data
    
    def timer(url):
        now = time.strftime('%Y-%m-%d %H:%M:%S')
        with open('timer.html',"r",encoding='utf-8') as f:
            data = f.read()
            data = data.replace('@@time@@',now)
    
        return data.encode('utf-8')
    
    # 定义 路径与函数对应关系的一个列表
    list1 = [
        ('/index/',index),
        ('/home/',home),
        ('/web/',web),
        ('/timer/',timer),
    ]
    
    while True:
        # 等待连接
        conn,addr = sk.accept()
    
        # 接收数据
        data = conn.recv(1024)
    
        # 获取到要访问的url路径
        data = data.decode('utf-8')
        url = data.split()[1]
    
        # 发送数据
        # 因为要遵循HTTP协议,所以回复的消息也要加状态行
        conn.send(b'HTTP/1.1 200 OK
    
    ')
    
        func = None
    
        for f in list1:
            if f[0] == url:
                func = f[1]
                break
    
        # 如果url没匹配上,func变量还是None
        if func:
            ret = func(url)
        else:
            ret = b'<h1>404 not found</h1>'
    
        conn.send(ret)
    
         # 断开连接
        conn.close()
    View Code
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!-- 自己定一个占位符 @@time@@ -->
    <h1> 现在的时间是: @@time@@ </h1>
    <script>
        window.location.reload()
    </script>
    </body>
    </html>
    timer.html
    
    

    服务器程序和应用程序

    对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。

    服务器程序负责对socket服务端进行封装,并在请求到来时,对请求的各种数据进行整理。

    应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。

    这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。

    这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

    WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。

    常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。

    wsgiref

    #!/usr/bin/env python3
    # _*_ coding:utf-8 _*_
    """
    WSGI(Web Server Gateway Interface)就是一种规范,
    它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,
    实现web应用程序与web服务器程序间的解耦。
    
    常用的WSGI服务器有uwsgi、Gunicorn。
    而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。
    """
    
    """
    利用wsgiref模块来替换我们自己写的web框架的socket server部分:
    根据URL中不同的路径返回不同的内容--函数进阶版  
    返回HTML页面  
    让网页动态起来  
    wsgiref模块版
    """
    import time
    from wsgiref.simple_server import make_server
    
    
    def index(url):
        with open("index.html","r",encoding="utf8") as f:
            data = f.read().encode("utf8")
        return data
    
    def home(url):
        with open("home.html","r",encoding="utf8") as f:
            data = f.read().encode("utf8")
        return data
    
    
    def timer(url):
        with open("timer.html","r",encoding="utf8") as f:
            data = f.read()
            data = data.replace('@@time@@',time.strftime("%Y-%m-%d %H:%M:%S")).encode("utf8")
        return data
    
    
    list1 = [
        ("/index/",index),
        ("/home/",home),
        ("/timer/",timer),
    ]
    
    def run_server(environ,start_response):
        start_response("200 OK",[('Content-Type','text/html;charset=utf8'),])
        url = environ['PATH_INFO']  # 取得用户输入的url路径
        func = None
        for f in list1:
            if f[0] == url:
                func = f[1]
                break
        if func:
            response = func(url)
        else:
            response = b'404 not found'
        return [response, ]
    
    if __name__ == '__main__':
        httpd = make_server('0.0.0.0', 9090,run_server)
        print('run server 0.0.0.0:9090 ')
        httpd.serve_forever()
    View Code

    jinja2

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

    下载jinja2:

    pip install jinja2
    #!/usr/bin/env python3
    # _*_ coding:utf-8 _*_
    from jinja2 import Template
    from wsgiref.simple_server import make_server
    
    def index(url):
        # 读取HTML文件内容
        with open('index2.html','r',encoding='utf8') as f:
            data = f.read()
            template = Template(data)  #生成模板文件
            ret = template.render({'name': 'alex', 'hobby_list': ['抽烟', '喝酒', '烫头']})    # 把数据填充到模板中
        return bytes(ret,encoding='utf8')
    
    def home(url):
        with open('home.html','rb') as f:
            data = f.read()
        return data
    
    # 定义一个url和实际要执行的函数的对应关系
    list1 = [
        ("/index/", index),
        ("/home/", home),
    ]
    
    
    def run_server(environ,start_response):
        start_response("200 OK",[('Content-Type','text/html;charset=utf8'),])
        url = environ['PATH_INFO']  # 取得用户输入的url路径
        func = None
        for f in list1:
            if f[0] == url:
                func = f[1]
                break
        if func:
            response = func(url)
        else:
            response = b'404 not found'
        return [response, ]
    
    if __name__ == '__main__':
        httpd = make_server('0.0.0.0', 9090,run_server)
        print('run server 0.0.0.0:9090 ')
        httpd.serve_forever()
    View Code
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="x-ua-compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Title</title>
    </head>
    <body>
        <!-- jinja2模板语法 {{ 变量 }}-->
        <h3>姓名:{{name}}</h3>
        <h3>爱好:</h3>
        <ul>
            <!-- for 循环 -->
            {% for hobby in hobby_list %}
            <li>{{hobby}}</li>
            {% endfor %}
        </ul>
    </body>
    </html>
    index2.html

    模板的原理就是字符串替换,我们只要在HTML页面中遵循jinja2的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。

  • 相关阅读:
    借助baidu的jsonp接口,做一个自己的候选词组件
    Cannot set property 'innerHTML' of null
    Win下端口占用问题:OSError: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试
    一文读懂ES6(附PY3对比)
    Wireshark:couldn't run dumpcap in child process(附带Linux下探索过程)
    一个模块导入的简单小测试
    万物互联之~网络编程加强篇
    网罗天下之~正则表达
    (转)RTSP协议详解
    (转)HLS协议,html5视频直播一站式扫盲
  • 原文地址:https://www.cnblogs.com/root0/p/10748652.html
Copyright © 2020-2023  润新知