HTTP 通信的工作原理
在深入探讨 HTTP 头部的定义之前,我认为至少对 HTTP 协议的工作原理有一个大致了解是非常重要的。相信大家已经知道,超文本传输协议(HTTP)是当今万维网的基础。从宏观层面来看,它允许信息在服务器与其客户端之间进行传输。这种信息交换的实际流程大致如下:
- 客户端建立一个新的 TCP 连接
- 它向服务器发送一条消息,这被称为 HTTP 请求
- 服务器接收并解析请求
- 然后,它会向客户端发送一条消息,这被称为 HTTP 响应
- 客户端读取消息后,继续连接或关闭连接

今天我们将重点探讨的核心内容是消息,尤其是客户端发送的消息。为了确保服务器与客户端之间的通信高效进行,消息的格式必须符合HTTP协议的规定。就HTTP请求而言,构成消息的要素包括:
该方法描述了我们希望通过请求执行什么操作(例如接收、发送或更新信息等)。
- 路径,即我们要访问的 URL 地址
- 我们正在使用的 HTTP 协议版本
- HTTP 头部用于在请求中附加额外信息和元数据
- 如果我们使用的是向服务器发送信息的方法(例如 POST 请求),则需要包含请求主体
什么是 Axios 中的 HTTP 头部,它们是如何工作的?
简而言之,HTTP 头部是用于向消息传递附加信息和元数据的字段。 这里所说的“消息”,指的是客户端发送的请求以及服务器发送的响应。因此,服务器和客户端都可以发送和接收头部。例如,假设你想与服务器建立持久连接。默认情况下,HTTP 连接会在每次请求后关闭。要避免这种情况,你只需发送 `Keep-Alive` 头部即可。
说到 Axios 中的 HTTP 头部,其实并没有什么特别之处。正如我们之前讨论过的,Axios 是一个 HTTP 客户端,而我们已经知道,HTTP 客户端可以发送和接收头部。
为什么 Axios 是 JavaScript 的优秀 HTTP 客户端
既然我们已经大致了解了 HTTP 的工作原理,接下来就来谈谈“客户端”。JavaScript 提供了几种通过 HTTP 与服务器进行“程序化交互”的选项。其中最受欢迎的包括 `axios`、`node-fetch` 和 `got`。
在 JavaScript 社区中,对于该选用哪一款存在不同看法。当然,每个包都有其优缺点,但我自己在对这三者进行了一次简单的速度测试后,最终选择了 Axios。
以下是我用来测试速度的脚本:
// index.js
import { get_timing, array_sum } from './helpers.js'
import got from 'got'
import axios from 'axios'
const CALLS = 5
const send = async () => {
const res = {}
let start = process.hrtime()
await got('https://httpbin.org/')
const g = get_timing(start)
res.got = g
start = process.hrtime()
await axios.get('https://httpbin.org/')
const a = get_timing(start)
res.axios = a
start = process.hrtime()
await fetch('https://httpbin.org/')
const f = get_timing(start)
res.fetch = f
return res
}
let test_results = {
got: [],
axios: [],
fetch: []
}
let avg = {}
console.log(`[i] Process started with ${CALLS} iterations.`)
for (let i = 0; i<=CALLS; i++) {
let r = await send()
Object.entries(test_results).map(([key, value]) => test_results[key].push(r[key]))
}
Object.entries(test_results).forEach(([key, value]) => {
console.log(`\n[+] ${key}`)
console.log(` [i] Average: ${array_sum(value)/value.length}`)
console.log(` [i] Values: ${value}`)
avg[key] = array_sum(value)/value.length
}
)
console.log(`\n🚀🚀🚀 WINNER: ${Object.keys(avg).reduce((key, v) => avg[v] < avg[key] ? v : key)} [${CALLS} calls sent] 🚀🚀🚀`)
以下是 `helper` 函数:
// helpers.js
export const get_timing = (start) => {
const NS_PER_SEC = 1e9
const NS_TO_MS = 1e6
const diff = process.hrtime(start)
return (diff[0] * NS_PER_SEC + diff[1]) / NS_TO_MS
}
export const array_sum = (array) => {
return array.reduce((accumulator, value) => {
return accumulator + value
}, 0)
}
我分别测试了每个数据包发送 5、10、20 和 30 个请求,每次迭代 10 次,以下是结果概览:

