# HTTP的请求头和响应头 ![a856a010-ba01-45f7-8e59-8234ac2e9488](./01.web框架的本质/a856a010-ba01-45f7-8e59-8234ac2e9488.jpeg) 浏览器与服务器通信,主要是依靠请求报文来向服务器表示想要获取到什么样的资源,服务器会用响应报文来表达服务器会员的数据的情况 # 自定义web框架 web应用本质上就是一个socket服务端,浏览器是socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响应,按照http协议的请求协议发送请求,服务端按照http协议的响应协议来响应请求,这样的网络通信,我们就可以自己实现Web框架了。 准备一个html文件 ```html 测试页面

标题1

头像 ``` 编写python的socket服务端 ```python import socket sk = socket.socket() # 允许地址复用 sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr = sk.accept() b_msg = conn.recv(1024) str_msg = b_msg.decode('utf-8') conn.send(b'HTTP/1.1 200 ok \r\n\r\n') conn.send(b'hello') conn.close() sk.close() ``` 运行服务端之后,浏览器访问http://127.0.0.1:8080,浏览器传给socket的内容如下 ![image-20241210163939143](./01.web框架的本质/image-20241210163939143.png) ``` GET / HTTP/1.1 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.7 Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8 Cache-Control: max-age=0 Connection: keep-alive Host: 127.0.0.1:8080 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: none Sec-Fetch-User: ?1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 sec-ch-ua: "Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "macOS" ``` 修改socket服务端,让其返回网页内容 ```python import socket sk = socket.socket() # 允许地址复用 sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1',8080)) sk.listen() conn,addr = sk.accept() b_msg = conn.recv(1024) str_msg = b_msg.decode('utf-8') print(str_msg) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') with open('test.html','rb') as f: f_data = f.read() conn.send(f_data) conn.close() sk.close() ``` 这样就可以将网页内容返回给浏览器了 如果想要在网页中携带本地路径的图片,那么修改html代码 ```html 测试页面

标题1

头像 ``` 然后修改socket服务端 ```python import socket sk = socket.socket() # 允许地址复用 sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1',8080)) sk.listen() while 1: # 浏览器多次访问,所以需要while conn,addr = sk.accept() b_msg = conn.recv(1024) str_msg = b_msg.decode('utf-8') path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') if path == '/': with open('test.html','rb') as f: f_data = f.read() conn.send(f_data) conn.close() # HTTP协议是短链接的,一次请求对应一次响应,这个请求就结束了,所以我们需要写上close,不然浏览器自己断了 elif path == '/logo.png': with open('logo.png','rb') as f: f_data = f.read() conn.send(f_data) conn.close() sk.close() ``` 可以改成使用函数的版本 ```python import socket sk = socket.socket() # 允许地址复用 sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1',8080)) sk.listen() def func1(conn): with open('test.html', 'rb') as f: f_data = f.read() conn.send(f_data) conn.close() def func2(conn): with open('logo.png', 'rb') as f: f_data = f.read() conn.send(f_data) conn.close() while 1: conn,addr = sk.accept() b_msg = conn.recv(1024) str_msg = b_msg.decode('utf-8') path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') if path == '/': func1(conn) elif path == '/logo.png': func2(conn) sk.close() ``` 现在还不支持高并发的情况,可以加上多线程 ```python import socket from threading import Thread sk = socket.socket() # 允许地址复用 sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1',8080)) sk.listen() def func1(conn): with open('test.html', 'rb') as f: f_data = f.read() conn.send(f_data) conn.close() def func2(conn): with open('logo.png', 'rb') as f: f_data = f.read() conn.send(f_data) conn.close() while 1: conn,addr = sk.accept() b_msg = conn.recv(1024) str_msg = b_msg.decode('utf-8') path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') if path == '/': t = Thread(target=func1,args=(conn,)) t.start() elif path == '/logo.png': t = Thread(target=func2,args=(conn,)) t.start() sk.close() ``` 替换字符串,实现不同的时间访问返回时间戳模拟动态内容 在网页中,用特殊的符号`@@666@@`表示需要被替换掉的地方,修改html代码 ```html 测试页面

