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

  目的:本文主要简单介绍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. # 例1
    import socket
  1. # 生成一个socket对象
  2. server = socket.socket()
  3. # 绑定机器的ip端口
  4. server.bind(("127.0.0.1", 8001))
  5. # 配置最多只能有五个请求在等待连接
  6. server.listen(5)
  7.  
  8. while True:
  9. # 阻塞,等待接受请求
  10. conn, addr = server.accept()
  11. # 建立连接后接受数据,规定一次数据大小为8096字节
  12. data = conn.recv(8096)
    print(data)
  13. # 在该连接通道中发送数据,注意要是字节形式
  14. conn.send(b"HTTP/1.1 200 OK\r\n\r\n")
  15. conn.send(b"Hello World!")
  16. # 关闭连接
  17. conn.close()

  

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

  

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

  

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

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

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

  1. b'GET / HTTP/1.1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\nAccept-Language: zh-CN\r\nUpgrade-Insecure-Requests: 1\r\nUser-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\r\nAccept-Encoding: gzip, deflate\r\nHost: 127.0.0.1:8001\r\nConnection: Keep-Alive\r\n\r\n'
  2.  
  3. 解释:
     'method url protocol\r\nheaders-param1\r\nheaders-param2\r\nheaders-param3...headers-paramn\r\n\r\n'

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

  此外,因为这个请求只是一个简单的GET请求,请求信息到\r\n\r\n就结束了,事实上\r\n\r\n也是一个分割符,分割的是请求头和请求体,当一个请求是POST请求时,POST的参数就在请求体中,也就是说post_params  = data.split("\r\n\r\n")[1]。而在例1中,也用"\r\n\r\n"分割开了响应头和响应体:conn.send(b"HTTP/1.1 200 OK\r\n\r\n"),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服务端,加入路由系统:

  1. # 例2
  2. import socket
  3.  
  4. def index():
  5. return b'Hello World!'
  6.  
  7. def func1():
  8. return b"I'm not hungry yet!"
  9.  
  10. def func2():
  11. return b"Cheers!"
  12.  
  13. # 路由表
  14. routers = [
  15. ('/', index),
  16. ('/eat', func1),
  17. ('/drank', func2),
  18. ]
  19.  
  20. server = socket.socket()
  21. server.bind(("127.0.0.1", 8001))
  22. server.listen(5)
  23.  
  24. while True:
  25.  
  26. conn, addr = server.accept()
  27. data = str(conn.recv(8096), encoding='utf-8')
  28. headers, bodys = data.split('\r\n\r\n') # 分割出请求头,请求体
  29. temp_list = headers.split('\r\n')
  30. method, url, protocal = temp_list[0].split(' ') # 分割出请求方法,url,协议
  31. conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
  32.  
  33. func_name = None
  34. for item in routers: # 路由匹配,根据url获取相应的处理函数
  35. if url == item[0]:
  36. func_name = item[1]
  37. break
  38.  
  39. if func_name:
  40. response = func_name()
  41. else:
  42. response = b'404 not found' # 假如url不在路由系统中,模拟返回404
  43. conn.send(response)
  44. conn.close()

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

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

 五. 模板引擎渲染

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

  1. # 例3
  2. # server.py
  3. import socket
  4.  
  5. def index():
  6. with open('index.html', 'r') as f: # 读取html文件内容作为响应体数据
  7. response = f.read()
  8. return response.encode('utf-8')
  9.  
  10. # 路由表
  11. routers = [
  12. ('/', index),
  13. ]
  14.  
  15. server = socket.socket()
  16. server.bind(("127.0.0.1", 8001))
  17. server.listen(5)
  18.  
  19. while True:
  20. conn, addr = server.accept()
  21. data = str(conn.recv(8096), encoding='utf-8')
  22. headers, bodys = data.split('\r\n\r\n')
  23. temp_list = headers.split('\r\n')
  24. method, url, protocal = temp_list[0].split(' ')
  25. conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
  26.  
  27. func_name = None
  28. for item in routers:
  29. if url == item[0]:
  30. func_name = item[1]
  31. break
  32.  
  33. if func_name:
  34. response = func_name()
  35. else:
  36. response = b'404 not found'
  37. conn.send(response)
  38. conn.close()
  39.  
  40. # index.html
  41. <!DOCTYPE html>
  42. <html lang="en">
  43. <head>
  44. <meta charset="UTF-8">
  45. <title>"HELLO WORLD"</title>
  46. </head>
  47. <body>
  48. <table border="">
  49. <tr>
  50. <th>Month</th>
  51. <th>Savings</th>
  52. </tr>
  53. <tr>
  54. <td>January</td>
  55. <td>$100</td>
  56. </tr>
  57. </table>
  58. </body>
  59. </html>

 

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

  

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

  1. # 例4
  2. # server.py
  3. import socket
  4. import datetime
  5.  
  6. def index():
  7. with open('index2.html', 'r') as f: # 读取html文件内容作为响应体数据
  8. response = f.read()
  9. response = response.replace('@temp@', str(datetime.datetime.now())) # 获取当前时间,替换html文件内容中的占位符
  10. return response.encode('utf-8')
  11.  
  12. # 路由表
  13. routers = [
  14. ('/', index),
  15. ]
  16.  
  17. server = socket.socket()
  18. server.bind(("127.0.0.1", 8001))
  19. server.listen(5)
  20.  
  21. while True:
  22. conn, addr = server.accept()
  23. data = str(conn.recv(8096), encoding='utf-8')
  24. headers, bodys = data.split('\r\n\r\n')
  25. temp_list = headers.split('\r\n')
  26. method, url, protocal = temp_list[0].split(' ')
  27. conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
  28.  
  29. func_name = None
  30. for item in routers:
  31. if url == item[0]:
  32. func_name = item[1]
  33. break
  34.  
  35. if func_name:
  36. response = func_name()
  37. else:
  38. response = b'404 not found'
  39. conn.send(response)
  40. conn.close()

  41. # index2.html
  42. <!DOCTYPE html>
  43. <html lang="en">
  44. <head>
  45. <meta charset="UTF-8">
  46. <title>"HELLO WORLD"</title>
  47. </head>
  48. <body>
  49. <table border="">
  50. <tr>
  51. <th>Month</th>
  52. <th>Savings</th>
  53. </tr>
  54. <tr>
  55. <td>January</td>
  56. <td>$100</td>
  57. </tr>
  58. <tr>
  59. <td>time</td>
  60. <!--用特殊符号作为占位符-->
  61. <td>@temp@</td>
  62. </tr>
  63. </table>
  64. </body>
  65. </html>

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

 

 六. 结语

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

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

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

