• Python Web框架本质——Python Web开发系列一


      前言:了解一件事情本质的那一瞬间总能让我获得巨大的愉悦感,希望这篇文章也能帮助到您。

      目的:本文主要简单介绍Web开发中三大基本功能:Socket实现、路由系统、模板引擎渲染。

      进入正题。

      

     一. 基础知识

      • Http/Https协议:简单的对象访问协议,对应于应用层。Http协议是基于TCP链接的。特点是无状态、短连接。
      • Socket:Socket是应用层与TCP/IP协议族通信的中间软件抽象层,是介于传输层和应用层之间的一个协议,是一组接口。所有支持网络编程的语言都会有对Socket的实现,而几乎所有的Web开发框架底层都是由Socket实现的。在Socket编程中,主动发起连接的叫客户端,被动响应连接的叫服务器,如在浏览网页时浏览器本质上就是一个Socket客户端,网站服务器就是一个Socket服务端。
      • Web框架:“Web应用框架(Web application framework)是一种开发框架,用来支持动态网站、网络应用程序及网络服务的开发。” (引用自百度百科),简而言之是用于开发Web应用的,Python常见Web框架有Flask、Django、Tornado等。

        Web开发中最基础的三大功能分别是:

      • Socket服务端实现
      • 路由系统
      • 模板引擎渲染

        下面将对这三部分一一说明。

     二. Socket编程

      既然几乎所有的Web开发框架底层都是由Socket实现的,我们就从Socket编程开始,用Socket实现一个服务端和浏览器进行通信(细想一下,这就是Web服务最基本的需求了吧)。

    # 例1
    import socket
    # 生成一个socket对象
    server = socket.socket()
    # 绑定机器的ip端口
    server.bind(("127.0.0.1", 8001))
    # 配置最多只能有五个请求在等待连接
    server.listen(5)
    
    while True:
        # 阻塞,等待接受请求
        conn, addr = server.accept()
        # 建立连接后接受数据,规定一次数据大小为8096字节
        data = conn.recv(8096)
    print(data) # 在该连接通道中发送数据,注意要是字节形式 conn.send(b"HTTP/1.1 200 OK ") conn.send(b"Hello World!") # 关闭连接 conn.close()

      

      这段代码实现了一个Socket服务端,server.accept()会让服务器阻塞等待客户端的连接,当接收到连接请求,就返回数据。下面用浏览器发送连接请求:

       

      可以看到,浏览器已经成功接收到了Socket服务端发来"Hello World",成功解析并显示在了网页上,一个最基本的Web服务就顺利完成啦!

      

     三. Socket编程中的HTTP/HTTPS协议

      Http/Https协议,简单的对象访问协议,对应于应用层,简单而言就是一个大家都遵循的格式、规范,根据这个规范我们可以获取自己所需的信息。Http协议是基于TCP链接的。但与TCP一直保存连接不主动断开相比,HTTP/HTTPS的连接在一次传输后就会断开,并且不会保存连接信息,下次再连接时没有上次连接的状态,所以说特点是无状态、短连接。

      先来看看上面代码中在服务端接收到连接请求的内容(具体对应代码:data = conn.recv(8096))

    b'GET / HTTP/1.1
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    Accept-Language: zh-CN
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362
    Accept-Encoding: gzip, deflate
    Host: 127.0.0.1:8001
    Connection: Keep-Alive
    
    '

    解释:
     'method url protocol headers-param1 headers-param2 headers-param3...headers-paramn '

      由此我们可以窥见一点HTTP/HTTPS协议内容,它规定GET请求的格式就如上面所示,那么我们拿到请求的数据后,通过简单的字符串处理就能拿到这个请求的method,url,protocol,headers,例如url = data.split(' ')[0].split(' ')[1]。

      此外,因为这个请求只是一个简单的GET请求,请求信息到 就结束了,事实上 也是一个分割符,分割的是请求头和请求体,当一个请求是POST请求时,POST的参数就在请求体中,也就是说post_params  = data.split(" ")[1]。而在例1中,也用" "分割开了响应头和响应体:conn.send(b"HTTP/1.1 200 OK "),conn.send(b"Hello World!")。

     四. 路由系统

      路由系统要完成的功能是:根据不同的请求信息做不同的数据处理,返回不同的数据响应。例如分别访问“https://www.cnblogs.com/hyonline/p/11812977.html”、“https://i-beta.cnblogs.com/posts/edit”,请求都会发送到博客园的服务器,根据url的不同,第一个请求会响应对应文章内容,而第二个请求会响应编辑后台。

      接回上面的话题,当接收到一个遵循HTTP/HTTPS协议的请求时,我们可以通过字符串处理获取到请求的url,然后根据不同的url调用不同功能的模块或者函数来处理该请求,生成不同的数据来响应请求。现在来改写我们的Socket服务端,加入路由系统:

    # 例2
    import socket
    
    def index():
        return b'Hello World!'
    
    def func1():
        return b"I'm not hungry yet!"
    
    def func2():
        return b"Cheers!"
    
    # 路由表
    routers = [
        ('/', index),
        ('/eat', func1),
        ('/drank', func2),
    ]
    
    
    server = socket.socket()
    server.bind(("127.0.0.1", 8001))
    server.listen(5)
    
    while True:
    
        conn, addr = server.accept()
        data = str(conn.recv(8096), encoding='utf-8')
        headers, bodys = data.split('
    
    ') # 分割出请求头,请求体
        temp_list = headers.split('
    ')
        method, url, protocal = temp_list[0].split(' ') # 分割出请求方法,url,协议
        conn.send(b'HTTP/1.1 200 OK
    
    ')
        
        func_name = None
        for item in routers: # 路由匹配,根据url获取相应的处理函数
            if url == item[0]:
                func_name = item[1]
                break
        
        if func_name:
            response = func_name()
        else:
            response = b'404 not found'  # 假如url不在路由系统中,模拟返回404
        conn.send(response)
        conn.close()

      在例2中,我们加入了路由表,通过字符串处理分割出请求url,并根据url去匹配路由表,找到合适的处理函数,如果没有则返回404,再看看浏览器访问结果:

      可以看到,例2的Socket服务器已经能够根据请求url的不同调用合适的处理函数来处理返回正确的数据了,这就是Web开发框架中路由系统的本质!

     五. 模板引擎渲染

      由上面的例子中我们可以发现,Socket编程中的数据传输的数据都是字节,而我们获取请求信息和响应信息构造其实都是字符串的处理。前面的例子我们响应的数据是简单的字节串,此外我们响应的数据还可以是HTML代码,这些代码传输到浏览器等客户端时会被渲染成我们常常看到的网页。看下个例子:

    # 例3
    # server.py
    import socket
    
    def index():
        with open('index.html', 'r') as f: # 读取html文件内容作为响应体数据
            response = f.read()
        return response.encode('utf-8')
    
    # 路由表
    routers = [
        ('/', index),
    ]
    
    server = socket.socket()
    server.bind(("127.0.0.1", 8001))
    server.listen(5)
    
    while True:
        conn, addr = server.accept()
        data = str(conn.recv(8096), encoding='utf-8')
        headers, bodys = data.split('
    
    ')  
        temp_list = headers.split('
    ')
        method, url, protocal = temp_list[0].split(' ')  
        conn.send(b'HTTP/1.1 200 OK
    
    ')
        
        func_name = None
        for item in routers:  
            if url == item[0]:
                func_name = item[1]
                break
        
        if func_name:
            response = func_name()
        else:
            response = b'404 not found'  
        conn.send(response)
        conn.close()
    
    # index.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>"HELLO WORLD"</title>
    </head>
    <body>
        <table border="1">
            <tr>
                <th>Month</th>
                <th>Savings</th>
            </tr>
            <tr>
                <td>January</td>
                <td>$100</td>
            </tr>
        </table>
    </body>
    </html>

     

      在这个例子中,我们修改了index(),在例2中的直接返回字节串作为响应体数据,而例3中我们读取了index.html文件内容作为响应体数据返回,在浏览器中已被渲染成一个带表格的网页了。在Web开发中,这个html文件就称之为模板,这种一成不变的网页,称之为静态网页。

      

      在本例中我们已经实现渲染静态页面了,但是目前大部分的网页并不是静态的,存在着大量的动态数据,动态网页的渲染需要我们获取最新的数据,拼接到模板合适的位置,然后作为响应体数据返回。动态拼接的功能我们可以通过字符串替换来实现,在模板中合适的位置用特殊字符来做占位符,当要响应数据时候,拿到最新的数据替换掉模板中的占位符,即可做到用最新的数据作为返回结果了。看下个例子:

    # 例4
    # server.py
    import socket
    import datetime
    
    def index():
        with open('index2.html', 'r') as f:  # 读取html文件内容作为响应体数据
            response = f.read()
        response = response.replace('@temp@', str(datetime.datetime.now())) # 获取当前时间,替换html文件内容中的占位符
        return response.encode('utf-8')
    
    
    # 路由表
    routers = [
        ('/', index),
    ]
    
    server = socket.socket()
    server.bind(("127.0.0.1", 8001))
    server.listen(5)
    
    while True:
        conn, addr = server.accept()
        data = str(conn.recv(8096), encoding='utf-8')
        headers, bodys = data.split('
    
    ')  
        temp_list = headers.split('
    ')
        method, url, protocal = temp_list[0].split(' ') 
        conn.send(b'HTTP/1.1 200 OK
    
    ')
        
        func_name = None
        for item in routers:  
            if url == item[0]:
                func_name = item[1]
                break
        
        if func_name:
            response = func_name()
        else:
            response = b'404 not found'  
        conn.send(response)
        conn.close()
    
    # index2.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>"HELLO WORLD"</title> </head> <body> <table border="1"> <tr> <th>Month</th> <th>Savings</th> </tr> <tr> <td>January</td> <td>$100</td> </tr> <tr> <td>time</td> <!--用特殊符号作为占位符--> <td>@temp@</td> </tr> </table> </body> </html>

       在这个例子中,我们修改了index.html,添加了一个占位符,并在server.py中对这个占位符用当前时间替换,于是在新的访问结果中,可以看到网页已经显示了最新的时间,用简单的字符串替换我们就实现了动态网页的模板渲染啦!这其实就是模板渲染的本质,用于模板渲染的模块称之为模板引擎,当然我们不需要自己实现,常用的python模板引擎有:jinjia2。

     

     六. 结语

      介绍完基本Web框架功能的本质后,我们来简单聊聊Python Web框架。如果按照上面介绍的Web开发基本功能来分类,可以分为三类:

      • 框架包含Socket实现、路由匹配、模板引擎渲染功能:Tornado
      • 框架包含路由匹配、模板引擎渲染功能:Django(其Socket实现通过引用wsgiref模块实现)
      • 框架包含路由匹配:Flask(其Socket实现通过引用werkzeug模块实现,模板引擎渲染实现通过引用jinjia2模块实现)

      当然Web框架除了以上介绍的基础功能外还实现了很多其他的功能,正是有了这些利器,我们的Web开发才能得心应手。

    作者: hyonline

    出处: https://www.cnblogs.com/hyonline/

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接 如有问题, 可私信咨询.

  • 相关阅读:
    spring cloud 和 阿里微服务spring cloud Alibaba
    为WPF中的ContentControl设置背景色
    java RSA 解密
    java OA系统 自定义表单 流程审批 电子印章 手写文字识别 电子签名 即时通讯
    Hystrix 配置参数全解析
    spring cloud 2020 gateway 报错503
    Spring Boot 配置 Quartz 定时任务
    Mybatis 整合 ehcache缓存
    Springboot 整合阿里数据库连接池 druid
    java OA系统 自定义表单 流程审批 电子印章 手写文字识别 电子签名 即时通讯
  • 原文地址:https://www.cnblogs.com/hyonline/p/11822223.html
Copyright © 2020-2023  润新知