标题1

头像

@@666@@

``` 修改socket服务端 ```python import socket from threading import Thread import time sk = socket.socket() # 允许地址复用 sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sk.bind(('127.0.0.1',8080)) sk.listen() def func1(conn): with open('test.html', 'r',encoding="utf-8") as f: f_data = f.read() now = str(time.time()) f_data = f_data.replace("@@666@@",now).encode('utf-8') conn.send(f_data) conn.close() def func2(conn): with open('logo.png', 'rb') as f: f_data = f.read() conn.send(f_data) conn.close() while 1: conn,addr = sk.accept() b_msg = conn.recv(1024) str_msg = b_msg.decode('utf-8') path = str_msg.split('\r\n')[0].split(' ')[1] print('path>>>',path) conn.send(b'HTTP/1.1 200 ok \r\n\r\n') if path == '/': t = Thread(target=func1,args=(conn,)) t.start() elif path == '/logo.png': t = Thread(target=func2,args=(conn,)) t.start() sk.close() ``` # WSGI WSGI(Web Server Gateway Interface)是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。 常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。 # 模板渲染JinJa2 上面的过程中用`@@666@@`替换了动态的内容, 其实模板渲染有个现成的工具: `jinja2` 安装`jinja2` ```bash pip install jinja2 -i https://pypi.tuna.tsinghua.edu.cn/simple ``` 编写html文件 ```html jinja2

姓名:{{name}}

爱好:

``` 编写wsgi服务端 ```python from wsgiref.simple_server import make_server from jinja2 import Template def index(): with open("test.html", "r", encoding='utf-8') as f: data = f.read() template = Template(data) ret = template.render({"name": "张三", "hobby_list": ["抽烟", "喝酒", "烫头"]}) return [bytes(ret, encoding="utf-8"), ] URL_LIST = [ ("/index/", index), ] def run_server(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ]) url = environ['PATH_INFO'] func = None for i in URL_LIST: if i[0] == url: func = i[1] break if func: return func() else: return [bytes("404没有该页面", encoding="utf8"), ] if __name__ == '__main__': http = make_server('', 8000, run_server) print("Serving HTTP on port 8000...") http.serve_forever() ``` 下面演示从数据库中提取内容进行替换 - mysql点此下载:https://www.123865.com/s/BKNfTd-WDXKA提取码:6666 ```sql -- 创建数据库 create database pywebdb; -- 进入数据库中 use pywebdb -- 创建表 CREATE TABLE user( id int auto_increment PRIMARY KEY, name CHAR(10) NOT NULL, hobby CHAR(20) NOT NULL )engine=innodb DEFAULT charset=UTF8; -- 插入一个数据 INSERT into user (name, hobby) VALUES ('张三', '抽烟,喝酒,烫头'); -- 查看插入的数据 SELECT * FROM user; ``` 修改socket服务端 ```python import pymysql from wsgiref.simple_server import make_server from jinja2 import Template def index(name,hobby): with open("index.html", "r", encoding='utf-8') as f: data = f.read() template = Template(data) ret = template.render({"name":name, "hobby_list":hobby}) return [bytes(ret, encoding="utf-8"),] URL_LIST = [ ("/index/", index), ] def run_server(environ, start_response): conn = pymysql.connect( host="127.0.0.1", port=3306, user="root", passwd="test123", db="pywebdb", charset="utf8" ) cursor = conn.cursor() cursor.execute('select * from user') data = cursor.fetchone() name=data[1] hobby=data[2] conn.close() start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'),]) url = environ['PATH_INFO'] func = None for i in URL_LIST: if i[0] == url: func = i[1] break if func: return func(name,hobby) else: return [bytes("404没有该页面", encoding="utf8"),] if __name__ == '__main__': http = make_server('',8000,run_server) print("Serving HTTP on port 8000...") http.serve_forever() ``` 模板的原理就是字符串替换,我们只要在HTML页面中遵循jinja2的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。