Python Web框架本质——Python Web开发系列一的更多相关文章

  1. Web 框架本质解析

    一  Web框架本质 1. 自己开发Web框架 - socket - http协议 - HTML知识 - 数据库(pymysql,SQLAlchemy) HTTP: 无状态.短连接 TCP: 不断开 ...

  2. Python开发【第十四篇】:Web框架本质

    Web框架本质 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 ...

  3. Python自动化运维之26、Web框架本质、MVC与MTV

    一.Web框架本质 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env python #coding:ut ...

  4. 【python】-- web框架本质

    web框架 一.web框架简述 所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. import socket def handle_request( ...

  5. Web框架本质

    Web框架本质 众所周知,对于所有的Web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端. #!/usr/bin/env python #coding:utf- ...

  6. Web框架本质及第一个Django实例

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

  7. Web框架本质及第一个Django实例 Web框架

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

  8. WEB框架本质和第一个Django实例

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

  9. Django之Web框架本质及第一个Django实例

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

随机推荐

  1. 手把手教您在 Windows Server 2019 上使用 Docker

    配置 Windows 功能 要运行容器,您还需要启用容器功能 Install-WindowsFeature -Name Containers 在 Window Server 2019 上安装 Dock ...

  2. springboot An incompatible version [1.1.32] of the APR based Apache Tomcat Native library is installed, while Tomcat requires version [1.2.14]

    1.错误 An incompatible version [1.1.32] of the APR based Apache Tomcat Native library is installed, wh ...

  3. Python学习笔记(20)-文件和文件夹的移动、复制、删除、重命名

    一,概述 python中对文件和文件夹进行移动.复制.删除.重命名,主要依赖os模块和shutil模块,要死记硬背这两个模块的方法还是比较困难的,可以用一个例子集中演示文件的移动.复制.删除.重命名, ...

  4. vue猜数字游戏

    <!doctype html> <html> <head> <meta charset="UTF-8"> <title> ...

  5. unity全屏截图

    using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; ...

  6. python列表-定义

    一.定义: 1.“列表”是一个值,它包含多个字构成的序列. 2.术语“列表值”指的是列表本身(它作为一个值,可以保存在变量中,或传递给函数,像所有其他值一样),而不是指列表值之内的那些值.列表值看起来 ...

  7. 小白学Python(14)——pyecharts 绘制K线图 Kline/Candlestick

    Kline-基本示例 from pyecharts import options as opts from pyecharts.charts import Kline data = [ [2320.2 ...

  8. servlet到springmvc的演进

    1.简单看看servlet 1.1.servlet继承关系 先看看下面servlet的这个继承关系,有点印象即可(可以暂时忽略ServletConfig,这个接口就是让我们可以从web.xml文件中拿 ...

  9. 一篇包含了react所有基本点的文章

    去年,我写了一本关于学习React.js的小书,原来是大约100页. 今年我要挑战自己,把它归纳为一篇文章. 本文不会涵盖什么是React,或者为什么要学习它. 相反,这是面向已经熟悉JavaScri ...

  10. Leetcode Lect7 哈希表

    传统的哈希表 对于长度为n的哈希表,它的存储过程如下: 根据 key 计算出它的哈希值 h=hash(key) 假设箱子的个数为 n,那么这个键值对应该放在第 (h % n) 个箱子中 如果该箱子中已 ...