一、Web框架的本质
1、本质
实际上Web应用本质上就是一个socket服务端,
而用户的浏览器就是一个socket客户端。
2、最原始的web框架
socket服务端
import socket
sk = socket.socket()
sk.bind(("127.0.0.1", 80))
sk.listen()
while True:
conn, addr = sk.accept()
data = conn.recv(5120)
print(data) # 打印一下接收到的数据是什么
conn.send(b"Hello World")
conn.close()
上面这几行代码就是最基础的web框架的服务端,
用户使用浏览器输入网址(IP),就会给服务端发送请求消息,
比如你在浏览器输入上面服务器的ip地址 127.0.0.1:80
浏览器就会帮你给服务端发送请求消息,服务端就可以接收到你的请求消息,
并对此可以做出相应的操作。
当然了,虽然上面的代码是web框架的本质,但是还不能进行消息的响应,为什么呢?
因为没有一个统一的规则啊,你想想如果每个人的网站都按照自己的意愿随便定制规则,
那浏览器不就崩了?
所以必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。
这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来进行
现在去看一眼刚才收到的data数据是什么(客户端的请求数据)
b\'GET /index/ HTTP/1.1\\r\\nHost: 127.0.0.1:8080\\r\\nConnection: keep-alive\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/69.0.3497.92 Safari/537.36\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: zh-CN,zh;q=0.9\\r\\n\\r\\n\'
然后可以再看一眼平时我们访问百度时浏览器接收到的响应信息
HTTP/1.1 301 Moved Permanently
Date: Wed, 24 Oct 2018 07:06:00 GMT
Content-Type: text/html; charset=UTF-8
Content-Length: 147
Location: https://www.cnblogs.com/
X-UA-Compatible: IE=10
X-Frame-Options: SAMEORIGIN
这样看起来,好像找不出什么规则,那就先了解一下HTTP协议(上一篇博客),在回来看看是否发现什么规则
HTTP协议对收发消息的格式要求:
每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的(就是可以不写)。
HTTP响应的Header中有一个 Content-Type表明响应的内容格式。如 text/html表示HTML网页。
那么上面的请求数据data,我们也可以给它分出Header和Body两部分(\\r\\n代表回车换行),如下:
GET /index/ HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
请求数据
所以我们想要开始那段代码能顺利给客户端返回消息,我们也按照HTTP协议进行发送就好了
import socket
sk = socket.socket()
sk.bind((\'127.0.0.1\',80))
sk.listen()
while True:
conn,addr = sk.accept()
data = conn.recv(5120)
print(data)
conn.send(b\'HTTP/1.1 200 OK\\r\\n\\r\\nHello World\') # HTTP/1.1 200 OK\\r\\n\\r\\n 是响应头部为空,响应正文为Hello World的HTTP响应格式
conn.close()
这个时候你在浏览器输入127.0.0.1:80就可以收到服务端给你返回的消息了
3、服务器例子
1、根据不同的路径返回不同的内容
注意:我们服务器接收到的请求数据全是bytes类型的,回车换行是用\\r\\n表示的
原始请求数据:b\'GET /index/ HTTP/1.1\\r\\nHost: 127.0.0.1:8080\\r\\nConnection: keep-alive\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: zh-CN,zh;q=0.9\\r\\n\\r\\n\'
"""
Web框架原理:
根据不同的URL返回不同的内容
1. 先拿到用户访问的URL是什么
2. 返回不同的内容
"""
import socket
sk = socket.socket()
sk.bind((\'127.0.0.1\',8080))
sk.listen()
while 1:
conn, addr = sk.accept()
# socket收到的消息是bytes类型的
data = conn.recv(5120)
# 从请求的消息中拿到请求的URL是什么
data_str = data.decode(\'utf8\')
# 按照\\r\\n分割字符串
list1 = data_str.split(\'\\r\\n\')
# url在第一个元素里面再进行分割取到
url = list1[0].split()[1]
# 对不同的url返回不同的消息
if url == \'/index/\':
msg = \'This is index page\'
elif url == \'/home/\':
msg = \'This is home page\'
else:
msg = \'404 Not Found\'
conn.send(b\'HTTP/1.1 200 OK\\r\\n\\r\\n\') # 先发送响应行
conn.send(msg.encode(\'utf8\'))
conn.close()
此时你在浏览器输入:
http://127.0.0.1:8080 ---> 404 Not Found
http://127.0.0.1:8080/index/ ---> This is index page
http://127.0.0.1:8080/home/ ---> This is home page
2、根据不同的路径返回不同的内容函数版
"""
Web框架原理:
根据不同的URL返回不同的内容函数版
1. 先拿到用户访问的URL是什么
2. 返回不同的内容
"""
import socket
sk = socket.socket()
sk.bind((\'127.0.0.1\',8080))
sk.listen()
# 定义处理用户请求的函数
def index(url):
s = \'你访问的是%s页面\' %url
return s.encode(\'utf8\')
def home(url):
s = \'你访问的是%s页面\' %url
return s.encode(\'utf8\')
while 1:
conn, addr = sk.accept()
# socket收到的消息是bytes类型的
data = conn.recv(5120)
# 从请求的消息中拿到请求的URL是什么
data_str = data.decode(\'utf8\')
# 按照\\r\\n分割字符串
list1 = data_str.split(\'\\r\\n\')
# url在第一个元素里面再进行分割取到
url = list1[0].split()[1]
# 对不同的url返回不同的消息
if url == \'/index/\':
msg = index(url)
elif url == \'/home/\':
msg = home(url)
else:
msg = b\'404 Not Found\'
# 因为有中文,所有要在响应头部添加Content-Type: text/html; charset=utf-8
conn.send(b\'HTTP/1.1 200 OK\\r\\nContent-Type: text/html; charset=utf-8\\r\\n\\r\\n\') # 先发送响应行
conn.send(msg)
conn.close()
3、根据不同的路径返回不同的内容函数进阶版
"""
Web框架原理:
根据不同的URL返回不同的内容函数进阶版
1. 先拿到用户访问的URL是什么
2.设置一个url和函数的对应关系
3. 根据对应关系返回不同的内容
"""
import socket
sk = socket.socket()
sk.bind((\'127.0.0.1\',8080))
sk.listen()
# 定义处理用户请求的函数
def index(url):
s = \'你访问的是%s页面\' %url
return s.encode(\'utf8\')
def home(url):
s = \'你访问的是%s页面\' %url
return s.encode(\'utf8\')
# 定义一个用户访问的url和要执行的函数的对应关系
url_func = [
(\'/index/\', index),
(\'/home/\', home)
]
while 1:
conn, addr = sk.accept()
# socket收到的消息是bytes类型的
data = conn.recv(5120)
# 从请求的消息中拿到请求的URL是什么
data_str = data.decode(\'utf8\')
# 按照\\r\\n分割字符串
list1 = data_str.split(\'\\r\\n\')
# url在第一个元素里面再进行分割取到
url = list1[0].split()[1]
# 循环url列表,对不同的url返回不同的消息
func = None
for i in url_func:
if i[0] == url:
func = i[1]
break
if func:
msg = func(url)
else:
msg = b\'404 Not Found\'
# 因为有中文,所有要在响应头部添加Content-Type: text/html; charset=utf-8
conn.send(b\'HTTP/1.1 200 OK\\r\\nContent-Type: text/html; charset=utf-8\\r\\n\\r\\n\') # 先发送响应行
conn.send(msg)
conn.close()
4、返回具体的html页面
import socket
sk = socket.socket()
sk.bind((\'127.0.0.1\',8080))
sk.listen()
# 定义处理用户请求的函数
def index(url):
s = \'你访问的是%s页面\' %url
return s.encode(\'utf8\')
def home(url):
s = \'你访问的是%s页面\' %url
return s.encode(\'utf8\')
def table(url):
# 返回TableOp.html
with open(\'TableOp.html\', mode=\'rb\') as f:
return f.read()
# 定义一个用户访问的url和要执行的函数的对应关系
url_func = [
(\'/index/\', index),
(\'/home/\', home),
(\'/table/\',table)
]
while 1:
conn, addr = sk.accept()
# socket收到的消息是bytes类型的
data = conn.recv(5120)
# 从请求的消息中拿到请求的URL是什么
data_str = data.decode(\'utf8\')
# 按照\\r\\n分割字符串
list1 = data_str.split(\'\\r\\n\')
# url在第一个元素里面再进行分割取到
url = list1[0].split()[1]
# 循环url列表,对不同的url返回不同的消息
func = None
for i in url_func:
if i[0] == url:
func = i[1]
break
if func:
msg = func(url)
else:
msg = b\'404 Not Found\'
# 因为有中文,所有要在响应头部添加Content-Type: text/html; charset=utf-8
conn.send(b\'HTTP/1.1 200 OK\\r\\nContent-Type: text/html; charset=utf-8\\r\\n\\r\\n\') # 先发送响应行
conn.send(msg)
conn.close()
5、返回动态的html页面
"""
因为时间是会动的,所以这里用时间戳代表动态的事件
"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>这是index页面!</h1>
<p>现在的时间是:@t@</p>
</body>
</html>
index页面代码
import time
import socket
sk = socket.socket()
sk.bind((\'127.0.0.1\',8080))
sk.listen()
# 定义处理用户请求的函数
def index(url):
with open(\'index.html\', \'r\', encoding=\'utf8\') as f1:
html_s = f1.read()
# 根据用户不同,取出不同的数据
# 用不同的数据去替换页面上的特殊符号
now = str(time.strftime("%H:%M:%S"))
msg = html_s.replace(\'@t@\', now)
return msg.encode(\'utf8\')
def home(url):
s = \'你访问的是%s页面\' %url
return s.encode(\'utf8\')
def table(url):
# 返回TableOp.html
with open(\'TableOp.html\', mode=\'rb\') as f:
return f.read()
# 定义一个用户访问的url和要执行的函数的对应关系
url_func = [
(\'/index/\', index),
(\'/home/\', home),
(\'/table/\', table)
]
while 1:
conn, addr = sk.accept()
# socket收到的消息是bytes类型的
data = conn.recv(5120)
# 从请求的消息中拿到请求的URL是什么
data_str = data.decode(\'utf8\')
# 按照\\r\\n分割字符串
list1 = data_str.split(\'\\r\\n\')
# url在第一个元素里面再进行分割取到
url = list1[0].split()[1]
# 循环url列表,对不同的url返回不同的消息
func = None
for i in url_func:
if i[0] == url:
func = i[1]
break
if func:
msg = func(url)
else:
msg = b\'404 Not Found\'
# 因为有中文,所有要在响应头部添加Content-Type: text/html; charset=utf-8
conn.send(b\'HTTP/1.1 200 OK\\r\\nContent-Type: text/html; charset=utf-8\\r\\n\\r\\n\') # 先发送响应行
conn.send(msg)
conn.close()
二、服务器程序和应用程序
1、本质介绍
对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序,然后通过一个协议连接起来,如下:
收发socket消息(Web服务器程序) --> uWsgi、Gunicorn、wsgiref (nginx和tomcat)
WSGI协议
业务逻辑不同(Web应用程序) --> Django、Flask、Webpy、bottle、Tornado
服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。
应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。
这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。
这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。
常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。
Python中Web框架的分类
a. 收发socket消息
b. 根据不同的URL执行不同的函数(业务逻辑)
c. 字符串替换(动态网页)
1. 第一种分类:(按照上面的三部分功能划分)
1. 自己实现b和c,使用第三方的a --> Django
2. 自己实现b,使用第三方的a和c --> Flask
3. 自己实现a、b、c --> Tornado
2. 第二种分类:
1. Django(大而全)
2. 其他
2、例子
1、wsgiref版web开发
"""
利用wsgiref模块来替换我们自己写的web框架的socket server部分
"""
import time
from wsgiref.simple_server import make_server
# 定义处理用户请求的函数
def index(url):
with open(\'index.html\', \'r\', encoding=\'utf8\') as f1:
html_s = f1.read()
# 根据用户不同,取出不同的数据
# 用不同的数据去替换页面上的特殊符号
now = str(time.strftime("%H:%M:%S"))
msg = html_s.replace(\'@t@\', now)
return msg.encode(\'utf8\')
def home(url):
s = \'你访问的是%s页面\' %url
return s.encode(\'utf8\')
def table(url):
# 返回TableOp.html
with open(\'TableOp.html\', mode=\'rb\') as f:
return f.read()
# 定义一个用户访问的url和要执行的函数的对应关系
url_func = [
(\'/index/\', index),
(\'/home/\', home),
(\'/table/\', table)
]
# wsgiref模块的格式要求
def run_server(environ, start_response):
start_response(\'200 OK\', [(\'Content-Type\', \'text/html;charset=utf8\'), ]) # 设置HTTP响应的状态码和头信息
url = environ[\'PATH_INFO\'] # 取到用户输入的url
# 循环url列表,对不同的url返回不同的消息
func = None
for i in url_func:
if i[0] == url:
func = i[1]
break
if func:
msg = func(url)
else:
msg = b\'404 Not Found\'
return [msg]
if __name__ == \'__main__\':
httpd = make_server(\'127.0.0.1\', 8080, run_server)
httpd.serve_forever()
2、wsgiref+jinja2版web开发
上面的代码实现了一个简单的动态,我完全可以从数据库中查询数据,然后去替换我html中的对应内容,然后再发送给浏览器完成渲染。 这个过程就相当于HTML模板渲染数据。
本质上就是HTML内容中利用一些特殊的符号来替换要展示的数据。 我这里用的特殊符号是我定义的,其实模板渲染有个现成的工具: jinja2
下载jinja2:在cmd命令行输入 pip install jinja2 安装第三方包
"""
利用wsgiref和jinja2模块动态渲染userinfo页面
"""
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>用户列表</title>
</head>
<body>
<table border="1">
<thead>
<tr>
<th>id值</th>
<th>姓名</th>
<th>密码</th>
</tr>
</thead>
<tbody>
{% for user in user_dict %}
<tr>
<td>{{user.id}}</td>
<td>{{user.username}}</td>
<td>{{user.password}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
userinfo代码
import time
import pymysql
from wsgiref.simple_server import make_server
from jinja2 import Template
# 定义处理用户请求的函数
def index(url):
with open(\'index.html\', \'r\', encoding=\'utf8\') as f1:
html_s = f1.read()
# 根据用户不同,取出不同的数据
# 用不同的数据去替换页面上的特殊符号
now = str(time.strftime("%H:%M:%S"))
msg = html_s.replace(\'@t@\', now)
return msg.encode(\'utf8\')
def home(url):
s = \'你访问的是%s页面\' %url
return s.encode(\'utf8\')
def table(url):
# 返回TableOp.html
with open(\'TableOp.html\', mode=\'rb\') as f:
return f.read()
def userinfo(url):
# 1. 连接数据库,把所有的用户数据拿到
conn = pymysql.connect(
host=\'127.0.0.1\',
port=3306,
user=\'root\',
password=\'123abc\',
database=\'userinfo\',
charset=\'utf8\'
)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
cursor.execute(\'select * from userinfo;\')
# 从数据库中拿到的数据user_dict
user_dict = cursor.fetchall()
with open(\'userinfo.html\',\'r\',encoding=\'utf8\') as f:
data = f.read()
# 读取网页内容,生成jinja2模板对象
template = Template(data)
# 让jinja2用数据替换HTML中的特殊符号,拿到新的html内容并返回
msg = template.render({\'user_dict\':user_dict})
return bytes(msg, encoding="utf8")
# 定义一个用户访问的url和要执行的函数的对应关系
url_func = [
(\'/index/\', index),
(\'以上是关于Web框架的原理和Django初识的主要内容,如果未能解决你的问题,请参考以下文章
初识Django —Python API接口编程入门
初识Django
初识django
Django框架-Django初识
Django初识(web框架,django下载安装,http协议)
Django——纯手撸简易web框架Django初识