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

Posted

技术标签:

【中文标题】请求网站时与发送的用户代理不同的 Javascript 用户代理(ajax)【英文标题】:Javascript user-agent (ajax) different to sent user-agent when requesting website 【发布时间】:2018-06-02 22:00:27 【问题描述】:

我注意到我的手机(OnePlus 3,android 8.0.0)上的 Chrome (64.0.3282.137) 在请求网页时与通过 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

【问题讨论】:

您是否考虑过提交issue 描述预期结果和实际结果? 谢谢,这对我来说应该更明显。我报告了该问题,并在收到反馈后更新/回答问题。 您能否在问题中包含指向问题的链接? @floor 我在我的一个页面上注意到它,然后使用一个空白页面和一个普通的 apache/php 服务器来复制结果。 我认为它只发生在 oneplus 设备上@9​​87654322@forums.oneplus.net/threads/… 【参考方案1】:

我分析了 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 只是我使用的一个术语,从技术上讲它可以以我不知道的不同方式调用。)。而且我不知道如何用 c++ 编写 StringPiece 的代码。

但是下面给出了一个非常简单的例子来说明它是如何出错的。

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请求没有值,在于android中chrome应用程序的架构。最初加载页面时,这些值实际上是由 chrome 应用程序设置的(​​一个非常大的 java 代码库,但我认为我们需要查看的核心文件是 LoadUrlParams.java),它具有发送 http 请求的不同实现(这里useragent 没有被同一个 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 的原因!非常感谢您的详细回答和您的时间。我很高兴为这个答案提供赏金!【参考方案2】:

在第一个userAgent中,浏览器在发出请求前通过修改userAgent将设备识别为移动设备;因此ONEPLUS A3003。然而,在第二个中,由于 w3 规范(Find it here),您不能修改 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

【讨论】:

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

以上是关于请求网站时与发送的用户代理不同的 Javascript 用户代理(ajax)的主要内容,如果未能解决你的问题,请参考以下文章

正向代理和反向代理

nginx反向代理

代理的基本原理和作用

正向代理和反向代理

是否可以在apache代理中将两个不同的URL模式发送到单独的转发代理

利用 squid 反向代理提高网站性能