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 不是浏览器/网站的技术。看看 CORSSame-origin-policyorigin-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 捕获(节点不支持 SSLKEYLOGFILEforcing you to jump through hoops, so I didn't even try checking how its capture looks)。细微差别如此之多,以至于尝试对 Cloudfare 使用的规则进行逆向工程非常 time-consuming。 --cert-status 是一个幸运的猜测。

Firefox/curl/node 中的 SSL Client Hello 非常不同: 火狐 卷曲 node11