学习万维网的基础知识是至关重要的,特别是如果你打算建立网络应用。而且,HTTP是你可以建立的任何网络应用的核心。
HTTP是超文本传输协议的缩写。它是一种无状态的应用层协议,用于在分布式系统之间进行通信,是现代网络的基础。
在这篇文章中,我将从基础知识开始。我将解释任何潜在的网络开发人员应该知道的HTTP的各个方面。然后,我们将深入探讨HTTP的更深层次和高级理论。
HTTP是一种超文本传输协议
当你在浏览器中打开一个网站时,你会看到文本、图像和嵌入式内容。所有这些内容都是从网络上其他地方的服务器上加载的。浏览器--也称为客户端--的作用是为这些内容提出请求。该请求被发送到服务器,而服务器则向浏览器发送一个响应。请求和响应都是以人类可读的文本形式发送。

浏览器发出的每个请求都是独立的。HTTP协议是无状态的。这意味着,每个单独的请求都需要携带履行请求所需的所有信息。在HTTP请求中,这些信息是通过头文件 传递的 。
HTTP协议支持混合的网络配置。浏览器是许多可能的客户端之一,它可以提出请求。请求是通过TCP/IP层发送的,而响应是通过TCP/IP层接收的。HTTP通信的默认端口是80端口,但对于不同的应用可以进行不同的配置。
HTTP的版本
- 目前,客户端使用HTTP/2.0版本。最近发布的Chrome、Firefox、Safari和Edge都支持HTTP/2.0。这个版本的HTTP允许客户端同时发送多个请求。 这种技术被称为多路复用。它减少了加载一个页面所需的时间。
- HTTP/1.1是在2014年修订的。这个版本只允许每个TCP/IP连接有一个未完成的请求。这种机制也被称为基线**。**
- HTTP/0.9 是该协议的原始版本。目前,这个版本已经完全废弃了。
下面的流程图解释了为什么HTTP/2.0比HTTP/1.1更好。
正如所见,HTTP/2.0允许客户端同时请求样式和脚本。同样地,样式和脚本也会同时被加载。整个过程在一个单一的TCP连接中完成。
!
此外,HTTP/2.0还具有智能数据包管理策略和头压缩机制,以减少延迟。
HTTP URLs
请求被发送到用统一资源定位器(URLs)指定的服务器。我相信你已经对URLs很熟悉了,但为了完整起见,我还是要把它放在这里。URLs有一个简单的结构,由以下部分组成。
www.domain.com:1234/path/to/res…
- https指定协议。它可以是http或https,这使得通信安全。
- www.domain.com 是主机。
- 1234 是端口。在许多情况下,浏览器会隐藏端口。默认是80。
- path/to/resource 是资源路径。它帮助服务器识别一个特定的资源,并产生正确的响应。
- a=b&x=y 是查询字符串参数。查询字符串参数被服务器用来发现正确的资源。
URL确定了客户想要与之通信的特定主机。它不执行任何行动。这就是请求的来源。HTTP有一个正式的方法来构筑请求,捕获所有需要的信息,这可以被任何种类的应用程序所应用。
HTTP请求
网络通信的核心是请求信息。一个请求是由以下部分组成的。
- 请求行:这说明正在请求什么。由一个动词、一个路径和HTTP版本组成。HTTP动词说的是要求主机采取什么行动,例如,GET资源或POST表单数据。
- 标头:关于消息、请求者和通信格式的额外信息。
- body (可选):请求的内容。对于像网页这样的静态资源的简单请求,它将是空的。对于一个表单的提交,这将包含表单的信息。主体与头文件之间有一个空行。
下面是一个典型的HTTP请求。
GET /articles/http-basics HTTP/1.1
Host: www.articles.com
Connection: keep-alive
Cache-Control: no-cache
Pragma: no-cache
Accept: text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8
第一行有动词、资源路径和HTTP版本。在这种情况下,我们试图获取**/articles/http-basics**的资源。其余的请求行是头文件,这个请求没有正文。
HTTP请求动词
在一个请求中,有四个普遍适用的HTTP动词。
- GET:从服务器上获取一个资源。对于一个GET请求,URL应该携带所有需要的信息,以便服务器能够找到正确的资源。它没有一个信息体。
- POST: 创建一个新的资源。该请求有可选的有效载荷,帮助服务器创建一个新资源。
- PUT: 更新一个现有资源。该请求应该有一个可选的有效载荷,以帮助服务器更新一个现有的资源。
- DELETE: 删除一个现有的资源。
以上四个动词是最流行的动词。有趣的是, PUT 和 DELETE 有时被认为是 POST 动词 的专门化版本 。在某些情况下,PUT 可能被打包成一个 POST 请求,其有效载荷包含确切的动作: 创建、更新或删除。
还有一些不太常用的动词。需要考虑的几个是。
- HEAD与GET类似,但没有信息体。它用于检索特定资源的服务器头,通常是通过时间戳来检查资源是否已经改变。
- TRACE 是用来检索一个请求在从服务器出发的往返过程中所采取的跳数。每个中间代理或网关将把它的IP或DNS名称注入
Viaheader字段中。这可用于诊断目的。 - OPTIONS用于检索服务器的能力。在客户端,它可以用来根据服务器支持的内容来修改请求。
HTTP标头
HTTP头为服务器提供了关于发件人、客户想要的交互方式和消息的信息。每个标头都是一个名-值对。HTTP协议规定了客户端和服务器可以使用的所有有效的HTTP标头。
请求和响应信息中都有一些通用的头信息。
- **
Cache-Control**缓存:一个控制CDN、代理或浏览器中如何进行缓存的指令。它从HTTP/1.1开始生效。 **Connection**网络连接:用于决定一个请求完成后,网络连接是需要关闭还是打开。可能的值是keep-alive,或closed。**Pragma**是一个有趣的,而且在很大程度上是具体实施的头。它的提供只是为了向后兼容HTTP/1.0,后者不支持Cache-Control。Trailer: 告诉服务器它可以将元数据附加到消息正文中,例如完整性检查或数字签名。Transfer-encoding: 定义了从服务器传输的有效载荷的编码。通常,这被称为逐跳 头,因为编码是在节点之间应用,而不是在服务器和客户端之间。Via在头中用于跟踪消息,以及客户端或服务器的能力。Upgrade仅在HTTP/1.1及以上版本中可用。如果允许客户端或服务器从一个协议转变为另一个协议,就必须设置这个头。例如:Upgrade: HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11。
下面是一些特定于请求的头信息。
**Accept-**前缀的头表示客户端可接受的媒体类型、编码、语言和字符集。**From**,Host, **Referer**和User-Agent有关于发起请求的客户端的详细信息。**Authorization**密码:客户端用来提供证书,服务器可以进一步使用这些证书来验证请求。这对于访问受密码保护的资源很有用。**If-**prefixed headers用于使请求有条件,服务器只有在条件符合时才返回资源。否则,它将返回一个304 Not Modified。- **
Referrer**前缀:包含请求页面的部分或绝对地址。
还有许多其他的HTTP标头在使用。如果你想了解HTTP协议的一些错综复杂的问题,请阅读更多关于标头的信息。
HTTP响应
响应在结构上与请求信息相似,除了状态行和头信息。
- 状态行:包括一个状态代码,表明请求是否成功(状态代码200)或请求为何失败。它还包括HTTP版本和对状态的一个非常简短的描述。
- 头信息:关于响应的额外信息。例如,内容类型,或关于服务器的信息。
- body (可选):响应的内容。例如,这可能是请求的网页的HTML内容,或图像的二进制数据。
服务器的成功响应将有一个类似于HTTP/1.1 200 OK 的状态行。
响应标头
就像在请求中一样,响应信息可以有一些标头。下面是一些常用的响应头。
Age:包含请求页面的部分地址或绝对地址。 : 自信息在服务器上生成以来的时间(秒)。ETag:实体的MD5哈希值,用于检查修改情况。LocationURL:在发送重定向时使用,包含新的URL。Server: 识别生成该消息的服务器。
HTTP响应也可以有一个实体头信息的集合 。 实体标头的作用是提供关于消息体的元信息。下面是一些典型的实体头信息。
Allow: 定义了一个资源可能支持的方法集。如果该资源不被支持,你将收到一个状态代码405 Method Not Allowed。**Content-**前缀的头信息表明响应的媒体类型、编码、语言和消息有效载荷上的字符集。例如,Content-Encoding,用于压缩传输的数据。Expires:表示响应将变得无效的日期和时间,因此该资源不应该被缓存超过该点。Last-Modified:携带服务器认为响应资源最后被修改的详细时间。
状态代码
有了这么多的理论概念,你可能觉得有点昏昏欲睡。但请坚持住。我们将以HTTP响应状态码来结束我们的理论。服务器的每个响应都会有一个状态码。 状态码很重要,它告诉客户如何解释服务器的响应
1xx:信息性消息
这类代码是在HTTP/1.1中引入的,纯属临时性质。服务器可以发送一个Expect: 100-continue ,告诉客户端继续发送请求的剩余部分,如果已经发送,则可以忽略。HTTP/1.0客户端应该忽略这个头。
2xx:成功
这告诉客户端,该请求已被成功处理。最常见的代码是 **200 OK**.对于一个GET 请求的响应,服务器会在消息正文中发送资源。
**200 OK**表示请求被成功处理,响应体将包含任何请求的内容。**202 Accepted**:请求被接受,但可能不包括响应中的资源。这对于服务器端的异步处理是很有用的。服务器可以选择发送信息进行监控。- **
204 No Content**响应:响应中没有消息体。 205 Reset Content: 表示给客户端重置其文档视图。206 Partial Content:表示响应中只包含部分内容。附加头信息表明确切的范围和内容过期信息。
3xx:重定向
这要求客户端采取额外的行动。最常见的使用情况是跳转到一个不同的URL以获取资源。
301 Moved Permanently:资源现在位于一个新的URL上。303 See Other:该资源暂时位于一个新的URL上。Location响应头包含临时URL。304 Not Modified:服务器已经确定该资源没有改变,客户应该使用其缓存的副本。这依赖于这样一个事实,即客户端正在发送ETag(实体标签)信息,这是内容的哈希值。服务器将其与自己计算的ETag进行比较,以检查是否有修改。
4xx: 客户端错误
这些代码是在服务器认为客户端有错误时使用的,要么是请求了一个无效的资源,要么是提出了一个错误的请求。这类代码中最常见的是 **404 Not Found**,我想每个人都会认识到这一点。
400 Bad Request: 该请求是畸形的。401 Unauthorized:请求需要认证。客户端可以用Authorization标头来重复该请求。如果客户端已经包含了Authorization标头,那么凭证是错误的。403 Forbidden: 服务器已拒绝访问该资源。404 Not Found: 表示该资源是无效的,不存在于服务器上。该类中的其他代码包括。405 Method Not Allowed:请求行中使用的HTTP动词无效,或者服务器不支持该动词。409 Conflict:服务器无法完成请求,因为客户试图修改一个比客户的时间戳更新的资源。冲突主要出现在对资源进行协作编辑时的PUT请求。
5xx: 服务器错误
这类代码用于表示在处理请求时服务器出现故障。最常用的错误代码是。
500 Internal Server Error:服务器发生了某种崩溃或内部错误,使其无法完成请求。501 Not Implemented:服务器还不支持请求的功能。503 Service Unavailable:如果服务器上的一个内部系统出现故障或服务器过载,就可能发生这种情况。通常情况下,服务器甚至不会响应,请求会超时。
在网络框架和库中使用HTTP
最后,让我们看看框架是如何使用请求-响应对的。作为一个例子,让我们从NodeJS的ExpressJS开始 。
用Express接收HTTP请求
如果你在NodeJS中构建Web服务器,你很有可能已经考虑过 Express。
Express提供了一个简单的API来编写Web服务器。我将不会介绍API的细节。相反,我将向你展示一个关于Express如何使用HTTP请求和响应的例子。
Express Hello World
const app = express()
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.get('*', (req, res) => {
res.sendStatus(401);
})
app.listen(3000, () => {
console.log(`Example app listening on port ${port}`)
})
在这个例子中,我们为一个路径为 / 的 GET 请求设置了一个处理程序。当服务器收到这个请求时,它将以200 OK 来回应,信息体将是Hello World。服务器将监听3000端口。对于任何其他路径的GET请求,服务器将返回一个401 Not Found 错误。
用Fetch API发送一个HTTP请求
最后,让我们看看如何从客户端发出一个HTTP请求。这曾经是通过jQuery完成的,但现代浏览器已经内置了Fetch API。而且,语法也没有变得更简单。
使用Fetch的GET请求
要从服务器上请求数据,使用fetch("https://example.com/something.html") 。这将返回一个Promise,你可以通过await 来获得响应。
let response = await fetch('https://example.com/movies.json')
let data = await response.json();
console.log(data);
在上面的例子中,我们向**example.com**发送了一个关于**movies.json**资源的 GET 请求。我们等待响应,然后提取响应体作为JSON。
使用Fetch的POST请求
为了将数据发布到服务器上,并带有可选的有效载荷,客户端使用了:fetch('https://example.com/profile', { method: 'POST'}) 。同样,它返回一个Promise,你可以等待它来获得请求的结果。
fetch('https://example.com/profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.catch((error) => {
console.error('Error:', error);
});
在这个例子中,我们向example.com.We 的profile 端点提交了一个post请求,希望向服务器发送JSON数据,所以我们将Content-Type 头部设置为application/json 。然后我们将数据字符串化为JSON并在请求正文中发送。
总结
恭喜你,你现在已经掌握了HTTP请求和响应的基础知识现在,你将能够在你的应用程序中使用该协议。此外,你应该能够为你的使用案例选择正确的HTTP动词和头信息。