HTTP 被称为无状态协议,是因为它在不同的请求之间并不保留任何状态信息。每个 HTTP 请求和响应都是独立的,服务器不会记住之前的请求或会话信息。
这是由于 HTTP 的无连接性的特性所决定的。每当客户端发送一个请求给服务器,服务器会根据请求进行处理并发送响应,完成后立即断开连接。在下一个请求中,服务器无法知道上一个请求的信息,也无法获取客户端的状态。
为了维护应用程序或用户状态,通常会使用一些机制实现会话管理,比如使用 Cookie 或者在请求中使用特定的标识符来标记用户会话。这样服务器可以根据这些标识符来识别用户和会话状态。
尽管 HTTP 是无状态的协议,但通过使用会话管理等技术,可以实现状态的保持和管理,使得 HTTP 在实际应用中能够处理复杂的交互和用户认证等需求。
HTTP 报文结构分为请求报文和响应报文两种类型。
请求报文的结构:
<方法> <请求目标>
<请求头字段 1>: <值 1>
<请求头字段 2>: <值 2>
...
<空行>
<请求主体>
其中,<方法>
表示请求方法(例如 GET、POST、PUT 等),<请求目标>
表示请求的目标 URL,
表示使用的 HTTP 版本号。请求头字段和值由一行一行组成,在空行之前。请求主体是可选的,用于包含请求的数据。
响应报文的结构:
<状态码> <原因短语>
<响应头字段 1>: <值 1>
<响应头字段 2>: <值 2>
...
<空行>
<响应主体>
其中,
表示响应所使用的 HTTP 版本号,<状态码>
表示服务器对请求的处理结果,<原因短语>
是对状态码的简要描述。响应头字段和值由一行一行组成,在空行之前。响应主体也是可选的,用于包含响应的数据。
请求报文和响应报文都采用了 ASCII 文本格式,通过 CRLF(回车换行)来分隔行,空行用于分隔头部和主体。这种简单的结构使得 HTTP 报文易于解析和组装。
在HTTP/1.1中,队头阻塞(Head-of-Line Blocking)是指在一个TCP连接上的请求和响应是按照顺序进行的,如果前面的请求由于某种原因被阻塞,那么后面的请求也会受到影响,导致整体性能下降。
为了解决HTTP的队头阻塞问题,可以采取以下策略:
Connection: keep-alive
字段,保持TCP连接的持久性,避免重复建立和关闭连接的开销。Accept-Encoding
字段,支持服务器对响应进行压缩,减小传输内容大小,提高传输速度。需要注意的是,尽管这些策略可以减轻HTTP的队头阻塞问题,但并不能完全解决。HTTP/2和HTTP/3协议在协议层面上引入了新的特性,如多路复用、头部压缩
等,更好地解决了队头阻塞问题。因此,在新的项目中优先考虑使用HTTP/2或HTTP/3以获取更好的性能表现。
在 http 中,有两种主要的表单提交的方式,体现在两种不同的Content-Type取值:
application/x-www-form-urlencoded
multipart/form-data
由于表单提交一般是POST请求,很少考虑GET,因此这里我们将默认提交的数据放在请求体中。
在 HTTP 中,表单数据的提交通常使用 POST 请求来完成。有几种常见的方式来提交表单数据:
表单的 method
属性设置为 post
,并将表单的 action
属性指向接收表单数据的服务端接口。提交表单时,浏览器会按照表单字段的名称和值构建一个键值对的数据结构,并将其作为请求体发送给服务端。
使用 AJAX 技术提交表单数据。可以使用库或原生 JavaScript 的 XMLHttpRequest
对象来发送 POST 请求,将表单字段构建的键值对数据作为请求体发送到服务端。
使用 Fetch API 来发送 POST 请求。可以使用 fetch
函数发送 POST 请求,并将表单字段构建的键值对数据作为请求体发送给服务端。
无论使用何种方式,服务端都需要接收请求,并解析请求体中的表单数据。服务端可以使用各种编程语言和框架来解析表单数据,常用的包括 PHP 的 $_POST
变量、Java 的 Servlet、Node.js 的 Express 等。
需要注意的是,为了防止跨站请求伪造(CSRF)攻击,通常需要在表单中添加一个令牌(CSRF token),并在服务端验证该令牌的有效性。这可以防止来自其他网站的恶意请求。
总而言之,表单数据的提交是通过发送 POST 请求,并将表单字段构建的键值对数据作为请求体发送给服务端来实现的。
application/x-www-form-urlencoded
和 multipart/form-data
是两种常见的用于在 HTTP 请求中传输表单数据的 Content-Type。
application/x-www-form-urlencoded
其中的数据会被编码成以&分隔的键值对
字符以URL编码方式编码。
这是默认的表单提交方式。在这种格式下,表单数据被编码为键值对的格式,并使用 &
符号分隔。字段名和字段值都需要进行 URL 编码,即对特殊字符进行替换。例如,一个含有两个字段的表单:
name=John+Doe&age=30
这种格式适合简单的表单数据,并且数据量较小。
multipart/form-data
请求头中的 Content-Type 字段会包含 boundary ,且 boundary 的值有浏览器默认指定。
例: Content-Type: multipart/form-data;boundary=----WebkitFormBoundaryRRJKeWfHPGrS4LKe。
数据会分为多个部分,每两个部分之间通过分隔符来分隔,每部分表述均有 HTTP 头部描述子包体,
如Content-Type,在最后的分隔符会加上--表示结束。
这种格式适用于上传文件或提交包含大量字段和较大数据量的表单。在这种格式下,表单数据被划分为多个部分,每个部分对应一个字段或文件。每个部分都有一个自己的头部信息,包含字段名、文件名等。每个部分以 boundary
分隔符作为边界,不同字段的数据之间使用 boundary
进行划分。一般情况下,在请求头中会包含 Content-Type: multipart/form-data; boundary=xxxxx
,其中 xxxxx
是一个随机生成的字符串。
这种格式在数据传输时比较灵活,可以支持多个文件上传、二进制数据等。但由于每个部分都需要有自己的头部信息,所以相对于 application/x-www-form-urlencoded
格式会产生更多的额外数据量。
需要根据具体的需求选择合适的格式。如果只是简单的表单数据提交,可以使用 application/x-www-form-urlencoded
。如果需要上传文件或传输大量的字段和数据,那么就需要使用 multipart/form-data
格式。
对于定长数据,HTTP 使用 Content-Length
头字段来指定消息体的长度。服务器在发送响应时会包含 Content-Length 头字段,客户端在接收响应时会根据这个长度来准确地读取响应的内容。
对于不定长数据,HTTP 使用 Transfer-Encoding
头字段来指定传输编码。常见的传输编码有 chunked 编码和 gzip 编码。在 chunked 编码中,响应的消息体被分割为多个块,每个块都包含一个长度前缀和实际的数据,最后一个块的长度为0。这样,客户端在接收响应时可以逐块读取数据,而不需要等到整个消息体都传输完毕。
对于 gzip 编码,服务器会对响应的消息体进行压缩,然后将压缩后的数据通过流式传输发送给客户端。客户端接收到压缩后的数据后,会对其进行解压缩,得到原始的消息体。
不论是定长数据还是不定长数据,HTTP 都是通过 TCP 连接进行传输的。