JavaScript的用户代理(Ajax)请求网站时与发送的用户代理不同

26
我发现Chrome(64.0.3282.137)在我的手机上(OnePlus 3,Android 8.0.0)请求网页和通过ajax请求时发送的用户代理略有不同。
请求网页时发送的用户代理为: Mozilla/5.0 (Linux; Android 8.0.0; ONEPLUS A3003 Build/OPR6.170623.013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36 进行ajax调用时发送的用户代理也会返回到调用navigator.userAgent中: Mozilla/5.0 (Linux; Android 8.0.0; Build/OPR6.170623.013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36 区别: ONEPLUS A3003 请问为什么模型包含在本地调用中,但不包括在ajax调用中?
附加信息:启用“请求桌面站点”功能后,在两种情况下用户代理都是Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Safari/537.36

@MichaelSmale 应用详情显示它是通过 Play Store 安装的。 - SpazzMarticus
这种情况是在所有的ajax请求中都发生了吗(不同的网站)?如果没有,那么可能是该网站编辑了用户代理字符串。 - floor
1
@floor 我在我的一个页面上注意到了这个问题,然后使用了一个空白页面和一个普通的Apache/PHP服务器来复制这个结果。 - SpazzMarticus
问题具体在哪里?你用什么来发起 Ajax 请求?你使用的是任何库还是纯旧版 JavaScript?还有,你使用的是哪个浏览器? - floverdevel
1
我认为这只发生在一加设备上。https://stackoverflow.com/questions/48596702/oneplus-chrome-different-user-agent-on-ajax https://forums.oneplus.net/threads/chrome-user-agent-is-different-when-making-an-post-request.691815/ - Arpan Sarkar
显示剩余8条评论
2个回答

6
我分析了Chromium源代码以获取一些见解。由于我的C++技能尚浅,因此只能达到某个水平。
在这段代码块中检测客户端或平台的用户代理(文件:useragent.cc)。
std::string BuildUserAgentFromProduct(const std::string& product) {
  std::string os_info;
  base::StringAppendF(
      &os_info,
      "%s%s",
      getUserAgentPlatform().c_str(),
      BuildOSCpuInfo().c_str());
  return BuildUserAgentFromOSAndProduct(os_info, product);
}

你可以在代码块中看到BuildOSCpuInfo(),它负责根据平台添加操作系统相关信息,这些信息可以在此处找到。
std::string android_build_codename = base::SysInfo::GetAndroidBuildCodename();
std::string android_device_name = base::SysInfo::HardwareModelName(); // this line in particular adds the ONEPLUS A3003

但是这个函数(BuildUserAgentFromProduct())并没有直接在负责发送http请求的net模块中使用。
当我调查了net(http)模块的代码后,我发现他们通过一系列的字符串操作和去除空格功能获取了useragent*并进行处理。http_request_headers.cc中的AddHeadersFromString()是将useragent字符串添加到请求头的接口。
注:*但我认为头部数据不来自useragent.cc,因为我无法在任何地方找到对该函数的调用。但我可能在这里错了。
**我认为这是修改OSInfo值的地方。任何未被识别或格式错误的空格字符都会导致此结果。
注:**我无法测试上述语句并证明它,因为Chromium中使用的字符串有一个名为StringPiece的包装器(*wrapper仅是我使用的一个术语,技术上可以以不同的方式调用),而我不知道如何编写StringPiece的c++代码。
但下面给出了一个非常简单的可能出错的示例。
int main()
{
   std::string s = " ONEPLUS\rA3003\rBuild/OPR6.170623.013";
   std::string delimiter = "\r\n"; //this is the delimeter used in chromium source code.
   std::string token = s.substr(0, s.find(delimiter,0));
   std::cout << token << std::endl;
   return 0;
}

https://www.onlinegdb.com/SkTrbFJDz

初始用户代理字符串具有值,而后续的http请求没有该值的原因在于Chrome应用程序在Android中的架构。当页面最初加载时,实际上是由Chrome应用程序(一个非常大的Java代码库,但我认为我们需要查看的核心文件是LoadUrlParams.java)设置这些值,它具有不同的发送HTTP请求的实现(这里用户代理并不是由相同的net(http)模块修剪而是由Java实现来处理),这仅发生在第一次加载期间。但任何其他后续调用都使用浏览器的net(http)模块。

文件参考链接: https://cs.chromium.org/chromium/src/content/common/user_agent.cc?sq=package:chromium&dr=CSs&l=80

https://cs.chromium.org/chromium/src/net/http/http_request_headers.cc?type=cs&q=AddHeadersFromString&l=155

https://cs.chromium.org/chromium/src/content/public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java?q=createLoadDataParamsWithBaseUrl&dr=CSs

我只是提供一个可能存在问题的原因,如果我有更多时间,我会尝试运行测试并证明这一点。最后需要注意的是,此答案没有解决问题的解决方案。它只是说明了原因。
[更新]
一个非常便宜的技巧是检查navigator.useragent是否具有oneplus值,并设置请求的ajax标头并发送它。这将覆盖浏览器添加用户代理标头的机制。
XMLHttpRequest.setRequestHeader(header, value)

这就是我为什么喜欢StackExchange的原因!非常感谢您详细的回答和您的时间。我非常乐意将悬赏授予此答案! - SpazzMarticus

0

在第一个userAgent中,浏览器在发出请求之前通过修改userAgent将设备识别为移动设备;因此出现了ONEPLUS A3003。然而,在第二个userAgent中,由于w3规范(在此处找到),您无法修改userAgent;因此省略了ONEPLUS A3003

当您使用“请求桌面站点”功能时,浏览器无需修改userAgent,因此您会得到相同的userAgent。

请注意:该Chrome浏览器的默认userAgent为:Mozilla/5.0 (Linux; Android 8.0.0; Build/OPR6.170623.013) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.137 Mobile Safari/537.36


浏览器不提供用户代理吗? - SpazzMarticus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接