Web框架原理
我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。
先写一个
原始的web框架
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(5)
while True:
conn, addr = sk.accept()
data = conn.recv(1024)
print(data) # 打印浏览器发过来的消息并分析
conn.send(b'ok')
conn.close()
可以说Web服务本质上都是在这几行代码基础上扩展出来的。这段代码就是它们的祖宗。
用户的浏览器一输入网址,会给服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,这互联网还能玩么?
所以,必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。
这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。
HTTP协议主要规定了客户端和服务器之间的通信格式,那HTTP协议是怎么规定消息格式的呢?
运行上面的代码,在浏览器输入服务器的地址和端口
得到浏览器发过来的data的打印结果:
# data结果
'''
1.请求首行:
b'GET / HTTP/1.1\r\n
2.请求体:一大堆K:V键值对
Host: 127.0.0.1:8080\r\n
Connection: keep-alive\r\n
Cache-Control: max-age=0\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36 chrome-extension\r\n
Sec-Fetch-Mode: navigate\r\n
Sec-Fetch-User: ?1\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\n
Sec-Fetch-Site: cross-site\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9,en-GB;q=0.8,en;q=0.7\r\n
\r\n
3.请求体:
这里是请求数据,get请求没有,post请求才有
'''
我们发现收发的消息需要按照一定的格式来,
1.数据格式
get请求格式
请求首行(请求方式,协议版本。。。)
请求头(一大堆k:v键值对)
\r\n
请求体(真正的数据 发post请求的时候才有 如果是get请求不会有)
响应格式
响应首行
响应头
\r\n
响应体
HTTP GET请求的格式:
HTTP响应的格式:
这里就需要了解一下HTTP协议了。
HTTP协议介绍
HTTP协议对收发消息的格式要求
每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。 HTTP响应的Header中有一个 Content-Type
表明响应的内容格式。如 text/html
表示HTML网页。
HTTP协议特点:
超文本传输协议
1.四大特性
1.基于TCP/IP之上作用于应用层
2.基于请求响应
3.无状态 cookie session token...
4.无连接
2.响应状态码
用特定的数字表示一些意思
1XX:服务端已经成功接收到了你的数据 正在处理 你可以继续提交其他数据
2XX:服务端成功响应(200请求成功)
3XX:重定向
4XX:请求错误(404 请求资源不存在 403 拒绝访问)
5XX:服务器内部错误(500 )
自定义web框架完整版
如果我们想要自己写的web server服务端真正运行起来,达到一种请求与响应的对应关系,我们必须要在sercer服务端给客户端回复消息的时候,按照HTTP协议的规则加上响应状态行 ,就是 协议版本+状态码+状态描述符:b'HTTP/1.1 200 OK\r\n\r\n'
如下例子:
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen(5)
while True:
conn, addr = sk.accept()
data = conn.recv(1024)
print(data)
# 需要向客服端发送响应头,客户端才能正常显示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
conn.send(b'hello world')
conn.close()
根据不同的路径返回不同的内容的Web服务端
如果我们在浏览器客户端输入:http://127.0.0.1:8080/home
,浏览器的页面显示就为home,那么可以这样做:
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 绑定IP和端口
sk.listen(5) # 监听
while True:
# 等待连接
conn, addr = sk.accept()
# 接收客户端返回的信息
data = conn.recv(1024)
# print(data)
# 从data中取到路径 并将收到的字节类型的数据转换成字符串
# data = str(data,encoding='utf8')
data = data.decode('utf8')
# 按\r\n分割
data1 = data.split('\r\n')[0]
# print(data1)
# 请求首行的进行切割拿到url url是我们从浏览器发来的消息分离出来的访问路径
url=data1.split(' ')[1]
# print(url)
# 必须遵循HTTP协议,需要向客服端发送状态行,客户端才能正常显示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
# 根据不同的路径返回不同内容
if url == '/index':
response = b'index'
elif url == '/home':
response = b'home'
else:
response = b'404 not found!!!'
conn.send(response)
conn.close()
不同路径不同内容-函数版
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 绑定IP和端口
sk.listen(5) # 监听
# 将返回不同的内容部分封装成函数
def index(url):
res = f'这是{url}页面|'
return bytes(res,encoding='gbk')
# return res.encode('utf-8')
def home(url):
res = f'这是{url}页面|'
return bytes(res,encoding='gbk')
# return res.encode('utf-8')
while True:
# 等待连接
conn, addr = sk.accept()
# 接收客户端返回的信息
data = conn.recv(1024)
# print(data)
# 从data中取到路径 并将收到的字节类型的数据转换成字符串
# data = str(data,encoding='utf8')
data = data.decode('utf8')
# 按\r\n分割
data1 = data.split('\r\n')[0]
# print(data1)
# 请求首行的进行切割拿到url url是我们从浏览器发来的消息分离出来的访问路径
url=data1.split(' ')[1]
# print(url)
# 根据不同的路径返回不同内容
if url == '/index':
response = index(url)
elif url == '/home':
response = home(url)
else:
response = b'404 not found!!!'
# 必须遵循HTTP协议,需要向客服端发送状态行,客户端才能正常显示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
conn.send(response)
conn.close()
不同路径不同内容-函数进阶版
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 绑定IP和端口
sk.listen(5) # 监听
# 定义一个url和实际要执行的函数的对应关系
def home(url):
res = bytes(url, encoding='utf8')
return res
def index(url):
res = bytes(url, encoding='utf8')
return res
# 定义一个url和要执行函数对应关系的字典
dt = {
'/index':index,
'/home':home
}
while True:
# 等待连接
conn, addr = sk.accept()
# 接收客户端返回的信息
data = conn.recv(1024)
# print(data)
# 从data中取到路径 并将收到的字节类型的数据转换成字符串
# data = str(data,encoding='utf8')
data = data.decode('utf8')
# 按\r\n分割
data1 = data.split('\r\n')[0]
# print(data1)
# 请求首行的进行切割拿到url url是我们从浏览器发来的消息分离出来的访问路径
url=data1.split(' ')[1]
# print(url)
func = None
# 根据不同的路径返回不同内容
for k,v in dt.items():
print(k,v)
if url == k:
func = v
break
if func:
response = func(url)
else:
response = b'404 not found!!!'
# 必须遵循HTTP协议,需要向客服端发送状态行,客户端才能正常显示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
conn.send(response)
conn.close()
返回具体的HTML页面
首先创建我们需要的html页面,然后把在代码里面以rb模式读取出来,发送到浏览器
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 绑定IP和端口
sk.listen(5) # 监听
def home(url):
with open('home页面.html','rb') as fr:
res = fr.read()
return res
def index(url):
with open('index页面.html', 'rb') as fr:
res = fr.read()
return res
# 定义一个url和实际要执行的函数的对应关系
dt = {
'/index':'index',
'/home':'home'
}
while True:
# 等待连接
conn, addr = sk.accept()
# 接收客户端返回的信息
data = conn.recv(1024)
# print(data)
# 从data中取到路径 并将收到的字节类型的数据转换成字符串
# data = str(data,encoding='utf8')
data = data.decode('utf8')
# 按\r\n分割
data1 = data.split('\r\n')[0]
# print(data1)
# 请求首行的进行切割拿到url url是我们从浏览器发来的消息分离出来的访问路径
url=data1.split(' ')[1]
# print(url)
func = None
# 根据不同的路径返回不同内容
for k,v in dt.items():
print(k,v)
if url == k:
func = v
break
if func:
response = func(url)
else:
response = b'404 not found!!!'
# 必须遵循HTTP协议,需要向客服端发送状态行,客户端才能正常显示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
conn.send(response)
conn.close()
返回动态的网页
上面的网页是不会变化的,如何实现得到一个动态的网站呢?下面做个例子:每次刷新都在获取新的时间,模拟动态的数据
import socket
import datetime
sk = socket.socket()
sk.bind(('127.0.0.1', 8080)) # 绑定IP和端口
sk.listen(5) # 监听
def home(url):
with open('get_time.html', 'r',encoding='utf8') as fr:
res = fr.read()
now = datetime.datetime.now().strftime("%Y-%m-%d %X")
# 在网页中定义好特殊符号,用动态的数据替换特殊字符
res = res.replace('*time*',now).encode('utf8')
return res
def index(url):
with open('index页面.html', 'rb') as fr:
res = fr.read()
return res
# 定义一个url和实际要执行的函数的对应关系
dt = {
'/index': index,
'/home': home
}
while True:
# 等待连接
conn, addr = sk.accept()
# 接收客户端返回的信息
data = conn.recv(1024)
# print(data)
# 从data中取到路径 并将收到的字节类型的数据转换成字符串
# data = str(data,encoding='utf8')
data = data.decode('utf8')
# 按\r\n分割
data1 = data.split('\r\n')[0]
# print(data1)
# 请求首行的进行切割拿到url url是我们从浏览器发来的消息分离出来的访问路径
url=data1.split(' ')[1]
# print(url)
func = None
# 根据不同的路径返回不同内容
for k,v in dt.items():
print(k,v)
if url == k:
func = v
break
if func:
response = func(url)
else:
response = b'404 not found!!!'
# 必须遵循HTTP协议,需要向客服端发送状态行,客户端才能正常显示信息
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
conn.send(response)
conn.close()
什么是服务器程序和应用程序?
对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。
服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理
应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。
为了统一规范,设立了一个标准,服务器和框架都支持这个标准。这样不同的服务器就能适应不同的开发框架,不同的开发框架也就能适应不同的服务器。
WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。
常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器
利用wsgiref模块创建web服务器
from wsgiref.simple_server import make_server
def run(environ,response):
# 当客户发送请求过来时,会先调用wsgi内部接口,然后调用run函数,并且携带了两个参数给run函数
# environ:一个包含所有HTTP请求信息的dict对象;
# response:一个发送HTTP响应的函数。
# 向客户端发送的状态码和头信息
response('200 OK',[('content-type','text/html; charset=utf-8'),])
# 返回的是一个列表,内容是发送给客户端展示的内容
return ['hello world'.encode('utf-8')]
if __name__ == '__main__':
# 相当于socket绑定ip和端口
server = make_server('127.0.0.1',8080,run)
# 实时监听地址,等待客户端连接,有连接来了就调用run函数
server.serve_forever()
wsgiref模块实现上所有述功能的服务端
from wsgiref.simple_server import make_server
import datetime
def index(url):
with open('index页面.html', 'rb') as fr:
data = fr.read()
return data
def home(url):
with open('home页面.html', 'rb') as fr:
data = fr.read()
return data
def get_time(url):
now = datetime.datetime.now().strftime('%Y-%m-%d %X')
with open('get_time.html','r',encoding='utf-8') as fr:
data = fr.read()
data = data.replace('*time*',now)
return data.encode('utf-8')
dic={
'/index':index,
'/home':home,
'/get_time':get_time
}
def run(env,response):
# 发送状态码和头信息到客户端
response('200 ok',[('content-type','text/html;charset=utf-8'),])
# print(env)
# 因为env就是客户端发过来的请求信息(k:v键值对形式),
# 通过打印信息得出PATH_INFO就是请求的url,
url = env.get('PATH_INFO')
print(url)
func = None
if url in dic:
func = dic.get(url)
if func:
res = func(url)
else:
res = b'404 not found!!!'
return [res]
if __name__ == '__main__':
server = make_server('127.0.0.1',8080,run)
server.serve_forever()
jinja2模块
jinja2模块,跟上面的用特殊符号去替换需要展示的内容的原理是一样的,jinja2他将html页面封装成一个可以渲染数据的模板,然后得到我们真正想要返回给浏览器的html页面。
例子:从数据库中获取数据展示到浏览器。
1.创建一张user表:
2.创建html文件
Title
用户列表
id
name
pwd
{% for user_dict in user_list %}
{{ user_dict.id }}
{{ user_dict.name }}
{{ user_dict.hobby}}
{% endfor %}
3.使用jinja2渲染html文件。
from wsgiref.simple_server import make_server
from jinja2 import Template
import pymysql
# 从数据库中获取,并使用jinja2将数据渲染到html
def get_db(url):
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='tomjoy',
password='123456',
database='user_info',
charset='utf8',
autocommit=True
)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
sql = "select * from user"
cursor.execute(sql)
# 1.从数据库中获取数据
res = cursor.fetchall()
with open('get_db.html','r',encoding='utf8') as fr:
data = fr.read()
# 2.生成html渲染模板对象
temp = Template(data)
# 3.将数据库中获取回来的数据,传到html模板对象进行渲染,
# 返回一个我们真正想要展示的html页面
ret = temp.render(user_list=res)
return ret.encode('utf8')
dic = {
'/get_db' : get_db
}
def run(env,response):
response('200 ok',[('content-type','text/html;charset=utf-8'),])
func = None
url = env.get('PATH_INFO')
if url in dic:
func = dic.get(url)
if func:
res = func(url)
else:
res = b'404 not found!!!'
return [res]
if __name__ == '__main__':
server = make_server('127.0.0.1',8080,run)
server.serve_forever()
jinja2模板语法(极其贴近python后端语法)
{{ user }}
{{ user.name }}
{{ user['pwd'] }}
{{ user.get('hobby') }}
{% for user_dict in user_list %}
{{ user_dict.id }}
{{ user_dict.name }}
{{ user_dict.pwd }}
{% endfor %}
模板渲染:利用模板语法 实现后端传递数据给前端html页面
模板语法书写格式:
变量相关:{{}}
逻辑相关:{%%}
注意:Django的模板语法由于是自己封装好的,只支持 点.取值
注:模板渲染的原理就是字符串替换,我们只要在HTML页面中遵循jinja2的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。
效果如下:
Django
1.安装django
pip3 install django==1.11.11
2.创建django项目
在cmd命令行下创建一个名为mysite的Django项目
django-admin startproject mysite
3.目录介绍
mysite
├── manage.py # Django入口管理文件
└── templates # 存放html文件
└── mysite # 项目目录
├── __init__.py
├── settings.py # 配置
├── urls.py # 路由 --> URL和函数的对应关系
└── wsgi.py # runserver命令就使用wsgiref模块做简单的web server
4.模板文件配置
使用命令行创建django项目 不会自动帮你创建templates文件夹, 只能自己创建
在.settings文件中 需要你手动在TEMPLATES的DIRS写配置
[os.path.join(BASE_DIR, 'templates')]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # templates 文件夹位置
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
5.启动django项目
python manage.py runserver
6.创建应用app01
python manage.py startapp app01
7.app应用目录:
└── app01 # 项目目录
├── migrations文件夹 # 存放数据库迁移记录
├── __init__.py
├── admin.py # django后台管理
└── apps.py # 注册相关
└── models.py # 模型类
└── tests.py # 测试文件
└── views.py # 存放视图函数
注意:如果是在命令行下创建app后,需要你去settings配置文件中注册添加app名字。这样django项目才能识别到你这个app
8.静态文件配置:
静态文件配置官方文档
什么是静态文件?
静态文件就是在打开网页时所用到的 图片、 js、css以及第三方的框架bootstrap、fontawesome、sweetalert
通常情况下 网站所用到的静态文件资源 统一都放在static文件夹下,为了方便识别
STATIC_URL = '/static/' # 是访问静态资源的接口前缀,并不是存放静态文件的文件夹
"""只要你想访问静态资源 你就必须以static开头"""
# 手动在settings最底下添加配置静态文件访问资源
# 下面都是存放静态文件的文件夹的路径
# 从上往下找静态文件,找不到就报错
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static'),
os.path.join(BASE_DIR,'static1'),
os.path.join(BASE_DIR,'static2'),
]
9.禁用中间件:
前期为了方便表单提交测试。在settings配置文件中暂时禁用csrf中间件
10.重定向:
重定向的意思就是,我访问的链接不是我刚刚输入的那个链接,而是我一输入他就跳转到了另外一个链接,这就是重定向
最后注意事项:
1.计算机的名称不能有中文
2.一个pycharm窗口就是一个项目
3.项目名里面尽量不要用中文
django版本问题
1.X 2.X 现在市面上用的比较多的还是1.X
推荐使用1.11.9~1.11.13
django安装
pip3 install django==1.11.11
如何验证django是否安装成功
命令行直接敲django-admin
一个django项目就类似于是一所大学,而app就类似于大学里面的学院
django其实就是用来一个个应用的
一个app就相当于一块独立的功能
用户功能
管理功能
.........
django支持任意多个app