• 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/

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

  • 相关阅读:
    C#操纵Excel,此工作薄包含嵌入对象,Office 2007的设定方法
    将本程序添加到自启动项
    .net表达式计算器(中缀表达式转后缀表达式,支持20多个数学函数,支持函数嵌套)
    Entity Framework 调用返回标量值的存储过程
    Dev控件 galleryControl
    Dev控件treeList
    Linux下USB驱动开发相关链接
    Linux下串口编程相关链接
    Linux常用命令操作说明(链接)
    Perl的主要应用领域
  • 原文地址:https://www.cnblogs.com/hyonline/p/11822223.html
Copyright © 2020-2023  润新知