HttpRequest 和 XMLHttpRequest 之间的真正区别
True difference between HttpRequest and XMLHttpRequest
阅读前注意
这不是 的副本
对于信息,我尝试了 this lib 但没有成功,因为它复制了 XMLHttpRequest 的结构但实际上并不像它那样。
我想知道来自 Node 的 HttpRequest
和来自浏览器的 XMLHttpRequest
之间真正的网络区别是什么。
如果我只查看 chrome 的开发工具中的 XMLHttpRequest,我在请求中看不到任何 X-Requested-with
header。
此外,CloudFlare 的 WAF 后面还有一个在线服务,带有自定义规则。如果我用 XMLHttpRequest
发出请求,它就可以工作,但我用 https.request
发出请求,它无法被 CF 防火墙保护。
我需要使用 HttpRequest
来配置代理。
两者之间的网络有什么区别,我如何从 HttpRequest 模拟 XMLHttpRequest ?这可能吗?
我查看了铬的来源 here,但找不到任何有趣的东西。
也许它与 IO 层不同? TCP 握手 ?
需要建议。谢谢
编辑
这是 XMLHttpRequest(工作)
let req = new XMLHttpRequest();
req.open("post", "https://haapi.ankama.com/json/Ankama/v2/Api/CreateApiKey", true);
req.withCredentials = true;
req.setRequestHeader('Accept', 'application/json');
req.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
req.setRequestHeader('Accept-Encoding', 'gzip, deflate, br');
req.onload = function() {
console.log(req.response)
};
req.send("login=smallladybug949&password=Tl9HDKWjusopMWy&long_life_token=true");
与 cURL 相同(不通过 CF 的防火墙)
curl 'URL' \
-H 'origin: null' \
-H 'accept-encoding: gzip, deflate, br' \
-H 'user-agent: Mozilla/5.0 (Linux; Android 6.0.1; Z988 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3497.100 Mobile Safari/537.36' \
-H 'content-type: text/plain;charset=UTF-8' \
-H 'accept: application/json' \
-H 'authority: URL.com' \
--data-binary 'login=123&password=def' \
--compressed
这里是 HttpRequest(没有通过 CF 的防火墙)
let opts = url.parse(URL);
opts.method = post;
opts.headers = {
'Accept': 'application/json',
'Content-Type': 'text/plain;charset=UTF-8',
'Accept-Encoding': 'gzip, deflate, br',
'User-Agent': 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36'
}
let req = https.request(opts, function (res) {
res.setEncoding('utf8');
res.body = "";
res.on('data', (chunk) => {
res.body += chunk;
});
res.on('end', (chunk) => {
try {
res.body = JSON.parse(res.body);
} catch (e) {
return reject(res.body); // error, http 403 / 1020 error from CF (custom FW rule)
}
console.log(res.body); // we'll not reach this
});
});
req.on('error', e => {
console.error('error', e);
});
req.write("login=abc&password=def");
req.end();
编辑 2
经过几次测试,curl 命令可以用,XHR 也可以用,但是用 Postman 或 HttpRequest 就失败了。
这是邮递员 vs curl 的视频:https://streamable.com/81s57
视频中的curl命令是这个:
curl -X POST \
https://haapi.ankama.com/json/Ankama/v2/Api/CreateApiKey \
-H 'accept: application/json' \
-H 'accept-encoding: gzip, deflate, br' \
-H 'accept-language: fr' \
-H 'authority: haapi.ankama.com' \
-H 'content-type: text/plain;charset=UTF-8' \
-H 'origin: null' \
-H 'user-agent: Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36' \
-d 'login=smallladybug949&password=Tl9HDKWjusopMWy&long_life_token=true'
(这是一个测试帐户,所以我不需要它,你可以用它进行测试)。您可以将 --compressed
标志添加到 curl 请求以将其解压缩或将其通过管道传输到 gunzip
.
编辑 3(最终)
我发现这是由于误用了(对于 CF)TLS 协议。通过降级使用 OpenSSL/1.1.0f 的 curl,调用就可以正常工作。但自从 OpenSSL/1.1.0g 他们没有。
您可以阅读有关 OpenSSL 变更日志的更多信息 here
我假设 curl 和节点 HttpRequest 缺少有效的 origin
header。 XMLHttpRequest 使用浏览器引擎,因此发送和验证 cross-origin-policy 并指定那些 header。
这用于防止网站访问 API-endpoints 不属于它们的其他网站。又名网络管理员可以指定 origin-domains,它可以与您的 API 通信。所有 http-rest-requests 浏览器实现都发送并验证来源 header。
Curl 和 HttpRequest 不是浏览器/网站的技术。看看 CORS
、Same-origin-policy
和 origin-header
。我想这会澄清问题。
正如我在评论中讨论的那样,我可以重现:第一个 "Edit" 中的 XMLHttpRequest 示例有效(HTTP 状态 = 200),而它的 "copy as cURL" 版本 returns来自 Cloudfare 的 403。
将 --cert-status
添加到 curl 使其对我有用,因此 Cloudfare 在决定拒绝请求时似乎会分析 TLS-level 通信。
第一次编辑中的 curl 命令与我使用 "Copy as cURL":
时得到的版本有一些其他差异
curl 'URL'
而不是 https://haapi.ankama.com/json/Ankama/v2/Api/CreateApiKey
显然失败了,请不要让重现结果变得更难。
-H 'origin: null'
vs -H 'Origin: https://localhost:4443' -H 'Referer: https://localhost:4443/test_http.html'
- 这没有区别。
- 我还有一些其他的 header
-H 'DNT: 1' -H 'Connection: keep-alive' -H 'Cookie: __cfduid=dcf1b80eef19562054c9b64f79139509e1566138746'
也没有什么区别。
- 变化
-H 'user-agent:
- 也不影响 Cloudfare
- 您有一个额外的
-H 'authority: URL.com'
(用占位符代替真实域),这也没有什么区别。
- POST数据是否正确
--data-binary 'login=123&password=def'
只影响API结果;不影响 403.
- 缺少
-H 'Accept-Language:
header 导致 Cloudfare 的 403。
所以您可以尝试将缺少的 Accept-Language
添加到 Node 版本中,看看是否有帮助。
我的 Node 版本没有在 TLS Client Hello 中发送 Extension: status_request
(这似乎是有或没有 --cert-status
的 curl 调用之间的区别),我不知道如何你会启用它。在这一点上,如果可能的话,我会尝试联系支持人员,或者回退到从节点调用 curl。
P.S。在调试它时,我试图比较 curl 与浏览器的 Wireshark 捕获(节点不支持 SSLKEYLOGFILE
、forcing you to jump through hoops, so I didn't even try checking how its capture looks)。细微差别如此之多,以至于尝试对 Cloudfare 使用的规则进行逆向工程非常 time-consuming。 --cert-status
是一个幸运的猜测。
阅读前注意
这不是 的副本 对于信息,我尝试了 this lib 但没有成功,因为它复制了 XMLHttpRequest 的结构但实际上并不像它那样。
我想知道来自 Node 的 HttpRequest
和来自浏览器的 XMLHttpRequest
之间真正的网络区别是什么。
如果我只查看 chrome 的开发工具中的 XMLHttpRequest,我在请求中看不到任何 X-Requested-with
header。
此外,CloudFlare 的 WAF 后面还有一个在线服务,带有自定义规则。如果我用 XMLHttpRequest
发出请求,它就可以工作,但我用 https.request
发出请求,它无法被 CF 防火墙保护。
我需要使用 HttpRequest
来配置代理。
两者之间的网络有什么区别,我如何从 HttpRequest 模拟 XMLHttpRequest ?这可能吗? 我查看了铬的来源 here,但找不到任何有趣的东西。
也许它与 IO 层不同? TCP 握手 ?
需要建议。谢谢
编辑
这是 XMLHttpRequest(工作)
let req = new XMLHttpRequest();
req.open("post", "https://haapi.ankama.com/json/Ankama/v2/Api/CreateApiKey", true);
req.withCredentials = true;
req.setRequestHeader('Accept', 'application/json');
req.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
req.setRequestHeader('Accept-Encoding', 'gzip, deflate, br');
req.onload = function() {
console.log(req.response)
};
req.send("login=smallladybug949&password=Tl9HDKWjusopMWy&long_life_token=true");
与 cURL 相同(不通过 CF 的防火墙)
curl 'URL' \
-H 'origin: null' \
-H 'accept-encoding: gzip, deflate, br' \
-H 'user-agent: Mozilla/5.0 (Linux; Android 6.0.1; Z988 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/69.0.3497.100 Mobile Safari/537.36' \
-H 'content-type: text/plain;charset=UTF-8' \
-H 'accept: application/json' \
-H 'authority: URL.com' \
--data-binary 'login=123&password=def' \
--compressed
这里是 HttpRequest(没有通过 CF 的防火墙)
let opts = url.parse(URL);
opts.method = post;
opts.headers = {
'Accept': 'application/json',
'Content-Type': 'text/plain;charset=UTF-8',
'Accept-Encoding': 'gzip, deflate, br',
'User-Agent': 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36'
}
let req = https.request(opts, function (res) {
res.setEncoding('utf8');
res.body = "";
res.on('data', (chunk) => {
res.body += chunk;
});
res.on('end', (chunk) => {
try {
res.body = JSON.parse(res.body);
} catch (e) {
return reject(res.body); // error, http 403 / 1020 error from CF (custom FW rule)
}
console.log(res.body); // we'll not reach this
});
});
req.on('error', e => {
console.error('error', e);
});
req.write("login=abc&password=def");
req.end();
编辑 2
经过几次测试,curl 命令可以用,XHR 也可以用,但是用 Postman 或 HttpRequest 就失败了。 这是邮递员 vs curl 的视频:https://streamable.com/81s57 视频中的curl命令是这个:
curl -X POST \
https://haapi.ankama.com/json/Ankama/v2/Api/CreateApiKey \
-H 'accept: application/json' \
-H 'accept-encoding: gzip, deflate, br' \
-H 'accept-language: fr' \
-H 'authority: haapi.ankama.com' \
-H 'content-type: text/plain;charset=UTF-8' \
-H 'origin: null' \
-H 'user-agent: Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36' \
-d 'login=smallladybug949&password=Tl9HDKWjusopMWy&long_life_token=true'
(这是一个测试帐户,所以我不需要它,你可以用它进行测试)。您可以将 --compressed
标志添加到 curl 请求以将其解压缩或将其通过管道传输到 gunzip
.
编辑 3(最终)
我发现这是由于误用了(对于 CF)TLS 协议。通过降级使用 OpenSSL/1.1.0f 的 curl,调用就可以正常工作。但自从 OpenSSL/1.1.0g 他们没有。 您可以阅读有关 OpenSSL 变更日志的更多信息 here
我假设 curl 和节点 HttpRequest 缺少有效的 origin
header。 XMLHttpRequest 使用浏览器引擎,因此发送和验证 cross-origin-policy 并指定那些 header。
这用于防止网站访问 API-endpoints 不属于它们的其他网站。又名网络管理员可以指定 origin-domains,它可以与您的 API 通信。所有 http-rest-requests 浏览器实现都发送并验证来源 header。
Curl 和 HttpRequest 不是浏览器/网站的技术。看看 CORS
、Same-origin-policy
和 origin-header
。我想这会澄清问题。
正如我在评论中讨论的那样,我可以重现:第一个 "Edit" 中的 XMLHttpRequest 示例有效(HTTP 状态 = 200),而它的 "copy as cURL" 版本 returns来自 Cloudfare 的 403。
将 --cert-status
添加到 curl 使其对我有用,因此 Cloudfare 在决定拒绝请求时似乎会分析 TLS-level 通信。
第一次编辑中的 curl 命令与我使用 "Copy as cURL":
时得到的版本有一些其他差异curl 'URL'
而不是https://haapi.ankama.com/json/Ankama/v2/Api/CreateApiKey
显然失败了,请不要让重现结果变得更难。-H 'origin: null'
vs-H 'Origin: https://localhost:4443' -H 'Referer: https://localhost:4443/test_http.html'
- 这没有区别。- 我还有一些其他的 header
-H 'DNT: 1' -H 'Connection: keep-alive' -H 'Cookie: __cfduid=dcf1b80eef19562054c9b64f79139509e1566138746'
也没有什么区别。 - 变化
-H 'user-agent:
- 也不影响 Cloudfare - 您有一个额外的
-H 'authority: URL.com'
(用占位符代替真实域),这也没有什么区别。 - POST数据是否正确
--data-binary 'login=123&password=def'
只影响API结果;不影响 403. - 缺少
-H 'Accept-Language:
header 导致 Cloudfare 的 403。
所以您可以尝试将缺少的 Accept-Language
添加到 Node 版本中,看看是否有帮助。
我的 Node 版本没有在 TLS Client Hello 中发送 Extension: status_request
(这似乎是有或没有 --cert-status
的 curl 调用之间的区别),我不知道如何你会启用它。在这一点上,如果可能的话,我会尝试联系支持人员,或者回退到从节点调用 curl。
P.S。在调试它时,我试图比较 curl 与浏览器的 Wireshark 捕获(节点不支持 SSLKEYLOGFILE
、forcing you to jump through hoops, so I didn't even try checking how its capture looks)。细微差别如此之多,以至于尝试对 Cloudfare 使用的规则进行逆向工程非常 time-consuming。 --cert-status
是一个幸运的猜测。