这里所说的“迭代”,指的是脚本的执行次数。使用以下 Bash 命令,会生成一个 .txt 文件,其中包含每次迭代的结果:
~ » for i in {1..10}
do
node got.js > "${i}.txt"
echo "${i} 完成"
done
如您查看详细结果表所见,每个批次的耗时各不相同,有时 Axios 并非最快。 不过总体而言,Axios 的平均响应时间为 387 毫秒,比其他竞争对手快了半秒。Got 和 Fetch 的响应时间非常接近,平均约为 435 毫秒。话虽如此,如果速度对您的项目至关重要,Axios 或许是您最理想的 HTTP 客户端。
如何使用 Axios 发送 HTTP 头部
我个人认为,通过实践学习几乎能立竿见影。既然我们已经掌握了发送 HTTP 头所需的知识和工具,那就开始动手做一个小项目吧。在本节中,我们将创建一个新的 Node 项目,安装 Axios,并利用它向服务器发送 HTTP 头。
设置项目
在继续操作之前,请确保您的机器已配备以下设备:
- Node.js——您可以从这里下载
- 一个集成开发环境(IDE)——我日常使用的 IDE 是Visual Studio Code
提示:您可以在终端中输入以下命令,检查是否已安装 Node.js:
~ » node -v
v19.3.0
现在,让我们创建一个新文件夹,并在 IDE 中打开它。如果你使用的是类 UNIX 系统(Mac 或 Linux),可以在终端中输入以下命令,通过编程方式创建一个新目录:
~ » mkdir axios_project && cd axios_project
~ » npm init -y
~ » npm i axios
~ » touch index.js
~ » code .
这些命令将:
- 创建一个新目录(命名为“axios_project”),然后进入该目录
- 在该目录内初始化一个新的 Node.js 项目
- 在项目中安装 `axios`
- 创建一个新的“index.js”文件
- 在当前项目中打开您的 IDE
编程学习
实际上,使用 Axios 发送 HTTP 头部有几种方法。例如,你可以按照此处的说明使用配置对象,或者使用实例方法,这些方法会自动将你传入的配置与实例配置合并。你还可以使用 `axios.defaults.headers.common` 对象为所有 Axios 请求设置默认头部。
另外,请注意 Axios 是一个基于 Promise 的 HTTP 客户端。这意味着我们必须在异步函数中等待其完成,或者手动解析响应。
考虑到这两个方面,让我们开始实际编写代码吧。我们将要在“index.js”文件中进行操作。为了方便起见,让我们先回顾一下需要做些什么:
- 在文件中导入 `axios`
- 定义一个用于存储请求头的配置对象
- 将配置传递给 `axios` 以发起请求
- 在终端中打印响应
#1:使用 config 对象发送一个 GET 请求
import axios from "axios"
const config = {
method: 'GET',
url: 'https://httpbin.org/headers',
headers: {
'HTTP-Axios-Headers': 'This is my custom header.'
}
}
axios(config)
.then((response) => {
console.log(response)
})
.catch((err) => {
console.log(err)
})
使用 Axios 发送 HTTP 头部再简单不过了。要执行此脚本,只需在终端中运行以下命令:
~ » node index.js
{
status: 200,
statusText: 'OK',
headers: ...,
config: ...,
request: ...,
data: {
headers: {
'Accept': 'application/json, text/plain, */*',
'Accept-Encoding': 'gzip, compress, deflate, br',
'Host': 'httpbin.org',
'Http-Axios-Headers': 'This is my custom header.',
'User-Agent': 'axios/1.2.2',
'X-Amzn-Trace-Id': 'Root=1-63b54d94-7656f02113483dfa036c476c'
}
}
}
整个响应数据量较大,且遵循以下结构。不过,我们最关注的是 `data` 字段,它包含了从服务器接收到的实际响应。现在请查看上方的响应。请记住,我们向服务器发送了一个自定义标头 `Http-Axios-Headers`,如您所见,服务器确实接收到了该标头。
#2:使用方法别名发送 POST 请求
import axios from "axios"
const data = {
'foo':'bar'
}
const config = {
headers: {
'HTTP-Axios-Headers': 'This is my custom header.'
}
}
axios.post('http://httpbin.org/post', data, config)
.then(response => console.log(response.data))
.catch(err => console.log(err))
请注意,为了发送 POST 请求,我在脚本中添加了一个新的 `data` 对象,并且修改了 URL。现在,如果你运行该脚本,你会发现从服务器接收到的数据如下:
{
args: {},
data: '{"foo":"bar"}',
files: {},
form: {},
headers: {
Accept: 'application/json, text/plain, */*',
'Accept-Encoding': 'gzip, compress, deflate, br',
'Content-Length': '13',
'Content-Type': 'application/json',
Host: 'httpbin.org',
'Http-Axios-Headers': 'This is my custom header.',
'User-Agent': 'axios/1.2.2',
'X-Amzn-Trace-Id': 'Root=1-63b5508a-3a86493f087662d3169e80ee'
},
json: { foo: 'bar' },
origin: '49.12.221.20',
url: 'http://httpbin.org/post'
}如何在 Axios 中使用 HTTP 头部进行网页抓取
如果您计划使用 Axios 进行网页抓取,请注意,大多数网站都设有防护规则,会拦截来自自动化软件(包括网页抓取工具)的请求。
在进行网页抓取时,利用 HTTP 头部(尤其是 `User-Agent` 头部)是一种有效的规避检测的方法。User-Agent 头部向服务器标识客户端的浏览器和操作系统,而 Web 服务器可能会根据这些信息提供不同的内容或阻止请求。通过设置 User-Agent 头部,您可以模拟常见的 Web 浏览器,从而增加绕过某些检测机制的可能性。
以下是一个示例,展示了如何在进行网页抓取时,利用 Axios 设置 User-Agent 头部来避免被检测:
import axios from "axios"
axios.defaults.headers.common['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
axios({
method: 'GET',
url: 'https://httpbin.org/get',
}).then(response => {
console.log(response.data)
});
请注意,这次我使用了 Axios 的config defaults选项,该选项会将该头部信息应用于所有后续请求。在此示例中,User-Agent 头部被设置为模拟运行于 Windows 10 操作系统上的 Chrome 浏览器。您可以尝试使用不同的 User-Agent 值或多种不同的头部信息,以找出最适合您具体使用场景的方案。
值得注意的是,虽然修改 User-Agent 头部在某些情况下可能有助于避免被检测,但这并非万无一失的方法,Web 服务器仍可能识别出您正在使用网络爬虫。因此,建议结合多种技术手段来避免被检测,并遵守网站的服务条款。




