如果单击超链接,使浏览器提交额外的 HTTP-Header

Posted

技术标签:

【中文标题】如果单击超链接,使浏览器提交额外的 HTTP-Header【英文标题】:Make browser submit additional HTTP-Header if click on hyperlink 【发布时间】:2019-01-14 06:08:38 【问题描述】:

如果用户点击链接,有没有办法让网络浏览器提交额外的 HTTP 标头?

背景:在我们的环境中,每个 http-request 在服务器端都有一个唯一的 ID。见https://serverfault.com/questions/797609/apache-x-request-id-like-in-heroku

如果您的 Web 应用程序收到一个 http 请求,我想知道哪个页面是之前的页面。 http 引用是不够的,因为用户可以在他的浏览器中使用多个标签。

我想避免将丑陋的请求 ID 放入从浏览器发送到服务器的每个 GET 请求中。到目前为止,我们的 URL 都很好。

我更喜欢的解决方案是一些 javascript 魔术,它将当前页面的 request-id 添加到下一个 http 请求中。

详细步骤:

    浏览器访问网址http://example.com/search Web 服务器收到请求 ID 为 123 的 http 请求 网络服务器将 URL 的内容发送到浏览器(搜索页面)。该页面在某处包含请求 ID 123 用户搜索“foobar”。 Web 浏览器向服务器提交一个 http 请求,并以某种方式包含先前的请求 ID。 Web 服务器接收到第二个 http 请求 (ID 456) 并可以通过某种方式访问​​第一个请求 (ID 123) 的值。 Web 服务器可以将关系“123 --> 456”存储在数据库中以供以后分析。

我的目标是跟踪关系“123 --> 456”。上述解决方案只是实现目标的策略。欢迎使用其他策略。

我们使用网络框架 django。但 AFAIK 这在这种情况下确实很重要。

用户可以在其浏览器中使用多个标签

我详细说明了这对匹配解决方案意味着什么。来自一个用户的一系列请求并不能解决问题。

一个使用多个标签:

    用户在 tab1 中查看页面 A 用户在 tab2 中查看页面 B 用户点击页面 A 上的链接到页面 C 用户通过页面 C 上的链接到页面 D 用户点击页面 B (tab2) 上的链接到页面 E。

我想知道看两个序列:

A -> C -> D

B -> E

【问题讨论】:

只是将 id 附加到所有链接(GET 参数)或使用 ajax? @appleapple 我想避免将丑陋的请求 ID 放入从浏览器发送到服务器的每个 GET 请求中。到目前为止,我们的 URL 都很好。 哦,错过那句话。顺便说一句,我认为一个好的 URL 并不重要。至少你可以重定向到一个更干净的。 【参考方案1】:

这里唯一的现代“理智”选项是使用 ServiceWorker。

ServiceWorker 可以拦截您控制的域的 HTTP 请求,并用更多标头装饰它。

ServiceWorker 在浏览器选项卡的“外部”工作,如果在同一个网站上打开多个选项卡,则所有这些选项卡都将使用同一个 ServiceWorker。

关于如何完成此任务的完整教程对于这个答案框来说绝对是太多了,但是拦截和处理 HTTP 请求是一个很大的用例,因此场外来源通常会以此为例。

我会说这是个坏主意。如果你认为你需要这个,也许你可以用不同的方式来处理它。执行此操作的常用方法可能是使用 cookie。

【讨论】:

非常感谢! 这在 2021 年仍然是一个解决方案吗?您能否分享一些有关如何使用 cookie 实现此目的的信息或链接? @PaulM 你可能仍然不应该这样做。如果您对 Cookie 的工作方式有任何疑问,请提出一个新问题。 谢谢@Evert,我有一个来自 S3 的签名 URL,我想用不同的名称下载它。我只是使用 AJAX 来获取并使用不同的文件名进行下载。最初的想法是将带有文件名的标头发送到 S3 以使用不同的名称下载。无论如何,与OP发布的问题不同的用例。 @PaulM 这个解决方案很有意义!不过对于大文件可能会有点困难【参考方案2】:

