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('
')
# 得到请求头中的每一行数据
temp_list = headers.split('
')
# 对请求头中的第一行数据进行提取,取得请求方法、请求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('
')
# 得到请求头中的每一行数据
temp_list = headers.split('
')
# 对请求头中的第一行数据进行提取,取得请求方法、请求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('
')
# 得到请求头中的每一行数据
temp_list = headers.split('
')
# 对请求头中的第一行数据进行提取,取得请求方法、请求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