Web框架基础
本章需要掌握
1、http协议:https://www.cnblogs.com/linhaifeng/p/6266327.html
2、web框架基础:
egon《web应用》:https://www.cnblogs.com/xiaoyuanqujing/articles/11641028.html
Jason《web框架前戏》 https://www.cnblogs.com/Dominic-Ji/articles/16294929.html
3、套接字 socket编程:https://www.cnblogs.com/linhaifeng/articles/6129246.html
4、WSGI,wsgiref,werkzeug,uwsgi,uWSGI,nginx
https://www.cnblogs.com/biggw/p/11342109.html
https://www.cnblogs.com/quegai18/p/10509996.html
https://www.cnblogs.com/guokaifeng/p/11007359.html
https://www.cnblogs.com/wcx666/p/10444400.html
5、Python三大主流框架:https://blog.51cto.com/u_15052541/3051579
下面是web简易框架代码:
app01.py
'''Web应用程序是基于B/S架构的,其中B指的是浏览器,S端由server和application两大部分构成。
我们无需开发浏览器(本质即套接字客户端),只要开发S端即可,S端的本质就是用套接字实现的。''' # S端 # import socket # 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 = app(recv_data) # # # 3.向浏览器返回消息(此处并没有按照http协议返回) # conn.send(res) # # conn.close() # # def app(environ): # 代表application # # 处理业务逻辑 # return b'hello world' # # if __name__ == '__main__': # make_server('127.0.0.1', 8000, app) """
上面的代码:目前S端已经可以正常接收浏览器发来的请求消息了,但是浏览器在接收到S端回复的响应消息b'hello world'时却无法正常解析 , 因为浏览器与S端之间收发消息默认使用的应用层协议是HTTP,浏览器默认会按照HTTP协议规定的格式发消息, 而S端也必须按照HTTP协议的格式回消息才行,所以接下来我们详细介绍HTTP协议
套接字工作流程: 服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。 在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。 客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。 """ # S端修订版本:处理HTTP协议的请求消息,并按照HTTP协议的格式回复消息 import socket def make_server(ip, port, app): # 创建一个服务器,传入参数,IP,端口port,处理函数app # ip+port=socket sock = socket.socket() # 绑定协议,生成套接字 sock.bind((ip, port)) # 绑定自己的IP地址和端口。(ip, port)必须是个元组 sock.listen(5) # 开始监听HTTP请求 设置连接最大排队数量 print('Starting developing server at http://%s:%s/'%(ip, port)) # 服务器启动 while True: # 链接循环 conn, addr = sock.accept() # 接受链接。对应客户端发过来的操作。客户端返回的是一个小元组,然后解压赋值,把这两个值解压出来。 # conn是一个对象,封装的就是三次握手的成果(一个双向的通路),addr是客户端的ip和端口。 # 1.接收并处理浏览器发来的请求 # 1.1 接收并处理浏览器发来的http协议的消息 recv_data = conn.recv(1024) # 最大接收字节数 # print('recv_data:',recv_data) # 1.2 对http协议的消息加以处理,简单示范如下: ll = recv_data.decode('utf-8').split('\r\n') print('ll:', ll) head_ll = ll[0].split(' ') print('head_ll:', head_ll) environ = {} environ['PATH_INFO'] = head_ll[1] environ['method'] = head_ll[0] print(environ) # 打印print(environ)后的结果: # {'PATH_INFO': '/', 'method': 'GET'} # {'PATH_INFO': '/favicon.ico', 'method': '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' # ② 返回html页面 # return b'<h1>hello web</h1><img src="http://www.baidu.com/img/pc_27928619fedf2633952764032537980f.gif"></img>' # ③ 打开文件,读取文件内容并返回 # with open('timer.html', 'r', encoding='utf-8') as f: # data = f.read() # return data.encode() # ④ 上述S端为浏览器返回的都是静态页面(内容都是固定的),我们还可以返回动态页面(内容是变化的) 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', 8000, app) # 在浏览器输入http://127.0.0.1:8000,每次刷新都会看到不同的时间
timer.html文件:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>{{ time }}</h2> </body> </html>
app02.py
''' 根据app01.py文件中的案例我们可以发现一个规律,在开发S端时,server的功能是复杂且固定的(处理socket消息的收发和http协议的处理), 而app中的业务逻辑却各不相同(不同的软件就应该有不同的业务逻辑),重复开发复杂且固定的server是毫无意义的,有一个wsgiref模块帮我们 写好了server的功能,这样我们便只需要专注于app功能的编写即可 ''' # wsgiref实现了server,即make_server → def make_server(ip, port, app): 这部分里面的内容 from wsgiref.simple_server import make_server # wsgiref的simple_server文件封装了make_server函数 def app(environ, start_response): # 代表application # 1. 返回http协议的响应首行和响应头信息 print(environ) start_response('200 OK', [('Content-Type', 'text/html')]) # 2. 处理业务逻辑:根据请求url的不同返回不同的页面内容 if environ.get('PATH_INFO') == '/index': with open('index.html', 'r', encoding='utf-8') as f: data = f.read() # 浏览器输入:http://127.0.0.1:8001/index 显示 主页 elif environ.get('PATH_INFO') == '/timer': with open('timer.html', 'r', encoding='utf-8') as f: data = f.read() # 浏览器输入:http://127.0.0.1:8001/timer 显示 2022-07-21 17:39:51(当前时间) import time now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) data = data.replace('{{ time }}', now) else: data = '<h1>Hello,web!</h1>' # 浏览器输入:http://127.0.0.1:8001 显示 Hello,web! # 3.返回http响应体信息,必须是bytes类型,必须放在列表中 return [data.encode('utf-8')] if __name__ == '__main__': # 当接收请求时,wsgiref模块会对该请求加以处理,然后会调用app函数,自动传入两个参数; # 1 environ是一个字典,存放了http的请求信息 # 2 start_response 是一个功能,用于返回http协议的响应首行和和响应头信息 s = make_server('', 8001, app) # 代表server print('监听8001') s.serve_forever() # 表示该服务器在正常情况下将永远运行。 在浏览器输入http://127.0.0.1:8001/index和http://127.0.0.1:8001/timer会看到不同的页面内容
timer.html已经存在了,新增的index.html页面内容如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>主页</h1> </body> </html>
app03.py
''' 根据app02.py文件案例中app处理逻辑时根据不同的url地址返回不同的页面,当url地址越来越多,需要写一堆if判断, 代码不够清晰,耦合程度高,所以我们做出以下优化 ''' # 处理业务逻辑的函数 def index(environ): with open('index.html', 'r', encoding='utf-8') as f: data = f.read() return data.encode('utf-8') def timer(environ): import datetime now = datetime.datetime.now().strftime('%y-%m-%d %X') # 浏览器显示:22-07-22 13:22:26 (%y y是小写时,是22,Y大写时是2022; %X 时间的本地版本) with open('timer.html', 'r', encoding='utf-8') as f: data = f.read() data = data.replace('{{ time }}', now) return data.encode('utf-8') # 路径和函数的映射关系 url_patterns = [ ('/index', index), ('/timer', timer), ] from wsgiref.simple_server import make_server def app(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) # 拿到请求的url并根据映射关系url_patterns执行响应的函数 request_url = environ.get('PATH_INFO') for url in url_patterns: if url[0] == request_url: data = url[1](environ) break else: data = b'404' return [data] if __name__ == '__main__': s = make_server('',8011, app) print('监听8011') s.serve_forever() # 表示该服务器在正常情况下将永远运行。
随着业务逻辑复杂度的增加,处理业务逻辑的函数以及url_patterns中的映射关系都会不断地增多,此时仍然把所有代码都放到一个文件中,程序的可读性和可扩展性都会变得非常差,所以我们应该将现有的代码拆分到不同文件中。
关键文件介绍:
-manage.py 项目入口,执行一些命令 -项目名 -settings.py 全局配置信息 -urls.py 总路由,请求地址跟视图函数的映射关系 -app名字 -migrations 数据库迁移的记录 -models.py 数据库表模型 -views.py 处理业务逻辑的函数,简称视图函数
代码内容根据egon博客敲的 https://www.cnblogs.com/xiaoyuanqujing/articles/11641028.html