我们可以使用以下方式修改请求标头:

XMLHttpRequest() 对象的.setRequestHeader() 方法(相同或allowed origins)。 在浏览器控制台中编辑标题或使用一些补充(不实用)。 执行来自服务器端的请求,例如使用 CURL、wget 或某些库(client->serverProxy->url with custom headers)。

不可能(使用javascript)更改浏览器在<a href=""></a> 之类的请求中发送的标头,因为至少现在,http 内容协商是浏览器的内部功能(除了部分使用XMLHttpRequest 或@987654323 @)。

然后,在我看来,正如@Evert 所说,您有两种实用的方法(实际上是第三种)来实现您的目标,即执行服务器代理或使用 cookie。这里有一个非常简单的使用window.localStorage的方法:

本地存储示例

if (!localStorage.getItem("ids")) //<-- the place in which we store the behavior
  localStorage.setItem("ids", 'somevalue')
 else 
  var ids = JSON.parse(localStorage.getItem("ids"));
  ids.ids.push(id);//<-- we add some value
  localStorage.setItem("ids", JSON.stringify(ids));  

此处的完整示例:https://jsfiddle.net/hy4rzob9/ 多次按下运行,您会看到我们存储了每次访问,当然,在您的实现中,您必须将随机数替换为每个页面的唯一标识符。

带有多个选项卡的 LocalStorage 示例

考虑到更新,我们还可以使用 document.referrerlocalStorage 来存储历史记录,如下所示:

var session = Math.random();

if(!localStorage.getItem("routes"))//<-- first time
    var routes = ;
routes[session] = [document.location.href];

localStorage.setItem("routes", JSON.stringify(routes))

else

    var routes = JSON.parse(localStorage.getItem("routes"));

    if(!document.referrer)
        routes[session] = [document.location.href];//<-- new root
    else

        for(let ses in routes)
             if(routes[ses].includes(document.referrer))
                routes[ses].push(document.location.href); 
             
        
    

    localStorage.setItem("routes", JSON.stringify(routes))




var r = JSON.parse(localStorage.getItem("routes"));

console.log(r);

此处为https://codesandbox.io/s/qk99o4vy7q 的完整示例,以模拟您的示例打开此https://qk99o4vy7q.codesandbox.io/a.html(代表A)并在新选项卡中打开https://qk99o4vy7q.codesandbox.io/b.html(代表B),在两个选项卡中导航并查看控制台。如果我们共享一些引荐来源网址,则此示例将不起作用,因为如果我们在 URL 中不附加任何内容,我们就无法区分引荐来源网址。 A -> C -> D 和 B -> E 可以工作,但 A -> C -> D 和 B -> E -> A 不行。

Ping 示例

还有其他方法,很简单,但在browser compatibility 中有一个限制,即使用&lt;a&gt;ping 属性,如下所示:

&lt;a href="https://www.google.com/" ping="trackPing.py"&gt;Link to track&lt;/a&gt;

ping 包含一个以空格分隔的 URL 列表,当 跟随超链接,将发送带有正文 PING 的 POST 请求 通过浏览器(在后台)。通常用于跟踪。

打开控制台->网络,删除所有,运行sn-p点击链接,如果你的浏览器支持,你会看到浏览器发送POST请求到trackPing.py(我猜不SO 中存在),该帖子是无效的,但您可以跟踪环境变量,例如request.environ['REMOTE_ADDR'] 或其他东西。

【讨论】:

这个工作是用户有几个浏览器标签吗?我详细阐述了这个问题以说明我的担忧。 @guettli 我已经更新了答案,看到这个codesandbox.io/s/qk99o4vy7q 我还指定了如何模拟你的情况。【参考方案3】:

首先,对不起我的英语。

编辑:

