HTTP 协议
1. HTTP 协议简介
- http 就是超文本传输协议
- 作用: 浏览器和web 服务器之间传输网页资源的协议格式
- HTTP底层是基于 TCP 协议实现的, 导致浏览器和服务器之间传输资源之前需要建立连接
- HTTP 协议是明文传输, HTTPS 是基于 HTTP 协议的安全通信方式< 密钥交互, 加密, 解密>
- web服务器: 提供网页
浏览器访问 web 服务器的通信过程
- 通过 DNS (域名解析服务器)将域名解析成 IP 地址
- 获取到 IP 地址
- 建立连接
- 发送 HTTP 请求数据
- 根据请求获取资源
- 返回资源给 web 服务器
- 返回 HTTP 响应数据
2. URL
URL的样子:
https://docs.python.org/3/search.html?q=asyncio&check_keywords=yes&area=default%23xxxx
URL的组成
- 协议部分: https:// http:// ftp://
- 域名部分: docs.python.org
- 查询参数:?q=asyncio&check_keywords=yes&area=default%23
- 资源路径:3/search.html
- 锚点: xxxx (不是给服务器用的 是给浏览器显示网页的时候用的)
3. HTTP 请求报文说明
3.1 get 请求报文的格式总结:
-- 请求行request line 分为三部分
-- 请求方式method 资源路径 版本
GET / HTTP/1.1
--- 请求方法
--- GET 获取 一般用于浏览器从服务器获取资源 <80%>
--- POST 提交 一般用于浏览器向服务器提交资源 上传
--- 资源路径<加查询参数> /表示主页
-- 网址是
https://www.baidu.com/s?wd=haha&rsv_sug4=2068
-- 请求行是
GET /s?wd=haha&&rsv_sug4=2068 HTTP/1.1
-- 请求头 k:v
# 代表浏览器需要请求的主机名称 一般是域名:非默认端口
Host: www.baidu.com
# 连接方式 keep-alive保持存活-长连接/close-立即关闭-短连接
Connection: keep-alive
# 用户代理 浏览器身份<适配、反爬虫 python3.7>
User-Agent: Mozilla/5.0 (Macintosh;Chrome/79.0.3945.88
# 接受文件类型
Accept: text/html,application/xhtml+xml..exchange;v=b3;q=0.9
# 接受的文件压缩格式 提高用户体验
Accept-Encoding: gzip, deflate, br
# 接受的语言 简体 英语 繁体
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7
get 和 post 的区别:
- get 获取少量提交查询参数中 键值对 容易被缓存 因此不安全, 一般没有请求体
- post 提交 请求体中任意类型 不被缓存安全 有请求体
3.2 get请求原始报文说明:
GET /index2.html HTTP/1.1\r\n
Host: 127.0.0.1:8888\r\n
Connection: keep-alive\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n
Upgrade-Insecure-Requests: 1\r\n
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36\r\n
Accept-Encoding: gzip, deflate, sdch\r\n
Accept-Language: zh-CN,zh;q=0.8\r\n
\r\n
3.3 POST 请求报文说明:
---- 请求⾏ ----
POST /xmweb?host=mail.itcast.cn&_t=1542884567319 HTTP/1.1 # POST请求⽅式 请求资源路径
HTTP协议版本
---- 请求头 ----
Host: mail.itcast.cn # 服务器的主机地址和端⼝号,默认是80
Connection: keep-alive # 和服务端保持⻓连接
Content-Type: application/x-www-form-urlencoded # 告诉服务端请求的数据类型
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHT
ML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 # 客⼾端的名称
---- 空⾏ ----
---- 请求体 ----
username=hello&pass=hello # 请求参数
3.4 POST 请求原始报文说明:
POST /xmweb?host=mail.itcast.cn&_t=1542884567319 HTTP/1.1\r\n
Host: mail.itcast.cn\r\n
Connection: keep-alive\r\n
Content-Type: application/x-www-form-urlencoded\r\n
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/537.36 (KHT
ML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n
\r\n(请求头信息后⾯还有⼀个单独的’\r\n’不能省略)
username=hello&pass=hello
3.5 请求报文的格式总结:
- 请求行[方法 路径 版本\r\n]
- 请求头[名字: 值\r\n]
- 空行[\r\n]
- 请求体[浏览器需要提交给服务器的数据]
需要注意的是:
- get 方式的请求报文没有请求体, 只有请求行, 请求头, 空行组成
- post 方式的请求报文可以由请求行, 请求头, 空行, 请求体四部分组成(post 方式可以允许没有请求体, 但是这种格式很少见)
4. HTTP 响应报文格式说明
4.1 响应报文说明:
-- 1. 状态行 status line 分为三步 版本 状态码 状态说明
HTTP/1.1 200 OK
--- 状态码和说明一一对应 HTTP 标准文档规定
--- 1xx 继续
--- 2xx 成功 200 OK
--- 3xx 重定向 307 Internal Redirect
--- Location: https://www.baidu.com/
--- 4xx 客户端错误 404 Not Found
--- 5xx 服务器错误 503 Service Unavailable
-- 2. 响应头 名字:值
-- 连接方式 长连接
Connection: keep-alive
# 内容压缩格式 浏览器通过这个参数知道数据应该怎么解压
Content-Encoding: gzip
# 内容的长度
Content-Length : 18
# 内容类型 决定了不同的解析方式
Content-Type: text/html;charset=utf-8
# 日期 响应报文返回的时间 东8区
Date: Sat, 21 Dec 2019 03:20:13 GMT
# 过期日期 不让浏览器缓存这个网页
Expires: Sat, 21 Dec 2019 03:20:13 GMT
# web服务器名字 Baidu Web Server
Server: BWS/1.1
4.2 原始响应报文说明:
HTTP/1.1 200 OK\r\n
Server: Tengine\r\n
Content-Type: text/html; charset=UTF-8\r\n
Transfer-Encoding: chunked\r\n
Connection: keep-alive\r\n
Date: Fri, 23 Nov 2018 02:01:05 GMT\r\n
\r\n(响应头信息后⾯还有⼀个单独的’\r\n’不能省略)
<!DOCTYPE html><html lang=“en”> …</html>
4.3 HTTP 状态码介绍
HTTP 状态码是用于表示 web 服务器相应的 3 位数字代码.
状态码 | 说明 |
---|---|
200 | 请求成功 |
307 | 重定向 |
400 | 错误的请求, 请求地址或者参数有误 |
404 | 请求资源在服务器不存在 |
500 | 服务器内部源代码出现错误 |
4.4 响应报文格式总结
-
状态行[版本 状态码 状态说明\r\n]
-
响应头[名字 : 值\r\n]
-
空行[\r\n]
-
响应体[服务器给浏览器发送的数据内容]
5. 模仿浏览器请求web 服务器
验证请求报文和响应报文
import socket
if __name__ == "__main__":
# 1. 创建一个套接字
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 域名解析得到服务器 IP +默认端口 连接服务器
tcp_socket.connect(("www.baidu.com", 80))
# 3. 发送请求报文
http_request_data = "DET/HTTP/1.1\r\n\r\n"
tcp_socket.send(http_request_data.encode())
# 4. 得到响应报文
recv_data = tcp_socket.recv(4096)
print(recv_data)
# 5. 关闭套接字
tcp_socket.close()
6 web 服务器
6.1 返回固定数据
# web 服务器 = TCP服务器+ HTTP 协议
# 1.0 返回固定数据
import socket
def main():
# 1 创建一个服务器套接字 绑定 监听转接用户连接请求到和客户端的套接字
server_socket = socket.socket()
server_socket.setsockopt(socket.SOL_SOCKET, sock.SO_REUSEADDR, 1)
server_socket.bind(("", 9999))
server_socket.listen(128)
while True:
new_client_socket, client_address = server_socket.accept()
print("客户端%s 上线了"% str(client_address))
# 2. 接受用户请求报文<一个套接字如果没有收数据就关了 那么可能会报错>
recv_data = new_client_socket.recv(4096)
if not recv_data:
print('客户端%s下线了' % str(client_address))
new_client_socket.close()
continue
# 3.回复响应报文< HTTP 响应报文>
http_response_data = "HTTP/1.1 200 OK\r\nServer: PWS1.0\r\n\r\n" + "hello world"
# 4.关闭套接字和客户端关联的套接字 ----短连接
new_client_socket.close()
if __name__ == "__main__":
main()
浏览器访问 web 服务器效果:
6.2 返回固定页面
# web 服务器 = TCP服务器+ HTTP 协议
# 1.0 返回固定数据
# 2.0 返回固定网页或者图片
import socket
def main():
# 1. 创建一个服务器套接字 绑定 监听转接用户连接请求到和客户端关联的套接字
server_socket = socket.socket()
server_socket.setsocketopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(("", 9999))
server_socket.listen(128)
while True:
new_client_socket, client_address = server_socket.accept()
print("客户端%s上线了" % str(client_address))
# 2. 接受用户请求报文<一个套接字如果没有收数据就关了, 那么可能会报错>
recv_data = new_client_socket.recv(4096)
if not recv_data:
print("客户端%s下线了" % str(client_address))
new_client_socket.close()
continue
# 打开一个固定文件 读取
with open("./static/xjj1.jpg", "rb") as file:
file_data = file.read()
# 3. 回复响应报文< HTTP 响应报文>
http_response_data = "HTTP/1.1 200 OK\r\nServer: PWS1.0\r\n\r\n.encode() + file_data"
new_client_socket.send(http_response_data)
# 4. 关闭和客户端关联的套接字 - 短链接
new_client_socket.close()
if __name__ == "__main__":
main()
6.3 返回指定页面
# web 服务器 =TCP 服务器 + HTTP 协议
# 1.0 返回固定数据
# 2.0 返回固定网页或者图片
import socket
def main():
# 1. 创建一个服务器套接字 绑定 监听转接用户连接请求到和客户端关联的套接字
server_socket = socket.socket()
server_socket.setsockopt(socket.SOL_SOCKET, socket_SO_REUSEADDR, 1)
server_socket.bind(("", 9999))
server_socket.listen(128)
while True:
new_client_socket, client_address = server_socket.accept()
print("客户端%s上线了" % str(client_address))
# 2. 接收用户请求报文<一个套接字如果没有收数据就关了, name可能报错>
recv_data = new_client_socket.recv(4096)
# 2.1 判断用户是否已经下线
if not recv_data:
print(" 客户端%s 已经下线了) % str(client_address))
new_client_socket.close()
continue
# 2.2 获取到用户请求报文中的资源路径
path_info = recv_data.decode().split(" ")[1]
print("收到用户的资源请求路径是," path_info)
# 2.3 判断如果用户请求路径是否为空 /
if path_info == "/":
patn_info = "/grand.html"
# 2.4 打开一个指定文件读取
try:
# 可能发生异常的代码 执行这里
with open("./static/404.html", "rb") as file:
file_data = file.read()
except Exception as e:
# 如果发生异常 执行这里
with open("./static/404.html", "rb") as file:
file_data = file.read()
http_response_data = "HTTP/1.1 404 Not Found\r\nServer: PWS1.0\r\n\r\n".encode() + file_data
else:
# 没有异常执行这里
# 3. 回复响应报文<HTTP 响应报文>
http_response_data = "HTTP/1.1 200 OK\r\nServer: PWS1.0\r\n\r\n".encode() + file_data
finally:
# 不管有没有异常 都执行这里
new_client_socket.send(http_response_data)
# 4. 关闭和客户端关联的套接字 ---短连接
new_client_socket.close()
if __name__ == "__main__":
main()