文章目录
- 参考
- 描述
- HTTP
- HTTP 报文
- HTTP 请求报文
- 请求行
- 请求头
- 空行
- 请求体
- HTTP 响应报文
- 状态行
- 响应头
- 空行
- 响应体
- HTTP 请求方法
- HTTP 与 TCP 协议
- HTTP 协议
- TCP 协议
- httpbin.org
- socket 模块
- socket.socket()
- Socket().connect()
- Socket().send() 与 Socket().sendall()
- Socket().recv()
- 使用 socket 发起 GET 请求
- 虚拟主机托管
- Host 请求头
- Transfer-Encoding
- 分块
- 响应体的长度限制
- 行结束符
- HTTP 协议与行结束符
- 具体实现
参考
项目 | 描述 |
---|---|
RFC 7230 - Hypertext Transfer Protocol (HTTP/1.1) | Message Syntax and Routing |
Python 官方文档 | https://docs.python.org/zh-cn/3/ |
搜索引擎 | Google 、Bing |
描述
项目 | 描述 |
---|---|
PyCharm | 2023.1 (Professional Edition) |
Python | 3.10.6 |
HTTP
HTTP(Hypertext Transfer Protocol)
即超文本传输协议,是一个遵循经典的 客户端-服务器
模型的协议,常用于在 WEB 浏览器
和 WEB 服务器
之间传输数据。HTTP 是互联网上 使用最广泛的协议之一
,常用于 Web 应用程序
的开发和端到端数据传输。
HTTP 协议的 基本工作原理
是,客户端发送 HTTP 请求
到服务器,服务器接收到来自客户端的请求后对其进行处理,并返回合适的 HTTP 响应
给客户端。
HTTP 报文
HTTP 请求报文
POST /api/user HTTP/1.1
Host: example.com
Content-Type: application/json
Content-Length: 45
{"username": "john", "password": "secretpassword"}
HTTP 请求报文可以划分为 请求行
、请求头
、空格
及 请求体
四部分。其中,仅有 请求体
可以不包含于 HTTP 请求报文中。
请求行
请求行(Request Line)
位于 HTTP 请求报文中的第一行,用于指定 请求方法
、请求的目标资源所在的路径
以及 该次请求过程中所使用的 HTTP 协议版本
。
POST /api/user HTTP/1.1
请求头
请求头(Request Headers)
位于请求行之后,用于传递与 HTTP 请求相关的各种信息。在 HTTP 请求报文中,每一个 头部字段
都由 字段名
和 字段值
组成,字段名与字段值之间使用冒号进行分隔。
Host: example.com
Content-Type: application/json
Content-Length: 45
在上述请求头示例中,HOST
字段用于指定目标主机所对应的域名信息;Content-Type
字段用于指定请求体的 媒体类型
,字段值 application/json
则表明请求体的媒体类型位 JSON
;Content-Length
字段用于指定请求体的大小,以 字节
为单位。
空行
单个空行
位于请求头与请求体之间,标志着请求头的结束以及请求体的开始。
请求体
请求体(Request Body)
位于 单个空行
之后,用于承载 HTTP 请求过程中需要发往 HTTP 服务器端的数据。
{"username": "john", "password": "secretpassword"}
在上述请求体示例中,请求体是一个 JSON
格式的数据,包含了用户名和密码的 键值对
。
HTTP 响应报文
HTTP 响应报文可以划分为 状态行
、响应头
、空格
及 响应体
四部分,这四部分内容 共同构成
了 完整的 HTTP响应
,用于向客户端传递服务器的处理结果。
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 56
{"message": "Success", "data": {"id": 1, "name": "John"}}
状态行
状态行(Status Line)
位于 HTTP 响应报文的第一行,状态行由 HTTP 协议版本
、状态码
和 状态消息
三部分组成。
HTTP/1.1 200 OK
在上述示例中,HTTP 协议版本为 HTTP/1.1
,状态码为 200
,表示请求成功,状态消息为 OK
。
响应头
响应头(Response Headers)
位于状态行之后,用于传递与 HTTP 响应相关的各种信息。与请求头类似,每个响应头字段都由字段名和字段值组成,使用冒号进行分隔。
Content-Type: application/json
Content-Length: 56
在上述示例中,Content-Type
字段指定了响应体的媒体类型为 application/json
,表示响应体的数据格式为 JSON
;Content-Length
字段指定了响应体的长度,以字节为单位。
空行
单个空行
位于响应头与响应体之间,用于明确分隔响应头部和响应体的开始。
响应体
响应体(Response Body)
位于 单个空行
之后,包含了 HTTP 服务器端返回给客户端的实际数据。
{"message": "Success", "data": {"id": 1, "name": "John"}}
在上述示例中,响应体是一个 JSON 格式的数据,包含了一个名为 message
的字段和一个名为 data
的字段,其中 data
字段又包含了 id
和 name
两个字段。
HTTP 请求方法
HTTP 请求方法用于定义客户端对服务器端 资源
的 操作类型
和 意图
。HTTP 协议定义了 九
个 HTTP 请求方法,分别是 GET、POST、PUT、DELETE、HEAD、OPTIONS、TRACE、CONNECT 以及 PATCH。
HTTP 与 TCP 协议
HTTP 协议
HTTP 协议是一种 应用层协议
,用于在 Web
上进行数据通信,该协议定义了客户端(例如 Web 浏览器
)和服务器之间进行请求和响应的 规则
。HTTP协议使用 TCP
作为其传输层协议,通过 TCP 协议建立 双向连接
,确保数据的 可靠传输
。
TCP 协议
TCP 协议是一种 传输层协议
,提供 进程到进程
的可靠数据传输。TCP 协议使用 IP 协议
来 定位
网络上的主机,并为应用程序提供了一种 可靠的
、面向连接
的通信机制。TCP 协议通过 序号
、确认
和 重传
等机制来确保数据的 完整性
和可靠性。TCP 还提供了 拥塞控制
和 流量控制
等功能,以确保网络中的各个节点能够适应不同的网络条件。
httpbin.org
httpbin.org
是一个 开发者友好
的 HTTP 请求和响应服务,它提供了一组简单的 HTTP 请求和响应工具,可以帮助开发人员测试他们的应用程序。
httpbin.org
的 主要功能
包括:
-
HTTP 请求测试
您可以向httpbin.org
发起各种类型的 HTTP 请求,包括 GET、POST、PUT、DELETE 等,以便测试您的应用程序如何处理这些请求。您可以设置请求头、查询参数、请求体等,并获得相应的响应。 -
请求信息展示
httpbin.org
可以显示您发起的 HTTP 请求的详细信息,包括请求头、查询参数、来源 IP 地址、用户代理等,这对于调试
和了解请求的细节
非常有用。 -
响应信息控制
httpbin.org
允许您指定响应的状态码
、头信息
和响应正文内容
,以模拟不同的响应情况
,这对于测试应用程序对不同响应的处理逻辑非常有帮助。 -
认证和授权测试
httpbin.org
支持模拟基本身份验证
和OAuth2 认证过程
,以帮助开发人员测试他们的应用程序对于认证和授权的处理是否正确。 -
WebSocket 测试
httpbin.org
还支持WebSocket
协议的测试,您可以使用 WebSocket 客户端连接到 httpbin.org,并进行交互式的通信测试。
httpbin.org
的一个主要优点是它的易用性和可靠性。httpbin.org
是一个 开源项目
,可以方便地与您的应用程序集成。您可以通过 直接访问
网站 httpbin.org
或使用该网站提供的 API
来进行测试。
访问 http://httpbin.org/get
,你将使用 httpbin.org
提供的 GET
请求 API。httpbin.org
还提供了许多其他请求类型的 API,要使用这些 API 你仅需要在 http://httpbin.org
后添加合适的路径即可。
尝试访问 https://httpbin.org/get?name=RedHeart 你将得到类似如下内容:
{
"args": {
"name": "RedHeart"
},
"headers": {
"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",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Host": "httpbin.org",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"X-Amzn-Trace-Id": "Root=1-648ae051-43a9054c3e288c6c6f24c251"
},
"origin": "35.77.73.113",
"url": "http://httpbin.org/get?name=RedHeart"
}
其中:
-
args
这个字段表示在 GET 请求中通过 URL 传递的参数。在这个例子中,参数是name=RedHeart
,它指定了一个名为name
的参数,其值为RedHeart
。 -
origin
这个字段表示发出请求的客户端
的IP 地址
。在这个例子中,客户端的IP地址是35.77.73.113
。 -
url
这个字段表示请求的 URL
。在这个例子中,请求的 URL 是http://httpbin.org/get?name=RedHeart
,其中包含了主机名、路径以及通过 URL 传递的查询字符串
。 -
headers
这个字段包含了请求头
中的各个字段信息,以键值对的形式表示。头部信息包括客户端发送请求时的各种元数据
,如浏览器类型、编码偏好、语言偏好等。
socket 模块
socket.socket()
在 socket
模块中,socket()
类用于创建 套接字对象
。
class socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0, fileno=None)
其中:
参数 | 描述 |
---|---|
family | 指定地址族类型,默认为 IPV4 。socket 模块提供常量 socket.AF_INET 和 socket.AF_INET6 分别用于指定地址族 IPV4 与 IPV6 。 |
type | 指定套接字类型,默认为 TCP 套接字 。socket 模块提供常量 socket.SOCK_STREAM 和 socket.SOCK_DGRAM 分别用于指定 TCP 套接字与 UDP 套接字。 |
proto | 指定套接字所使用的传输层协议,默认值为 0 。使用 默认值 0 意味着 socket() 将依据参数 family 与 type 的值自动选择合适的传输层协议。socket 模块提供了常量 socket.IPPROTO_TCP 和 socket.IPPROTO_UDP 分别用于指定传输层协议 TCP 与 UDP 。 |
fileno | 指定套接字所使用的 文件描述符(文件描述符是操作系统中用于标识打开文件或套接字的整数值) ,使用同一文件描述符的 套接字对象将 被视为同一个 套接字 。 |
Socket().connect()
socket
对象的 connect()
方法用于在客户端中通过 客户端套接字
向服务器端的 监听套接字
发起连接请求。
connect()
方法接收一个二元元组作为实参,该元组用于表示服务器端监听套接字的套接字地址。
connect()
方法没有返回值,它会尝试与指定的服务器建立连接。若连接建立失败,该方法将会抛出异常。
Socket().send() 与 Socket().sendall()
套接字对象的 send() 方法
的作用是将数据发送到 套接字
,以便将数据传输到远程主机。send()
方法将返回已发送数据的 字节数
,若因为某些不可控因素导致数据 仅部分
发送至目标套接字,则程序需要自行处理数据未完全发送的情况。
套接字对象还具有一个与 send()
方法类似的方法 sendall()
,sendall()
方法的作用是将指定的数据发送到与当前套接字连接的另一端套接字。与 send()
方法不同的是,sendall()
方法会自动处理数据仅部分发送的情况,并保证所有数据都被发送出去。若数据无法 全部
发送至目标套接字,则该方法将 抛出异常
。
注:
使用 send()
或 sendall()
方法仅能够向远程主机传递二进制数据。通过调用字符串对象的 encode()
方法将能够得到该字符串对象的二进制数据形式。
Socket().recv()
套接字对象的 recv()
方法,能够接收来自 与当前套接字连接的另一端套接字
的数据,并将接收到的数据存储在指定大小的 缓冲区
中。
recv()
方法允许传递一个参数,用以指定存储数据的缓冲区大小(以 字节
为单位)。如果接收到的数据大小超过了设定缓冲区大小,那么服务器端将仅能够接收到缓冲区大小的数据。因此在实际使用时,通常需要在一个循环中多次调用recv()
方法,直到接收到所需的所有数据。
注:
recv()
方法是一个 阻塞调用
,即在没有接收到数据时,当前线程将会被阻塞,直到接收到数据或发生错误。可以通过调用 socket.setdefaulttimeout()
函数设置套接字的超时时长以控制阻塞的行为,当阻塞时长大于设定的超时时长时,recv()
方法将抛出异常。
使用 socket 发起 GET 请求
虚拟主机托管
虚拟主机托管是一种在 单个物理服务器
上托管多个 域名
或 网站
的技术。通过虚拟主机托管,多个域名可以 共享同一台服务器的资源
,每个域名都可以拥有自己的网站、电子邮件和其他互联网服务。
在 传统的物理服务器环境中
,一台服务器通常只托管一个域名或网站。然而,虚拟主机托管通过使用 虚拟化技术
和 HTTP 协议
的特性,使得一台服务器可以同时托管多个域名。这些虚拟主机之间是 相互隔离
的,它们在 逻辑上被视为独立的服务器
,每个虚拟主机都可以有自己的独立设置和配置。
虚拟主机托管的 核心思想
是通过在HTTP请求中使用 Host
请求头来区分不同的域名。当客户端发送请求时,它会在请求报文的 Host
请求头中指定目标域名或主机名。服务器接收到请求后,解析 Host
请求头来确定请求的目标是哪个虚拟主机。根据目标主机的配置,服务器会将请求路由到相应的虚拟主机,使得每个域名可以独立地提供自己的网站内容和服务
。
虚拟主机托管是现代互联网中广泛使用的技术,它允许在有限的硬件资源下托管和管理多个域名和网站,提供了 更高的灵活性和效率
。
Host 请求头
根据 HTTP/1.1
规范,Host
请求头在请求报文中是 必需的
。指定了目标主机及端口号(HTTP
协议默认使用 80
端口),并且在使用 HTTP/1.1
版本的协议中,该字段是 强制性
的。
根据 HTTP/1.1
规范,如果服务器接收到的请求没有 Host
请求头字段,服务器应该返回状态码为 400 Bad Request
的响应,表示 请求有误
。
根据 HTTP/1.1
规范的要求,当客户端发送请求时,必须包含 Host
请求头。这是因为在 虚拟主机托管
的环境中,一个物理服务器可以托管多个域名
,因此通过 Host
请求头来指示 目标虚拟主机
是非常重要的。服务器使用该字段来 确定请求的目标
,以便正确地处理请求。
Transfer-Encoding
Transfer-Encoding
是一个响应头(Response Header),它用于指示服务器在将 响应主体
发送给客户端时所 采用的传输编码方式
。
Transfer-Encoding: chunked
Transfer-Encoding
响应头的主要作用是 允许服务器以分块(chunked)方式传输响应主体
。分块传输允许服务器将响应分成一系列的块,每个块都带有长度信息,这样可以 逐块地将响应发送给客户端
。这种传输方式对于 大型响应
或 流式传输
非常有用,因为它允许响应在传输过程中 逐步生成而无需等待整个响应主体准备完毕
。
使用 Transfer-Encoding: chunked
字段的 HTTP 响应报文
,无需
提供 Content-Length
响应头,因为响应主体的长度通过 每个分块的长度信息
来确定。
在实际的应用中,大多数情况下,你不需要直接处理 Transfer-Encoding
响应头。通常,HTTP 客户端和服务器会 自动处理响应的传输编码
,确保正确接收和解析响应主体。
分块
使用 Transfer-Encoding: chunked
字段的 HTTP 响应报文
,响应体将以 一系列分块
的形式进行发送。Content-Length
首部在这种情况下不被发送,为了统计响应体的长度,需要在每一个分块的开头添加 当前分块的长度
,分块长度以 十六进制数
表示,单位为 字节
,后面紧跟着 CRLF(该符号用于指示光标移动至下一行)
,之后是分块本身,后面也是 CRLF
。终止块
是一个常规的分块,不同之处在于其 长度为 0
,并且该分块的内容为一个 CRLF
。
响应体的长度限制
在使用 Transfer-Encoding
响应头后,每个响应体的长度是 没有强制规定
的,响应体的长度
可以根据服务器的 策略和需要
进行确定。
通常情况下,服务器会根据 响应主体的大小
和 网络传输的优化考虑
来确定块的大小。使用长度较小的响应体
可以 更快地传输数据
,但会导致 较多的块头开销
。使用长度较大的响应体
可以 减少块头开销
,但可能会 导致传输过程中的延迟增加
。
行结束符
CRLF
是 Carriage Return and Line Feed
的缩写,指的是 回车符(Carriage Return)
和 换行符(Line Feed)
。
回车符(\r)
表示将光标移到当前行的行首,换行符(\n)
表示将光标移到下一行。在文本文件中,CRLF
用于表示 行结束符
。
不同的 操作系统
使用的换行符有所不同,例如:
Windows
系统使用CRLF(\r\n)
作为行结束符。Unix/Linux
系统使用LF(\n)
作为行结束符。Macintosh
系统(可以认为是MacOS 的早期版本
)使用CR(\r)
作为行结束符。MacOS
系统使用LF(\n)
作为行结束符。
HTTP 协议与行结束符
HTTP 协议规定在 HTTP 报文中,应使用 CRLF(\r\n)
作为行结束符。HTTP 做出此规定,有如下原因:
-
规范要求
HTTP 协议规范定义了使用 CRLF 作为行结束符,遵循规范是确保与其他遵循相同规范的软件和系统之间的互操作性
的重要步骤。 -
统一标准
使用 CRLF 作为行结束符可以确保不同系统之间的一致性
。不同操作系统可能使用不同的换行符,而使用 CRLF 可以避免换行符差异的问题。
具体实现
import socket as sk
# 设置需访问的目标套接字地址,由目标主机(与主机名相绑定的
# 域名或 IP 地址)及端口号组成。HTTP 服务通常使用 80 端口。
HOST = 'httpbin.org'
PORT = 80
# 建立 TCP 套接字
client = sk.socket()
# 向目标套接字发起连接
client.connect((HOST, PORT))
# 准备需要向目标套接字发送的 HTTP 请求报文
message = f'GET /get?content=RedHeart HTTP/1.1\r\n' \
f'Host: {HOST}\r\n' \
f'UserAgent: Python 3.10.6\r\n' \
f'WhoAmI: RedHeart\r\n' \
f'\r\n\r\n'
# 向目标套接字发送已编码 HTTP 请求报文
client.sendall(message.encode())
# 接受来自 WEB 服务器返回的响应信息
response = client.recv(5120)
# 解码来自 WEB 服务器返回的 HTTP 响应报文
print(response.decode())
执行效果
HTTP/1.1 200 OK
Date: Fri, 16 Jun 2023 06:30:52 GMT
Content-Type: application/json
Content-Length: 307
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"args": {
"content": "RedHeart"
},
"headers": {
"Host": "httpbin.org",
"Useragent": "Python 3.10.6",
"Whoami": "RedHeart",
"X-Amzn-Trace-Id": "Root=1-648c019b-313d5dd72e5bd8dc288be814"
},
"origin": "222.72.145.152",
"url": "http://httpbin.org/get?content=RedHeart"
}
注:
在发送 HTTP 请求时,需要在 HTTP 请求报文中包含 Host
请求头,否则你可能将收到 类似
如下的 HTTP 响应报文
。
HTTP/1.1 400 Bad Request
Server: awselb/2.0
Date: Fri, 16 Jun 2023 07:02:11 GMT
Content-Type: text/html
Content-Length: 122
Connection: close
<html>
<head><title>400 Bad Request</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
</body>
</html>