432 lines
10 KiB
Markdown
432 lines
10 KiB
Markdown
# HTTP的请求头和响应头
|
||
|
||

|
||
|
||
浏览器与服务器通信,主要是依靠请求报文来向服务器表示想要获取到什么样的资源,服务器会用响应报文来表达服务器会员的数据的情况
|
||
|
||
# 自定义web框架
|
||
|
||
web应用本质上就是一个socket服务端,浏览器是socket客户端,基于请求做出响应,客户都先请求,服务端做出对应的响应,按照http协议的请求协议发送请求,服务端按照http协议的响应协议来响应请求,这样的网络通信,我们就可以自己实现Web框架了。
|
||
|
||
准备一个html文件
|
||
|
||
```html
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>测试页面</title>
|
||
</head>
|
||
<body>
|
||
<h1>标题1</h1>
|
||
<img src="https://iproute.cn/medias/logo.png" alt="头像">
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
编写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的内容如下
|
||
|
||

|
||
|
||
```
|
||
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
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>测试页面</title>
|
||
</head>
|
||
<body>
|
||
<h1>标题1</h1>
|
||
<img src="logo.png" alt="头像">
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
然后修改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
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>测试页面</title>
|
||
</head>
|
||
<body>
|
||
<h1>标题1</h1>
|
||
<img src="logo.png" alt="头像">
|
||
<h2>@@666@@</h2>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
修改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
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>jinja2</title>
|
||
</head>
|
||
<body>
|
||
<h1>姓名:{{name}}</h1>
|
||
<h1>爱好:</h1>
|
||
<ul>
|
||
{% for hobby in hobby_list %}
|
||
<li>{{hobby}}</li>
|
||
{% endfor %}
|
||
</ul>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
编写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的语法规则写上,其内部就会按照指定的语法进行相应的替换,从而达到动态的返回内容。
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|