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('\r\n\r\n') # 得到请求头中的每一行数据 temp_list = headers.split('\r\n') # 对请求头中的第一行数据进行提取,取得请求方法、请求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('\r\n\r\n') # 得到请求头中的每一行数据 temp_list = headers.split('\r\n') # 对请求头中的第一行数据进行提取,取得请求方法、请求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('\r\n\r\n') # 得到请求头中的每一行数据 temp_list = headers.split('\r\n') # 对请求头中的第一行数据进行提取,取得请求方法、请求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框架应该实现的功能
- Socket服务端
- 路由系统:根据URL来返回不同的内容,URL --> 函数
- 字符串加入响应体返回给用户:模板引擎渲染,字符串
5.2 Web框架的种类
根据以上的三种主要功能,web框架可以分为以下几类
- 自己实现了 1,2,3
- Tornado
- 实现了 2,3 ,而使用了第三方的Socket服务端
- Django
- 实现了 2 , 而使用了第三方的Socket服务器以及第三方的模板渲染引擎(如Jinja2)
- flask
本文作者:Pray
本文链接:https://www.cnblogs.com/hgzero/p/13216511.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步