Web应用组成
Web应用程序是基于B/S架构的,其中B指的是浏览器,负责向S端发送请求信息,而S端会根据接收到的请求信息返回相应的数据给浏览器,需要强调的一点是:S端由server和application两大部分构成
开发Web应用
我们无需开发浏览器(本质即套接字客户端),只需要开发S端即可,S端的本质就是用套接字实现的
- server端
import socket
def make_server(ip, port, app): # 代表server
sock = socket.socket()
sock.bind((ip, port))
sock.listen(5)
print('Starting development server at http://%s:%s/' %(ip,port))
while True:
conn, addr = sock.accept()
# 1、接收浏览器发来的请求信息
recv_data = conn.recv(1024)
# print(recv_data.decode('utf-8'))
# 2、将请求信息直接转交给application
res = application(recv_data)
# 3、向浏览器返回消息(此处并没有按照http协议返回)
conn.send(res)
conn.close()
def application(environ): # 代表application
# 处理业务逻辑
return b'hello world'
if __name__ == '__main__':
make_server('127.0.0.1', 8008, app)
# 在客户端浏览器输入:http://127.0.0.1:8008 会报错(注意:请使用谷歌浏览器)
目前S端已经可以正常接收浏览器发来的请求消息了,但是浏览器在接收到S端回复的响应消息b'hello world'时却无法正常解析 ,因为浏览器与S端之间收发消息默认使用的应用层协议是HTTP,浏览器默认会按照HTTP协议规定的格式发消息,而S端也必须按照HTTP协议的格式回消息才行
HTTP协议详解链接地址 https://blog.csdn.net/Waller_/article/details/104155456
S端修订版本:处理HTTP协议的请求消息,并按照HTTP协议的格式回复消息
import socket
def make_server(ip, port, app): # 代表server
sock = socket.socket()
sock.bind((ip, port))
sock.listen(5)
print('Starting development server at http://%s:%s/' % (ip, port))
while True:
conn, addr = sock.accept()
# 1、接收并处理浏览器发来的请求信息
# 1.1 接收浏览器发来的http协议的消息
recv_data = conn.recv(1024)
# 1.2 对http协议的消息加以处理,简单示范如下
ll = recv_data.decode('utf-8').split('\r\n')
head_ll = ll[0].split(' ') # 将请求首行切分
environ = {}
environ['PATH_INFO'] = head_ll[1] # 拿到PATH
print(head_ll[1]) # /
environ['method'] = head_ll[0] # 拿到请求方法
print(head_ll[0]) # GET
# 2:将请求信息处理后的结果environ交给application,这样application便无需再关注请求信息的处理,可以更加专注于业务逻辑的处理
res = app(environ)
# 3:按照http协议向浏览器返回消息
# 3.1 返回响应首行
conn.send(b'HTTP/1.1 200 OK\r\n')
# 3.2 返回响应头(可以省略)
conn.send(b'Content-Type: text/html\r\n\r\n')
# 3.3 返回响应体
conn.send(res)
conn.close()
def app(environ): # 代表application
# 处理业务逻辑
return b'hello world'
if __name__ == '__main__':
make_server('127.0.0.1', 8008, app)
重启S端后,再在客户端浏览器输入:http://127.0.0.1:8008 便可以看到正常结果hello world了。
我们不仅可以回复hello world这样的普通字符,还可以夹杂html标签,浏览器在接收到消息后会对解析出的html标签加以渲染
import socket
def make_server(ip, port, app):
sock = socket.socket()
sock.bind((ip, port))
sock.listen(5)
print('Starting development server at http://%s:%s/' %(ip,port))
while True:
conn, addr = sock.accept()
recv_data = conn.recv(1024)
ll=recv_data.decode('utf-8').split('\r\n')
head_ll=ll[0].split(' ')
environ={}
environ['PATH_INFO']=head_ll[1]
environ['method']=head_ll[0]
res = app(environ)
conn.send(b'HTTP/1.1 200 OK\r\n')
conn.send(b'Content-Type: text/html\r\n\r\n')
conn.send(res)
conn.close()
def app(environ):
# 返回html标签
return b'<h1>hello web</h1><img src="https://www.baidu.com/img/bd_logo1.png"></img>'
if __name__ == '__main__':
make_server('127.0.0.1', 8008, app)
注意: HTTP响应的Header中有一个 `Content-Type`表明响应的内容格式。如 `text/html`表示HTML网页。
更进一步我们还可以返回一个文件,例如timer.html,内容如下
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h2>{{ time }}</h2>
</body>
</html>
S端程序如下
import socket
def make_server(ip, port, app): # 代表server
sock = socket.socket()
sock.bind((ip, port))
sock.listen(5)
print('Starting development server at http://%s:%s/' %(ip,port))
while True:
conn, addr = sock.accept()
recv_data = conn.recv(1024)
ll=recv_data.decode('utf-8').split('\r\n')
head_ll=ll[0].split(' ')
environ={}
environ['PATH_INFO']=head_ll[1]
environ['method']=head_ll[0]
res = app(environ)
conn.send(b'HTTP/1.1 200 OK\r\n')
conn.send(b'Content-Type: text/html\r\n\r\n')
conn.send(res)
conn.close()
def app(environ):
# 处理业务逻辑:打开文件,读取文件内容并返回
with open('timer.html', 'r', encoding='utf-8') as f:
data = f.read()
return data.encode('utf-8')
if __name__ == '__main__':
make_server('127.0.0.1', 8008, app)
上述S端为浏览器返回的都是静态页面(内容都固定的),我们还可以返回动态页面(内容是变化的)
import socket
def make_server(ip, port, app): # 代表server
sock = socket.socket()
sock.bind((ip, port))
sock.listen(5)
print('Starting development server at http://%s:%s/' %(ip,port))
while True:
conn, addr = sock.accept()
recv_data = conn.recv(1024)
ll=recv_data.decode('utf-8').split('\r\n')
head_ll=ll[0].split(' ')
environ={}
environ['PATH_INFO']=head_ll[1]
environ['method']=head_ll[0]
res = app(environ)
conn.send(b'HTTP/1.1 200 OK\r\n')
conn.send(b'Content-Type: text/html\r\n\r\n')
conn.send(res)
conn.close()
def app(environ):
# 处理业务逻辑
with open('timer.html', 'r', encoding='utf-8') as f:
data = f.read()
import time
now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
data = data.replace('{{ time }}', now) # 字符串替换
return data.encode('utf-8')
if __name__ == '__main__':
make_server('127.0.0.1', 8008, app) # 在浏览器输入http://127.0.0.1:8008,每次刷新都会看到不同的时间
Web框架的由来
综上案例我们可以发现一个规律,在开发S端时,server的功能是复杂且固定的(处理socket消息的收发和http协议的处理),而app中的业务逻辑却各不相同(不同的软件就应该有不同的业务逻辑),重复开发复杂且固定的server是毫无意义的,有一个wsgiref模块帮我们写好了server的功能,这样我们便只需要专注于app功能的编写即可
wsgiref
wsgiref实现了server,即make_server
from wsgiref.simple_server import make_server
# wsgiref 将socket封装好了
def run(env, response):
'''
env 是请求相关的数据
response 是响应相关的数据
'''
print(env) # 收到的是一个字典,里面的 键 PATN_INFO 对应的值就是客户端url的请求后缀
response('200 OK', [])
return [b'hello Django']
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, run)
# 实时监测127.0.0.1:8080地址,一旦有客户端连接,会自动加括号调用run方法
server.serve_forever() # 启动服务端
根据不同的路径返回不同的内容
from wsgiref.simple_server import make_server
# wsgiref 将socket封装好了
def index(env):
return 'index'
def login(env):
return 'login'
def errors(env):
return '404 error'
urls = [
('/index', index),
('/login', login),
]
def run(env, response):
response('200 OK', [])
current_path = env.get('PATH_INFO')# 拿到的是URL后缀
# 定义一个存储函数的标志位
func = None
for url in urls:
# 判断当前请求的url是否在元组内
if url[0] == current_path:
# 若在,赋值给func
func = url[1]
# 一旦匹配上就退出循环,节省资源
break
# 判断func是否有值
if func:
# 调用
res = func(env) # env是个大字典,里面含有信息,在后续的逻辑中可能还需要用到字典内的信息
else:
res = errors(env)
return [res.encode('utf-8')] # 将所有逻辑函数返回的字符串在此统一编码,发给客户端
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, run)
# 实时监测127.0.0.1:8080地址,一旦有客户端连接,会自动加括号调用run方法
server.serve_forever() # 启动服务端
随着业务逻辑复杂度的增加,处理业务逻辑的函数以及url_patterns中的映射关系都会不断地增多,此时仍然把所有代码都放到一个文件中,程序的可读性和可扩展性都会变得非常差,所以我们应该将现有的代码拆分到不同文件中
基于wsgiref模块将代码拆分成urls.py路由文件,views.py视图文件和wsgiref通信文件三部分,后续加功能只需要在urls.py文件和view.py文件中加入对应功能即可
mysite # 文件夹
├── app01 # 文件夹
│ └── views.py
├── mysite # 文件夹
│ └── urls.py
└── templates # 文件夹
│ ├── index.html
│ └── timer.html
├── main.py
- views.py 内容如下
def index(env):
return 'index'
def login(env):
return 'login'
def errors(env):
return '404 error'
- urls.py内容如下:
# 路径跟函数的映射关系
from app01.views import * # 需要导入views中的函数
urls = [
('/index', index),
('/login', login),
]
- main.py 内容如下:
from wsgiref.simple_server import make_server
from app01.urls import urls # 需要导入urls中的urls
def run(env, response):
response('200 OK', [])
current_path = env.get('PATH_INFO')# 拿到的是URL后缀
# 定义一个存储函数的标志位
func = None
for url in urls:
# 判断当前请求的url是否在元组内
if url[0] == current_path:
# 若在,赋值给func
func = url[1]
# 一旦匹配上就退出循环,节省资源
break
# 判断func是否有值
if func:
# 调用
res = func(env) # env是个大字典,里面含有信息,在后续的逻辑中可能还需要用到字典内的信息
else:
res = errors(env)
return [res.encode('utf-8')] # 将所有逻辑函数返回的字符串在此统一编码,发给客户端
if __name__ == '__main__':
server = make_server('127.0.0.1', 8080, run)
# 实时监测127.0.0.1:8080地址,一旦有客户端连接,会自动加括号调用run方法
server.serve_forever() # 启动服务端
至此,我们就针对application的开发自定义了一个框架,所以说框架的本质就是一系列功能的集合体、不同的功能放到不同的文件中。有了该框架,可以让我们专注于业务逻辑的编写,极大的提高了开发web应用的效率(开发web应用的框架可以简称为web框架)
比如我们新增一个业务逻辑,要求为:浏览器输入http://127.0.0.1:8011/home 就能访问到home.html页面,在框架的基础上具体开发步骤如下:
步骤一:在templates文件夹下新增home.html
步骤二:在urls.py的url_patterns中新增一条映射关系
urls = [
('/index', index),
('/login', login),
('/home', home),
]
步骤三:在views.py中新增一个名为home的函数
def home(environ):
with open('templates/home.html', 'r',encoding='utf-8') as f:
data = f.read()
return data.encode('utf-8')
我们自定义的框架功能有限,在Python中我们可以使用别人开发的、功能更强大的Django框架
其他: django源码之启动wsgi发生了那些事https://www.cnblogs.com/xiaoyuanqujing/articles/11902303.html