阅读您的编辑后,我意识到我的答案根本不适合,因为标签。

无法直接修改浏览器发出获取请求的方式。知道了这一点,您的可能性是:

使用 GET 参数。我知道你会尽量避免这种情况。 正如@Evert 所说,使用ServiceWorkers。这是在请求离开浏览器之前修改请求的最简洁方式。 最后一种方法(一种简单的方法)类似于@Emeeus 的方法,但不要使用localStorage,其值在选项卡之间共享,您应该使用sessionStorage,其值与选项卡无关。此外,您应该只存储一个随机 ID,而不是存储整个路线。此 ID 将用作特定选项卡的请求链的标识。然后,一旦您的网络服务器返回每个 Request-ID,例如使用 &lt;meta name="request_id" content="123" /&gt;,您只需通过 ajax 向特定跟踪端点发出请求并存储: chain_id(存储在 sessionStorage 中) request_id(存储在 head > meta 中) 时间戳(在网络服务器中生成) session_id(可从网络服务器访问)。您可以避免这种情况,但它对于检查目的仍然有用。

存储路由的请求是在页面加载之后发出的,而不是之前。这种方法与 Analytics 的工作方式非常相似。

// generate an unique code and store it in sessionStorage.
if (!sessionStorage.getItem('chain_id'))  
    sessionStorage.setItem('chain_id', 'a7835e0a-3ee9-e981-...');


// Then, if you use JQuery:
$(document).ready(function() 
    $.ajax(
        type: "POST",
        url: 'your/tracking/endpoint/',
        data: 
            'chain_id': sessionStorage.getItem('chain_id'),
            'request_id': document.querySelector("meta[name='request_id']").getAttribute('content'),
        
    );
);

注意:最好不要使用 JQuery 来处理跟踪请求,也不要等到文档完全加载。这只是一个例子。

仅此而已。你有用户代理、链、请求和请求时间戳之间的关系,所以如果你需要知道在给定之前或之后发出了什么请求一,您只需要使用Chain-ID 和时间戳作为过滤器在数据库中查找。

您请求的 django 模型可能是。

from django.db import models
from django.contrib.sessions.models import Session


class Request(models.Model):
    session = models.ForeignKey(Session)
    chain_id = models.Charfield(max_length=100)
    request_id = models.WhatEverField...
    request_url = models.URLField(max_length=200)
    created = models.DateTimeField(auto_now_add=True)

希望对你有帮助。

【讨论】:

该解决方案需要适用于多个浏览器选项卡。我详细说明了这个问题。如果有不清楚的地方,请告诉我。谢谢。 需要独立使用多个浏览器选项卡,这使您的问题有点复杂。我要编辑我的答案。【参考方案4】:

我不知道这是否会有所帮助,但我认为也许 Ajax 会有所帮助, 就像在 onclick 事件侦听器中设置额外的标头一样,对于请求 id,如果它不是那么敏感,那么您可以将 cookie 用于容器,或者更好的东西......

【讨论】:

如果用户点击链接的 href,我不确定 AJAX 是否可以拦截 http 请求。这会创建一个从浏览器到服务器的 GET 请求。我不知道如何通过 AJAX 添加额外的标头。 使用 POST reuest 发送它,例如: onclick(function() //set header //post to destination //检索下一个数据,附加到 cookie,然后将用户重定向到点击的 url )跨度>

以上是关于如果单击超链接,使浏览器提交额外的 HTTP-Header的主要内容,如果未能解决你的问题,请参考以下文章

如何在超链接单击 [重复] 上将浏览器从 IE 更改为 Chrome

单击链接时如何使 Onclick 代码不执行

Pjax无刷新跳转页面实现,支持超链接与表单提交

我怎样才能使评论中的标记用户成为该用户主页的超链接? (姜戈)

单击超链接时如何关闭 MaterialAlertDialog?

如何使超链接在 RichTextBox 中工作?