如果单击超链接,使浏览器提交额外的 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.referrer
和 localStorage
来存储历史记录,如下所示:
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 中有一个限制,即使用<a>
的ping
属性,如下所示:
<a href="https://www.google.com/" ping="trackPing.py">Link to track</a>
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
,例如使用 <meta name="request_id" content="123" />
,您只需通过 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
我怎样才能使评论中的标记用户成为该用户主页的超链接? (姜戈)