文章目录
一、 Web框架本质
我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。
用户的浏览器一输入网址,会给服务端发送数据,那浏览器会发送什么数据?怎么发?这个谁来定? 你这个网站是这个规定,他那个网站按照他那个规定,这互联网还能玩么?
所以,必须有一个统一的规则,让大家发送消息、接收消息的时候有个格式依据,不能随便写。
这个规则就是HTTP协议,以后浏览器发送请求信息也好,服务器回复响应信息也罢,都要按照这个规则来。
HTTP协议主要规定了客户端和服务器之间的通信格式,那HTTP协议是怎么规定消息格式的呢?
让我们首先打印下我们在服务端接收到的消息是什么。
我们发现收发的消息需要按照一定的格式来,这里就需要了解一下HTTP协议了。
网络协议:
HTTP协议---------------数据传输是明文
HTTPS协议-------------数据传输是密文
websocket协议---------数据传输是密文
二、 HTTP协议
超文本传输协议:规定了浏览器与服务端之间数据交互的格式
1. HTTP协议的四大特性
1.基于TCP、IP协议作用于应用层之上的协议
2.基于请求响应 -- 浏览器给服务端发起请求,服务端收到后做出回应
3.无状态 -- 不保存用户端的登录状态,见你千百遍我都当你如初见
ps:cookie、session、token...
4.无(短)连接 -- 一次请求响应后即断开连接
ps:长连接:websocket
2. HTTP协议的数据传输格式
请求数据格式:
请求首行(请求方法...)
请求头(一大堆K:V键值对)
\r\n ---换行
请求体(并不是所有的请求方法都有 主要用来携带敏感性数据)响应数据格式:
响应首行(响应状态码...)
响应头(一大堆K:V键值对)
\r\n ---换行
响应体(展示给用户的数据)
3. 响应状态码
用简单的数字来表示一串中文意思
1XX:服务端已经接受到你的数据正在处理,你可以继续提交
2XX:200 OK >>>:请求成功
3XX:重定向 --原本想访问A,但是内部跳到B
4XX:403当前请求不符合条件 404请求资源不存在
5XX:服务器内部错误
ps:除了上述统一的响应状态码之外,公司还可以自定义自己的状态码
4. 请求方法:
1.get请求
朝别人索要数据
2.post请求
朝别人提交数据
>>>上述两种请求都可以携带额外的参数<<<
get请求携带数据的方式:
url?username=jason&hobby=mn
post请求携带数据的方式: 数据是放在请求体里面的
HTTP GET请求的格式:
HTTP响应的格式:
5. 请求数据格式示例:
# 请求首行--\r\n是换行符
b'GET / HTTP/1.1\r\n
# 请求头(都是一大堆K:V键值对)
Host: 127.0.0.1:8080\r\n
Connection: keep-alive\r\n
sec-ch-ua: "Google Chrome";v="89", "Chromium";v="89", ";Not A Brand";v="99"\r\n
sec-ch-ua-mobile: ?0\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.82 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n
Sec-Fetch-Site: none\r\n
Sec-Fetch-Mode: navigate\r\n
Sec-Fetch-User: ?1\r\n
Sec-Fetch-Dest: document\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
\r\n
# 请求体 (下面是空的:因为发出的是get请求,get请求不需要请求体)'
三、 纯手写web框架(了解即可)
1. 处女版自定义web框架
类型转换小常识
# 采用此类方式实现类型转换可以不需要用encode 和 decode
# 只需要认识str和bytes就行了
data = b'hello world'
# 转字符串
data1 = str(data,encoding='utf8')
print(data1)
# 转bytes类型
data2 = bytes(data1,encoding='utf8')
print(data2)
纯手撸web框架
import socket
server = socket.socket() # 默认就是TCP协议
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
conn, addr = server.accept() # 三次四次挥手
data = conn.recv(1024)
res = data.decode('utf8')
conn.send(b'HTTP/1.1 200 OK\r\n\r\n')
# 字符串切割获取地址
path = res.split(' ')[1]
# 判断地址
if path == '/index':
# conn.send(b'index')
with open(r'fh.html','rb') as f:
data = f.read()
conn.send(data)
elif path == '/login':
conn.send(b'login')
conn.close()
fh.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
</head>
<body>
<h1 class="text-center">这是一个非常牛逼的页面</h1>
</body>
</html>
问题:
- 服务端代码重复
- 手动处理http数据格式过于繁琐
2. wsgiref模块
基于wsgiref模块
解决了上述两个问题
我们利用wsgiref模块来替换我们自己写的web框架的socket server部分:
from wsgiref.simple_server import make_server
def run(request,response):
"""
:param request:请求相关的所有数据
:param response:响应相关的所有数据
:return:返回给浏览器的数据
"""
response('200 OK',[]) # 响应首行 响应头
current_path = request.get("PATH_INFO")
if current_path == '/index':
return [b'index']
elif current_path == '/login':
return [b'login']
return [b'404 error']
if __name__ == '__main__':
server = make_server('127.0.0.1',8080,run) # 一旦被访问会全部交给run函数处理
server.serve_forever() # 启动服务端
问题
- 网址很多的情况下如何匹配
- 网址多匹配如何解决
- 功能复杂代码块如何解决
3. 封装处理
1.定义一个网址与功能函数的对应关系
# 地址与功能的对应关系
urls = [
('/index',index_func),
('/login',login_func),
('/reg',reg_func),
]
2.按照功能的不同划分成不同的py文件
urls.py views.py 服务端.py
3.书写服务端代码
func = None
# for循环判断
for url_tuple in urls: # (),()
if current_path == url_tuple[0]:
# 将匹配到的函数名赋值给func变量
func = url_tuple[1]
break
# 判断func是否有值
if func:
# 执行对应的函数
res = func(request)
else:
res = errors(request)
return [bytes(res,encoding='utf8')]
4. 动静态网页
静态网页:
页面上的数据全部都是写死的,万年不变
动态网页:
页面上的数据来源于后端(代码、数据库)
1.访问网址展示当前时间(由后端模块生成并展示到html页面)
def get_time_func(request):
from datetime import datetime
current_time = datetime.now().strftime('%Y-%m-%d %X')
with open(r'get_time.html','r',encoding='utf8') as f:
data = f.read() # 字符串
data = data.replace('sadadadad',current_time)
return data
2.后端有一个字典,将该字段传递给html页面,并且在该页面上还可以使用字典取值的各种操作
python强大而优秀的三方库为我们解决了这个问题
实现模板渲染 模板语法的第三方模块:jinja2模块
安装方式:pip3 install jinja2
ps:该模块是flask框架必备的模块 所以下载flask也会自动下载该模块
def get_dict_func(request):
user_dict = {"username":'jason','password':123,'hobby':'study'}
from jinja2 import Template
with open(r'templates/get_dict.html','r',encoding='utf8') as f:
data = f.read() # 字符串
temp = Template(data)
# 将user_dict传递给get_dict.html页面 在该页面上使用变量名user_data调用
res = temp.render(user_data=user_dict)
return res
模板语法(用近似于python的语法在html文件上操作数据) 红色标记为固定语法,jinja2支持字典、列表等数据类型,在前端页面文件中的使用方式也和python中如出一辙。
{{user_data}}
{{user_data['username']}}
{{user_data.get('password')}}
{{user_data.hobby}}
{%for user_dict in data_list%}
<tr>
<td>{{user_dict.id}}</td>
<td>{{user_dict.name}}</td>
<td>{{user_dict.age}}</td>
</tr>
{%endfor%}
3.获取MySQL数据库数据展示到页面上
def get_db_func(request):
import pymysql
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='123',
db='db666',
charset='utf8',
autocommit=True
)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
sql = 'select * from userinfo'
affect_rows = cursor.execute(sql)
res1 = cursor.fetchall() # [{},{},{}]
with open(r'templates/get_db.html','r',encoding='utf8') as f:
data = f.read() # 字符串
temp = Template(data)
# 将user_dict传递给get_dict.html页面 在该页面上使用变量名user_data调用
res = temp.render(data_list=res1)
return res
5. 总结
1.纯手撸web框架
- socket代码需要我们自己写
- http格式的数据自己处理(只能拿到用户输入的路由)
2.基于wsgiref模块
- 封装了socket代码
- 处理了http数据格式
web服务网关接口
- 请求来的时候解析http格式的数据,封装成了大字典
- 响应走的时候把数据打包成符合http格式,在返回给浏览器
3.根据功能的不同拆分成不同的文件夹
urls.py 路由与视图函数对应关系:主要是匹配浏览器请求的视图页面并交由对应视图函数处理
views.py 视图函数:得到浏览器的页面请求及浏览器携带的请求数据,进行html页面提取、数据处理、模板渲染并返回
templates 模板文件夹:放有所有前端html页面文件夹
- 第一步添加路由与视图函数的对应关系
- 去views中书写功能代码
- 如果需要使用到html则去模板文件夹中操作
4.jinja2模板语法
{{···}}
{%···%}
5.简易版本web框架流程图
四、 Python三大主流web框架
1.django框架:
特点:大而全,自带的功能组件非常非常非常的多!类似于航空母舰
不足之处: 有时候过于笨重
2.flask框架:
特点:小而精,自身的功能组件非常非常非常的少!类似于游骑兵
但是第三方模块非常之多,如果把第三方模块全部叠加起来完全可以盖过django并且也越来越像django
不足之处: 比较依赖于第三方的开发者,有时候也会受限于第三方模块
ps:三行代码就可以启动一个flask后端服务
3.tornado框架:
特点:异步非阻塞,支持高并发
速度非常的快 快到可以开发游戏服务器
ps:Sanic、FastAPI...
A : socket部分 | B: 路由与视图匹配 | C: 模版语法 | |
---|---|---|---|
django | 别人的, wsgiref模块 | 自己写的 | 自己的(没有jinja2模块好用 但是也很方便) |
flask | 别人的,wsgiref模块封装之后werkzeug | 自己写的 | 别人的,jinja2模块 |
tornado | 自己写的 | 自己写的 | 自己写的 |
五、⚠ 想要正常运行django项目所需要知道的注意事项
- 计算机名称不能有中文,不然bug在哪儿你都不知道!
- 项目名和py文件名最好也不要使用中文,使用中文极容易出现各种莫名其妙的报错,使用拼 音也会显得非常的不专业
- 在pycharm窗口只能有一个django在运行状态,如果多开永远只有第一个在运行,且一个pycharm窗口就是一个项目,不要在窗口下面的子文件夹创建项目、不要和其他项目并列。
- 浏览器一定要设置为不保存缓存的,因为保存缓存可能会导致这次调试的前端效果仍旧是上次改动前的效果,浏览器还没读修改后的内容,用的是缓存数据
清除浏览器缓存
1.在浏览器页面右键点击检查
2.点击设置齿轮图标 Settings
3.找到 Network
4.Disble cache(while DevTools is open) 勾选上
- django版本问题
1.X版本,2.X版本,3.X版本,推荐使用1.X版本中的1.11.11版本,原因后面文章中会介绍,感兴趣可找度娘
django命令行或者pycharm下载
pip3 install django==1.11.11
# 测试是否安装完成
django-admin
六、 django创建项目
# 1.创建django项目
"""
你可以先切换到对应的D盘 然后再创建
"""
django-admin startproject 项目名
# 2.启动django项目
"""
一定要先切换到项目目录下
cd /项目名
"""
python3 manage.py runserver ip:port
ps:如果报错需要修改py文件源码
D:\Python38\lib\site-packages\django\contrib\admin\widgets.py
152行后面的逗号去掉即可!!!
'%s=%s' % (k, v) for k, v in params.items()
# 3.创建app
python manage.py startapp app名字
# 应用名应该做到见名知意
# pycharm操作
# 1 new project 选择左侧第二个django即可
# 2 启动
1.还是用命令行启动
2.点击绿色小箭头即可
# 3 创建应用
1.pycharm提供的终端直接输入完整命令
2.pycharm
>tools
>run manage.py task
# 4 修改端口号以及创建server
edit confi....
七、 app概念
django是一款专注于开发app(应用)的软件
我们创建一个django项目之后类似于创建了一所大学 而app就类似于大学里面的各个学院,每个学院都可以有自己独立的各项功能职责 django相当于是一个空壳子用来给各个学院提供资源!!!
强调:我们手动创建的app一定要去settings文件中注册才能生效
创建出来的的应用第一步先去配置文件中注册 其他的先不要给我干
ps:你在用pycharm创建项目的时候 pycharm可以帮你创建一个app并且自动注册
八、命令行与pycharm创建不同点
-
命令行创建django项目不会自动创建templates模板文件夹
-
命令行也不会自动在配置文件中配置模板文件夹路径
也就意味着在命令行创建django项目的时候不单单需要手动在项目目录创建templates模板文件夹,还需要去配置文件中手动添加templates模板文件夹路径,如下图
九、 django目录结构
mysite
├─ app01文件夹 # 应用(可以有多个)
├─ migrations文件夹 # 数据库迁移记录(类似于操作日志)
├─ admin.py # django后台管理
├─ apps.py # 注册app
├─ models.py # 数据库相关(模型层)
├─ tests.py # 测试文件
└─ views.py # 视图函数(视图层)
├─ mysite文件夹 # 项目同名文件夹
├─ settings.py # django暴露给用户可以配置的配置文件
├─ urls.py # 路由与视图函数(可以是函数也可是类)对应关系(路由层)
└─ wsgi.py # wsgiref模块(忽略)
├─ templates # 模板文件(存储html文件)(模板层)
├─ db.sqlite3 # django自带的小型数据库
└─ manage.py # django入口文件
十、 django小白必会三板斧
from django.shortcuts import HttpResponse, render, redirect
HttpResponse
内部传入一个字符串参数,返回给浏览器。
def index(request):
# 业务逻辑代码
# 视图函数必须要接收一个形参request
return HttpResponse("OK")
render
除request参数外还接受一个待渲染的模板文件和一个保存具体数据的字典参数。
将数据填充进模板文件,最后把结果返回给浏览器。(类似于我们上面用到的jinja2)
def index(request):
# 业务逻辑代码
# return render(request, "index.html", {"name": "alex", "hobby": ["烫头", "泡吧"]})
return render(request,'myfirst.html',locals())
redirect
接受一个URL参数,表示跳转到指定的URL。
def index(request):
# 业务逻辑代码
# return redirect('http://www.baidu.com')
return redirect("/home/")