01 web 应用
一、软件开发架构
C/S 客户端 服务端
B/S 浏览器和服务端
注意:B/S 的本质也是C/S架构
二、web应用的组成
接下来学习的目的是为了开发一个web应用程序,而web应用程序是基于B/S架构的,其中B就是上面说的浏览器,负责向S端发送请求信息,而S端会根据接收到的请求信息返回响应的数据给浏览器
既然web应用程序是通过B/S架构来开发的,那么我们现在就开始来开发一个简单的web应用
三、开发一个web应用
我们无需开发浏览器(本质即套接字客户端),只需要开发S端即可,S端的本质就是用套接字实现的,如下
# server 端
import socket
server = socket.socket() # 实例化一个server对象
server.bind(('127.0.0.1', 8080))# server对象绑定端口号
'''
特别提示:
1. 127.0.0.1 是本机的回环地址,只有本机可以访问
2.端口是唯一标识计算机的某一个应用程序
3. 端口号在用的时候尽量用8000以上的
4. 1-1024 是操作系统自己用的
5.还有一些特殊的是代表其他的应用软件的
'''
# TCP半开连接是指发送了TCP连接请求,等待对方应答的状态,此时连接并没有完全建立起来,双方还无法进行通信交互的状态,此时就称为半连接。由于一个完整的TCP连接需要经过三次握手才能完成,这里把三次握手之前的连接都称之为半连接。
server.listen(5)
while True:
# 客户端/浏览器 连接服务端的时候,会产生客户端/浏览器 的地址和连接对象
conn,addr = server.accept()
# 由于是基于B/S 架构,所以接收浏览器的请求信息
data = conn.recv(1024)
# 接收了请求以后,给浏览器发送响应信息
conn.send(b'hello world!')
conn.close()
## 这个时候当浏览器访问服务器的地址时就会出现服务器和浏览器响应信息
127.0.0.1:8080 (尽量用谷歌浏览器)
注意:以上的S端已经可以正常接收浏览器发来的请求信息了,但是浏览器在接收到S端回复的响应消息b'hello world’时却不能正常的解析,此处是因为浏览器和S端之间收发消息默认使用的应用层协议是Http协议,浏览器默认以http协议规定的格式发消息,而s端也必须按照http协议的格式回消息才行,所以接下来就简单的介绍一下HTTP协议
四、HTTP协议简单介绍
HTTP协议:超文本传输协议
四大特性:
1. 基于TCP/IP之上作用于应用层
2. 基于请求响应
3. 无状态 cookie、session、token
4. 无连接: 比如基于B/S架构,浏览器请求访问服务端时,服务端智慧给他响应信息,并不会和当前访问的浏览器一直建立连接,(一次请求、、、一次响应)
数据格式:
# 请求格式
请求首行(请求方式,协议版本)
请求头(一大堆的k:V键值对)
空行(\r\n------直接记\n即可)
请求体(真正的数据,发送post请求的时候才有,get请求没有请求体)
# 响应格式
响应首行
响应头
空行(\n)
响应体
响应状态码:
用特定的数字表示一些意思
1XX:服务端已经成功接收到了你的数据 正在处理 你可以继续提交其他数据
2XX:服务端成功响应(200请求成功)
3XX:重定向
4XX:请求错误(404 请求资源不存在 403 拒绝访问)
5XX:服务器内部错误(500 )
请求方式:
get请求:
问服务端要数据
post请求:
向服务端提交数据
url:统一资源定位符
HTTP协议详解连接地址:01 HTTP 协议
S端修正版本
处理HTTP协议的请求信息,并按照HTTP协议的格式响应回复信息
# server端修正版
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
'''
特别提示:
1. 127.0.0.1 是本机的回环地址,只有本机可以访问
2.端口是唯一标识计算机的某一个应用程序
3. 端口号在用的时候尽量用8000以上的
4. 1-1024 是操作系统自己用的
5.还有一些特殊的是代表其他的应用软件的
'''
server.listen(5)
while True:
conn,addr = server.accept()
data = conn.recv(1024)
conn.send(b'http://1.1 200 ok\r\n\r\n')# 此处是服务端给浏览器发送的协议
conn.send(b'hello world!')
conn.close()
## 这个时候当浏览器访问服务器的地址时就会出现服务器给浏览器正确响应信息
127.0.0.1:8080 (尽量用谷歌浏览器)
## 此时重新启动S端就可以看到解析正确的hello world了
S端将html标签返回
# server端返回html标签
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
'''
特别提示:
1. 127.0.0.1 是本机的回环地址,只有本机可以访问
2.端口是唯一标识计算机的某一个应用程序
3. 端口号在用的时候尽量用8000以上的
4. 1-1024 是操作系统自己用的
5.还有一些特殊的是代表其他的应用软件的
'''
server.listen(5)
while True:
conn,addr = server.accept()
data = conn.recv(1024)
conn.send(b'http://1.1 200 ok\r\n\r\n')
# 返回html标签
conn.send(b'<p>hello world!</p>')
conn.close()
浏览器携带参数访问S端时的处理
# 浏览器访问 127.0.0.1:8080/index
import socket
server = socket.socket()
server.bind(("127.0.0.1", 8080))
server.listen(5)
while True:
conn, addr = server.accept()
# 接收浏览器的请求
data = conn.recv(1024)
current_path = data.decode('utf8')
# print(current_path)
# 在这里我们会发现用户访问服务端的一些信息
'''
请求头\r\n
请求首行\r\n
\r\n
请求体
'''
# 通过接收到的信息进行字符串切割的方法去到用户访问服务端时携带的参数
path_info = current_path.split(' ')[1]
print(path_info)
# 规定和浏览器使用的传输协议,我们都要遵守
# tcp连接时传输时通过二进制的形式
conn.send(b'http://1.1 200 ok \r\n\r\n')
# 判断用户携带的参数是否有我服务端的权限,是否可以正确的与服务端之间进行通信
if path_info=="/index":
conn.send(b'hello index')
conn.close()
elif path_info=="/login":
conn.send(b'<p>hello world!</p>')
conn.close()
else:
conn.send(b'404')
conn.close()
S端返回html页面给浏览器
# index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="python folder/bootstrap/js/bootstrap.min.js"></script>
<link href="python folder/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1 ">
<title>index</title>
</head>
<body>
<p>hello 我是index</p>
</body>
</html>
# login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="python folder/bootstrap/js/bootstrap.min.js"></script>
<link href="python folder/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1 ">
<title>lgoin</title>
</head>
<body>
<p>hello 我是login</p>
</body>
</html>
# server 端返回html文件渲染浏览器页面
import socket
server = socket.socket()
server.bind(("127.0.0.1", 8080))
server.listen(5)
while True:
conn, addr = server.accept()
# 接收浏览器的请求
data = conn.recv(1024)
current_path = data.decode('utf8')
# print(current_path)
# 在这里我们会发现用户访问服务端的一些信息
'''
请求头\r\n
请求首行\r\n
\r\n
请求体
'''
# 通过接收到的信息进行字符串切割的方法去到用户访问服务端时携带的参数
path_info = current_path.split(' ')[1]
print(path_info)
conn.send(b'http://1.1 200 ok \r\n\r\n')
# 判断用户携带的参数是否有我服务端的权限,是否可以正确的与服务端之间进行通信
if path_info=="/index": # 如果浏览器携带的参数是可以访问服务器的就打开html文件,以rb读取内容,响应给浏览器,此时浏览器矿口会看到相应的html页面
with open('index.html','rb') as fr:
data = fr.read()
conn.send(data)
conn.close()
elif path_info=="/login":
with open('login.html','rb') as fr:
data = fr.read()
conn.send(data)
conn.close()
else:
conn.send(b'404')
conn.close()
五、减少代码冗余的server端
# index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="python folder/bootstrap/js/bootstrap.min.js"></script>
<link href="python folder/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1 ">
<title>index</title>
</head>
<body>
<p>hello my index</p>
</body>
</html>
# login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
<script src="python folder/bootstrap/js/bootstrap.min.js"></script>
<link href="python folder/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1 ">
<title>lgoin</title>
</head>
<body>
<p>hello my login</p>
</body>
</html>
# server减少代码冗余
import socket
server = socket.socket()
server.bind(("127.0.0.1", 8080))
server.listen(5) # 相当于半连接池,只能一次接受5个客户端的访问,这个时候服务端处于监听的状态,等待客户端的连接
# 通过进行判断,执行任务的接口函数
def index():
with open('index.html','r') as fr:
data = fr.read()
return data
def login():
with open('login.html','r') as fr:
data = fr.read()
return data
def earro():
return '404'
# 这是为了减少代码的冗余,把用户携带的参数放在一个固定的列表里
# 通过进行判断,任务分发的方式去调用这些接口
urls_list = [
('/index',index),
('/login',login)
]
while True:
conn, addr = server.accept()
#接收浏览器的请求
data = conn.recv(1024)
current_path = data.decode('utf8')
conn.send(b'http://1.1 200 ok\r\n\r\n')
# print(current_path)
# 在这里我们会发现用户访问服务端的一些信息
'''
请求头\r\n
请求首行\r\n
\r\n
请求体
'''
# 通过接收到的信息进行字符串切割的方法去到用户访问服务端时携带的参数
path_info = current_path.split(' ')[1]
# print(path_info)
func = None # 定义一个获取用户携带参数的
for url in urls_list:
if path_info == url[0]:
func = url[1]
break #只要一匹配到,我退出循环,减少消耗的资源
# 这里是判断func用户请求我的时候,有没有参数,
# 若有则取执行响应的功能
if func:
res = func()
else:
res = earro()
# 执行对应的接口函数过后,我们会接收到函数返回给我们的字符串值
# 发给用户的时候,我们要转换为二进制的形式去发送
conn.send(res.encode('utf8'))
conn.close()
六、web框架的由来
综上案例我们可以发现一个规律,在开发S端时,server的功能时复杂且固定的(处理socket消息的收发和http协议的处理),而每个视图函数处理的功能的业务逻辑确实各不相同的(对于不同的软件就应该由不同的业务逻辑),重复开发复杂且规定的server是毫无意义的,然而我们知道python有一个外号叫做掉包侠
说到这里,大家应该就想到了有没有第三方模块来帮我们解决这个事情呢?
哈哈哈,当然有,有一个wsgrief模块帮我们写好了server的功能,这样我们就不需要自己去写server了,也不需要自己去对用户携带的参数进行切割了
基于wsgrief模块的server
这个模块实现了上面的所有手动去写原生怠慢的过程
根据每个部分功能不同,将他们分成不同的py文件
urls.py:只用来存放路由与视图函数对应的关系的
View.py:用来存放视图函数(函数、类)
拆分完成后,如果想要给一个应用添加应用,仅仅只需要在urls.py和View.py两个文件里改就可以了
并且使用wsgrief模块的本质是将不用我们自己去写server部分,而且我们是通过任务分发的方式去实现每个浏览器请求信息的,以下就是一个完整的例子
# urls.py文件
from views import *
# 此处是路由和功能函数的关系
# 通过进行判断,任务分发的方式去调用这些接口
urls_list = [
('/index', index),
('/login', login),
]
# views.py
# 这里是用户视图的
# 通过进行判断,执行任务的接口函数
def index(env):
with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\index.html', 'r') as fr:
data = fr.read()
return data
def login(env):
with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\login.html', 'r') as fr:
data = fr.read()
return data
def error(env):
return '404'
# server 端
from wsgiref.simple_server import make_server
from url_list import *
# 有客户端连接服务端的时候,服务端会将客户端进行的操作转给run函数来处理
def run(env, response):
'''
:param env: 客户端请求的相关数据
:param response: 服务端响应的所有数据
:return:
'''
response('200 ok', []) # 这里涉及到浏览器访问后台的一些状态码
current_path = env.get('PATH_INFO') # wsgiref帮我们返回了用户所有的信息,以字典的形式返回的
# 我们通过查找发现,用户请求服务端的时候,携带的参数是在PATH_INFo:values
# 先定义一个变量名:用来从存储后续匹配到的用户请求我携带的参数
func = None
for url in urls_list:
if current_path == url[0]:
func = url[1] # 一旦匹配成功 就将匹配到的函数名赋值给func变量
break # 主动结束匹配
# 判断func是否有值
if func:
res = func(env) # 这里把env传给每一个功能函数,方便日后可能会用到
else:
res = error(env)
return [res.encode('utf-8')]
if __name__ == '__main__':
# 实时监听该地址 只要有客户端来连接 统一交给run函数去处理
server = make_server('127.0.0.1', 8080, run)
server.serve_forever() # 启动服务端
七、server传递动态的页面给浏览器
7.1 动静态网页的区别
静态网页:数据是自己手动写死的,万年不变的,每次去访问都是一个数据
动态页面:数据是实时获取的,每次访问都不一样
7.2 通过后端获取当前时间展示到前端
# 这里是用户视图的
# 通过进行判断,执行任务的接口函数
def index(env):
with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\index.html', 'r') as fr:
data = fr.read()
return data
def login(env):
with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\login.html', 'r') as fr:
data = fr.read()
return data
def error(env):
return '404'
# 用于显示当前时间的
import datetime
def get_time(env):
current_time = datetime.datetime.now()
current_time = current_time.strftime('%Y-%m-%d %X')
with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\get_time.html','r') as fr:
data = fr.read()
data = data.replace('time',current_time)
# 我们也可以通过特定的语法来帮我们实现,一般都是用规定的字符
# data = data.replace('$$time$$',current_time)
return data
# urls.py
from views import *
# 此出是路由和功能函数的关系
# 通过进行判断,任务分发的方式去调用这些接口
urls_list = [
('/index', index),
('/login', login),
('/get_time', get_time),
('/get_user', get_user),
]
# server 端
from wsgiref.simple_server import make_server
from url_list import *
# 有客户端连接服务端的时候,服务端会将客户端进行的操作转给run函数来处理
def run(env, response):
'''
:param env: 客户端请求的相关数据
:param response: 服务端响应的所有数据
:return:
'''
response('200 ok', []) # 这里涉及到浏览器访问后台的一些状态码
current_path = env.get('PATH_INFO') # wsgiref帮我们返回了用户所有的信息,以字典的形式返回的
# 我们通过查找发现,用户请求服务端的时候,携带的参数是在PATH_INFo:values
# 先定义一个变量名:用来从存储后续匹配到的用户请求我携带的参数
func = None
for url in urls_list:
if current_path == url[0]:
func = url[1] # 一旦匹配成功 就将匹配到的函数名赋值给func变量
break # 主动结束匹配
# 判断func是否有值
if func:
res = func(env) # 这里把env传给每一个功能函数,方便日后可能会用到
else:
res = error(env)
return [res.encode('utf-8')]
if __name__ == '__main__':
# 实时监听该地址 只要有客户端来连接 统一交给run函数去处理
server = make_server('127.0.0.1', 8080, run)
server.serve_forever() # 启动服务端
7.2 后端通过获取数据库的信息,展示到前端页面
如何将后端获取的数据传递给Html页面呢?
此处提出一个概念:模板的渲染
模板的渲染:后端获取的数据 传递给前端页面就叫做模板的渲染
此处在提醒一句python 是掉包侠 哈哈哈哈
现在又有一个包可以帮我们实现这个任务了:jinja2
安装方式:pip3 install jinja2
模板语法(极其贴近python后端语法)
# 这里是下面的一个例子可以将字典相应给前端页面,然后通过点取值的方式
<p>{{ user }}</p>
<p>{{ user.name }}</p>
<p>{{ user['pwd'] }}</p>
<p>{{ user.get('hobby') }}</p>
#通过获取数据库的数据,然后渲染到前端页面
{% for user_dict in user_list %}
{{ user_dict.id }}
{{ user_dict.name }}
{{ user_dict.pwd }}
{% endfor %}
# viess.py
# Author:Cecilia
# 这里是用户视图的
# 通过进行判断,执行任务的接口函数
def index(env):
with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\index.html', 'r') as fr:
data = fr.read()
return data
def login(env):
with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\login.html', 'r') as fr:
data = fr.read()
return data
def error(env):
return '404'
# 用于显示当前时间的
import datetime
def get_time(env):
current_time = datetime.datetime.now()
current_time = current_time.strftime('%Y-%m-%d %X')
with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\get_time.html','r') as fr:
data = fr.read()
data = data.replace('time',current_time)
# 我们也可以通过特定的语法来帮我们实现,一般都是用规定的字符
# data = data.replace('$$time$$',current_time)
return data
# 现在我们有一个需求,就是我后台有一个字典,我需要从后台去渲染前端的页面
# 并且让用户在浏览器通过输入字典的key九可以取值
# 这个时候我们需要导入一个jinja2的模块来帮助我们实现,这是封装好的方法,我们直接用就可以了
from jinja2 import Template
def get_user(env):
d = {'name':'cecilia','age':18,'sex':'female','hobbies':['run','sing','play']}
with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\get_user.html','r') as fr:
data = fr.read()
# 我们拿到get_userhtml文件的文本格式以后
# 通过jinja2库的Template模块来帮我们完成
temp = Template(data)
res = temp.render(user=d)# 将字典d传递给前端页面 页面上通过变量名user就能够获取到该字典
return res
#我们需要从数据库里面取值
import pymysql
def get_db(env):
conn = pymysql.connect(
host = '127.0.0.1',
port = 3306,
database = 'mydb',
user = 'root',
password = '123',
charset = 'utf8',
autocommit = True
)
# 获取游标对象
curser = conn.cursor(pymysql.cursors.DictCursor)
sql = 'select * from userinfo'
curser.execute(sql)
res = curser.fetchall()
with open(r'E:\python folder\GjanGO框架\01 server_bs_web 基础\04 基于wsgire 的server\04 web推导框架\templates\get_db.html','r',encoding='utf8') as fr:
data = fr.read()
temp = Template(data)
ret = temp.render(user_list = res) # 将从数据库里取出来的值,放进我们get_db html页面种
return ret
# urls.py
from views import *
# 通过进行判断,任务分发的方式去调用这些接口
urls_list = [
('/index', index),
('/login', login),
('/get_time', get_time),
('/get_user', get_user),
('/get_db', get_db),
]
# server 端
from wsgiref.simple_server import make_server
from url_list import *
# 有客户端连接服务端的时候,服务端会将客户端进行的操作转给run函数来处理
def run(env, response):
'''
:param env: 客户端请求的相关数据
:param response: 服务端响应的所有数据
:return:
'''
response('200 ok', []) # 这里涉及到浏览器访问后台的一些状态码
current_path = env.get('PATH_INFO') # wsgiref帮我们返回了用户所有的信息,以字典的形式返回的
# 我们通过查找发现,用户请求服务端的时候,携带的参数是在PATH_INFo:values
# 先定义一个变量名:用来从存储后续匹配到的用户请求我携带的参数
func = None
for url in urls_list:
if current_path == url[0]:
func = url[1] # 一旦匹配成功 就将匹配到的函数名赋值给func变量
break # 主动结束匹配
# 判断func是否有值
if func:
res = func(env) # 这里把env传给每一个功能函数,方便日后可能会用到
else:
res = error(env)
return [res.encode('utf-8')]
if __name__ == '__main__':
# 实时监听该地址 只要有客户端来连接 统一交给run函数去处理
server = make_server('127.0.0.1', 8080, run)
server.serve_forever() # 启动服务端
注意:在这里我们发现,不管我们添加多少个功能,我们的server页面都没有变过,所以这就是我们的web框架
注意2:在我们的功能逐渐变多时,我们此时可能要写很多个html页面,这样会影响到我们调式代码,所以在这里,我们把这些html页面单独拿出来放在一个文件夹里(templates文件夹)