编程的三部分: 使用数据 存储数据 展示数据
1. 在浏览器中输入URL并回车后都发生了什么?
- 浏览器连接网页服务器
- 浏览器向服务端发送了一条消息
- 网页服务器接收到消息
- 网页服务器向浏览器发送消息(html文件)
- 浏览器展示页面
2.使浏览器 和 网页服务器的说明
- 网页服务器相当于 socket 服务端
- 浏览器相当于 socket 客户端
3.本质:
- web框架的本质
- 本质上:通过http协议使用 socket 互发消息
- 不同的URL返回不同的页面
- 本质上:根据路径的不同返回不同的html文件内容
- 动态网页(html模板渲染)
- 本质上:字符串的替换
4. Python Web框架的分类:
- A. socket -> 服务器程序(专门用来处理 socket 收发消息)
- B. 根据不同的URL路径执行不同的函数 -> 应用程序(处理业务逻辑,控制服务器程序要返回的消息的具体内容)
- C. 动态页面(html模板渲染)
- Django -> 第三方的 A, 框架自带 B、C
- Flask -> 第三方的 A、C, 框架自带 B
- Tornado -> 框架自带 A、B、C
3. 使用 socket 模拟上网
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
while 1:
conn, addr = sk.accept()
data = conn.recv(2048)
print(data)
conn.send(b'HTTP/1.1 200 OK\r\n\r\n') # 因为要遵循HTTP协议,所以要在回复消息之前先发送状态行消息 -> 这里就相当于使用 socket 发送文件的例子中的报头(即:接收文件前要知道文件的名称/大小/类型,才可以开始接收文件,如果不懂的就看回 socket 中的报头)
conn.send(b'<h1>Hello</h1>')
conn.send(b'<a href="http://www.baidu.com">baidu</a>')
conn.close()
# socket 服务端所接收到消息 -> 其实服务端所接收到的就是网页请求头的内容
b'GET / HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nConnection: keep-alive\r\nPragma: no-cache\r\nCache-Control: no-cache\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 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'
# 优化返回的信息 -> \r\n 代表着回车
b'GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.119 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
'
_files/Image.png)
# 浏览器访问 127.0.0.1:8080 -> 因为浏览器相当于 socket 客户端,所以不用编写 socket 客户端
_files/Image [1].png)
4. 根据不同的路径返回不同的内容
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
while 1:
conn, addr = sk.accept()
data = conn.recv(2048) # 接收浏览器发送过来的消息
# 从浏览器发送过来的消息中获取路径
str_data = str(data, encoding='utf-8') # 把bytes类型转换为字符串类型
path = str_data.split()[1] # /index/
conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n') # 需要设置为 utf-8 不然浏览器会以系统默认的 GBK 编码对发送过来的内容进行编译,这样就会出现乱码
# 根据不同的路径返回不同的内容
if path == '/index/':
response = bytes('<h1>index 页面</h1>', encoding='utf-8')
elif path == '/login/':
response = bytes('<h1>login 页面</h1>', encoding='utf-8')
else:
response = bytes('<h1>404 not found!</h1>', encoding='utf-8')
conn.send(response) # 向浏览器发送具体响应体(内容)
conn.close()
_files/Image [2].png)
_files/Image [3].png)
_files/Image [4].png)
4. 根据不同的路径返回不同的内容 -- 函数版
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
# 将返回不同内容的部分封装成函数
def index(path):
s = '<h1>这是{}页面!</h1>'.format(path)
return bytes(s, encoding='utf-8')
def login(path):
s = '<h1>这是{}页面!</h1>'.format(path)
return bytes(s, encoding='utf-8')
while 1:
conn, addr = sk.accept()
data = conn.recv(2048) # 接收浏览器发送过来的消息
# 从浏览器发送过来的消息中获取路径
str_data = str(data, encoding='utf-8') # 把bytes类型转换为字符串类型
path = str_data.split()[1] # /index/
conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n') # 需要设置为 utf-8 不然浏览器会以系统默认的 GBK 编码对发送过来的内容进行编译,这样就会出现乱码
# 根据不同的路径返回不同的内容
if path == '/index/':
response = index(path)
elif path == '/login/':
response = login(path)
else:
response = bytes('<h1>404 not found!</h1>', encoding='utf-8')
conn.send(response) # 向浏览器发送具体响应体(内容)
conn.close()
5. 根据不同的路径返回不同的内容 -- 函数版进阶版
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
# 将返回不同内容的部分封装成函数
def index(path):
s = '<h1>这是{}页面!</h1>'.format(path)
return bytes(s, encoding='utf-8')
def login(path):
s = '<h1>这是{}页面!</h1>'.format(path)
return bytes(s, encoding='utf-8')
# 使用反射解决每次都要更新判断的问题
lis = [
('/index/', index),
('/login/', login)
]
while 1:
conn, addr = sk.accept()
data = conn.recv(2048) # 接收浏览器发送过来的消息
# 从浏览器发送过来的消息中获取路径
str_data = str(data, encoding='utf-8') # 把bytes类型转换为字符串类型
path = str_data.split()[1] # /index/
conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n') # 需要设置为 utf-8 不然浏览器会以系统默认的 GBK 编码对发送过来的内容进行编译,这样就会出现乱码
# 根据不同的路径返回不同的内容
# 使用反射解决每次都要更新判断的问题
func = None
for i in lis:
if i[0] == path:
func = i[1]
break
if func:
response = func(path)
else:
response = bytes('<h1>404 not found!</h1>', encoding='utf-8')
conn.send(response) # 向浏览器发送具体响应体(内容)
conn.close()
6. 根据不同的路径返回具体的html文件
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
# 将返回不同内容的部分封装成函数
def index(path):
with open('index.html', encoding='utf-8') as f:
s = f.read() # 读取 index.html 的内容
return bytes(s, encoding='utf-8') # 返回 bytes类型的 html 内容
def login(path):
with open('login.html', encoding='utf-8') as f:
s = f.read() # 读取 login.html 的内容
return bytes(s, encoding='utf-8') # 返回 bytes类型的 html 内容
# 使用反射解决每次都要更新判断的问题
lis = [
('/index/', index),
('/login/', login)
]
while 1:
conn, addr = sk.accept()
data = conn.recv(2048) # 接收浏览器发送过来的消息
# 从浏览器发送过来的消息中获取路径
str_data = str(data, encoding='utf-8') # 把bytes类型转换为字符串类型
path = str_data.split()[1] # /index/
conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n') # 需要设置为 utf-8 不然浏览器会以系统默认的 GBK 编码对发送过来的内容进行编译,这样就会出现乱码
# 根据不同的路径返回不同的内容
# 使用反射解决每次都要更新判断的问题
func = None
for i in lis:
if i[0] == path:
func = i[1]
break
if func:
response = func(path)
else:
response = bytes('<h1>404 not found!</h1>', encoding='utf-8')
conn.send(response) # 向浏览器发送具体响应体(内容)
conn.close()
# index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>index</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link rel="stylesheet" href="">
<script type="text/javascript" src=''></script>
</head>
<body>
<h1>这是index页面</h1>
<script type="text/javascript"></script>
</body>
</html>
# login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>login</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link rel="stylesheet" href="">
<script type="text/javascript" src=''></script>
</head>
<body>
<h1>这是login页面</h1>
<script type="text/javascript"></script>
</body>
</html>
7. 动态网页(html模板渲染)
import socket
import time
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
# 将返回不同内容的部分封装成函数
def index(path):
with open('index.html', encoding='utf-8') as f:
s = f.read() # 读取 index.html 的内容
now = str(time.time())
s = s.replace('@@xx@@', now) # 在网页中定义好特殊符号,用动态的数据去替换提前定义好的特殊符号
return bytes(s, encoding='utf-8') # 返回 bytes类型的 html 内容
def login(path):
with open('login.html', encoding='utf-8') as f:
s = f.read() # 读取 login.html 的内容
return bytes(s, encoding='utf-8') # 返回 bytes类型的 html 内容
# 使用反射解决每次都要更新判断的问题
lis = [
('/index/', index),
('/login/', login)
]
while 1:
conn, addr = sk.accept()
data = conn.recv(2048) # 接收浏览器发送过来的消息
# 从浏览器发送过来的消息中获取路径
str_data = str(data, encoding='utf-8') # 把bytes类型转换为字符串类型
path = str_data.split()[1] # /index/
conn.send(b'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n') # 需要设置为 utf-8 不然浏览器会以系统默认的 GBK 编码对发送过来的内容进行编译,这样就会出现乱码
# 根据不同的路径返回不同的内容
# 使用反射解决每次都要更新判断的问题
func = None
for i in lis:
if i[0] == path:
func = i[1]
break
if func:
response = func(path)
else:
response = bytes('<h1>404 not found!</h1>', encoding='utf-8')
conn.send(response) # 向浏览器发送具体响应体(内容)
conn.close()
# index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>index</title>
<meta name="description" content="">
<meta name="keywords" content="">
<link rel="stylesheet" href="">
<script type="text/javascript" src=''></script>
</head>
<body>
<h1>时间戳:@@xx@@</h1>
<script type="text/javascript"></script>
</body>
</html>
_files/Image [5].png)
8. 服务器程序 和 应用程序
对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序
- 服务器程序: 主要负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理(通俗理解: 专门用来处理 socket 收发消息),例如:wsgiref(Python的内置模块)
- 应用程序: 主要负责具体的逻辑处理(通俗理解: 处理业务逻辑,控制服务器程序要返回的消息的具体内容)。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务
由于出现了众多的Web框架,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。
这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。
WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。
常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器
WSGI 没有并发的功能,只能排队执行
_files/20190412165112.png)
_files/未标题-1.png)
9. wsgiref 服务器程序模块
- wsgiref模块是一个遵循 wsgi 协议处理请求的服务器程序模块
- 利用wsgiref模块来替换我们自己写的web框架的 socket 部分的服务区程序
import time
from wsgiref.simple_server import make_server
# 将返回不同内容的部分封装成函数
def index(path):
with open('index.html', encoding='utf-8') as f:
s = f.read() # 读取 index.html 的内容
now = str(time.time())
s = s.replace('@@xx@@', now) # 在网页中定义好特殊符号,用动态的数据去替换提前定义好的特殊符号
return bytes(s, encoding='utf-8') # 返回 bytes类型的 html 内容
def login(path):
with open('login.html', encoding='utf-8') as f:
s = f.read() # 读取 login.html 的内容
return bytes(s, encoding='utf-8') # 返回 bytes类型的 html 内容
# 使用反射解决每次都要更新判断的问题
lis = [
('/index/', index),
('/login/', login)
]
# 使用wsgiref模块帮我们收发浏览器的消息
def run_server(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息
path = environ['PATH_INFO'] # 取到用户输入的url
# 根据不同的路径返回不同的内容
# 使用反射解决每次都要更新判断的问题
func = None
for i in lis:
if i[0] == path:
func = i[1]
break
if func:
response = func(path)
else:
response = bytes('<h1>404 not found!</h1>', encoding='utf-8')
return [response]
if __name__ == '__main__':
httpd = make_server('127.0.0.1', 8080, run_server)
print("我在8080等你哦...")
httpd.serve_forever()
10. jinja2 模块
- jinja2 模块一般用于html模板渲染,和上面所写的动态网页例子是一样的
# jinja2 的安装
pip3 install jinja2 -i https://pypi.douban.com/simple # 使用豆瓣的镜像
from wsgiref.simple_server import make_server
import jinja2
# 将返回不同内容的部分封装成函数
def index(path):
with open('index.html', encoding='utf-8') as f:
data = f.read() # 读取 index.html 的内容
template = jinja2.Template(data) # 生成模板文件
s = template.render({"name": "Alex", "hobby_list": ["烫头", "泡吧"]}) # 把数据填充到模板里
return bytes(s, encoding='utf-8') # 返回 bytes类型的 html 内容
def login(path):
with open('login.html', encoding='utf-8') as f:
s = f.read() # 读取 login.html 的内容
return bytes(s, encoding='utf-8') # 返回 bytes类型的 html 内容
# 使用反射解决每次都要更新判断的问题
lis = [
('/index/', index),
('/login/', login)
]
def run_server(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) # 设置HTTP响应的状态码和头信息
path = environ['PATH_INFO'] # 取到用户输入的url
# 根据不同的路径返回不同的内容
# 使用反射解决每次都要更新判断的问题
func = None
for i in lis:
if i[0] == path:
func = i[1]
break
if func:
response = func(path)
else:
response = bytes('<h1>404 not found!</h1>', encoding='utf-8')
return [response]
if __name__ == '__main__':
httpd = make_server('127.0.0.1', 8080, run_server)
print("我在8080等你哦...")
httpd.serve_forever()
# index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="x-ua-compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>index</title>
</head>
<body>
<h1>姓名:{{name}}</h1>
<h1>爱好:</h1>
<ul>
{% for hobby in hobby_list %}
<li>{{hobby}}</li>
{% endfor %}
</ul>
</body>
</html>
_files/Image [6].png)