如果单击超链接,使浏览器提交额外的 HTTP-Header
Make browser submit additional HTTP-Header if click on hyperlink
如果用户点击 link,是否有办法让网络浏览器提交额外的 HTTP header?
背景:在我们的环境中,每个 http-request 在服务器端都有一个唯一的 ID。参见 https://serverfault.com/questions/797609/apache-x-request-id-like-in-heroku
如果您的网络应用程序收到 http-request,我想知道之前的页面是哪个页面。 http 引荐来源网址是不够的,因为 用户可以在他的浏览器中使用多个选项卡。
我想避免将难看的 request-id 放入从浏览器发送到服务器的每个 GET 请求中。到目前为止,我们的 URL 都很好。
我更喜欢的解决方案是一些 JavaScript 魔术,它将当前页面的 request-id 添加到下一个 http 请求中。
详细步骤:
- 浏览器访问 URL 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。但是据我所知,这在这种情况下确实很重要。
用户可以在他的浏览器中使用多个选项卡
我详细说明了匹配解决方案的含义。来自一个用户的请求序列不能解决问题。
一次使用多个选项卡:
- 用户查看tab1中的页面A
- 用户查看tab2中的页面B
- 用户在页面 A 上关注 link 到页面 C
- 用户跟随页面 C 上的 link 到页面 D
- 用户在页面 B (tab2) 上跟随 link 到页面 E。
我想知道看两个序列:
A -> C -> D
和
B -> E
此处唯一的现代 'sane' 选项是使用 ServiceWorker。
ServiceWorker 可以拦截您控制的域的 HTTP 请求,并用更多 headers.
对其进行修饰
一个 ServiceWorker 工作 'outside' 个浏览器选项卡,如果在同一个网站上打开多个选项卡,则所有这些选项卡都将使用同一个 serviceworker。
关于如何实现的完整教程对于这个答案框来说肯定太多了,但是拦截和处理 HTTP 请求是一个很大的问题 use-case,所以 off-site 来源通常会有这个举个例子。
我会说这是个坏主意。如果你认为你需要这个,也许你可以用不同的方式处理它。执行此操作的常见方法可能是改用 cookie。
我们可以修改请求 headers 使用:
- .setRequestHeader() method of XMLHttpRequest() object (in same or allowed origins)。
- 在浏览器控制台中编辑 headers 或使用一些补充(不实用)。
- 从服务器端执行请求,例如使用 CURL、wget 或某些库(client->serverProxy->url 和自定义 headers)。
不可能(使用javascript)改变浏览器在像<a href=""></a>
这样的请求中发送的headers,因为至少现在,http内容协商是浏览器的内部能力(除了部分使用 XMLHttpRequest 或 allowed origins)。
然后,在我看来,正如@Evert 所说,您有两种实用的方法(实际上是第三种)来实现您的目标,即执行服务器代理或使用 cookie。在这里你有一个非常简单的方法使用 window.localStorage:
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, to emulate your example open this https://qk99o4vy7q.codesandbox.io/a.html (represents A) and open in a new tab 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 Contains a space-separated list of URLs to which, when the
hyperlink is followed, POST requests with the body PING will be sent
by the browser (in the background). Typically used for tracking.
打开控制台->网络,全部删除,运行片段,点击link,如果你的浏览器支持,你会看到浏览器发送一个POST 请求 trackPing.py
(我想在 SO 中不存在), post 是无效的,但您可以跟踪环境变量,例如 request.environ['REMOTE_ADDR']
或其他东西。
首先,对不起我的英语。
编辑:
阅读您的编辑后,我意识到我的答案根本不合适,因为有制表符。
无法直接修改浏览器发起get请求的方式。知道这一点,你的可能性是:
- 使用 GET 参数。我知道你尽量避免这种情况。
- 正如@Evert 所说,使用 ServiceWorkers。这是在离开浏览器之前修改请求的最干净的方法。
- 最后一种方法(一种简单的方法)类似于@Emeeus 的方法,但不是使用
localStorage
,其值在选项卡之间共享,你应该使用 sessionStorage
,其值与制表符无关。此外,您不应存储整个路线,而应仅存储一个随机 ID。此 ID 将用作特定选项卡请求链的标识。然后,一旦你的网络服务器 returns 每个 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)
希望对您有所帮助。
我不知道这是否有帮助,但我想也许 Ajax 可以,
比如在 onclick 事件侦听器中设置额外的 header,至于请求 id,如果它不是那么敏感,那么你可以使用 cookie 作为容器,或者更好的东西......
如果用户点击 link,是否有办法让网络浏览器提交额外的 HTTP header?
背景:在我们的环境中,每个 http-request 在服务器端都有一个唯一的 ID。参见 https://serverfault.com/questions/797609/apache-x-request-id-like-in-heroku
如果您的网络应用程序收到 http-request,我想知道之前的页面是哪个页面。 http 引荐来源网址是不够的,因为 用户可以在他的浏览器中使用多个选项卡。
我想避免将难看的 request-id 放入从浏览器发送到服务器的每个 GET 请求中。到目前为止,我们的 URL 都很好。
我更喜欢的解决方案是一些 JavaScript 魔术,它将当前页面的 request-id 添加到下一个 http 请求中。
详细步骤:
- 浏览器访问 URL 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。但是据我所知,这在这种情况下确实很重要。
用户可以在他的浏览器中使用多个选项卡
我详细说明了匹配解决方案的含义。来自一个用户的请求序列不能解决问题。
一次使用多个选项卡:
- 用户查看tab1中的页面A
- 用户查看tab2中的页面B
- 用户在页面 A 上关注 link 到页面 C
- 用户跟随页面 C 上的 link 到页面 D
- 用户在页面 B (tab2) 上跟随 link 到页面 E。
我想知道看两个序列:
A -> C -> D
和
B -> E
此处唯一的现代 'sane' 选项是使用 ServiceWorker。
ServiceWorker 可以拦截您控制的域的 HTTP 请求,并用更多 headers.
对其进行修饰一个 ServiceWorker 工作 'outside' 个浏览器选项卡,如果在同一个网站上打开多个选项卡,则所有这些选项卡都将使用同一个 serviceworker。
关于如何实现的完整教程对于这个答案框来说肯定太多了,但是拦截和处理 HTTP 请求是一个很大的问题 use-case,所以 off-site 来源通常会有这个举个例子。
我会说这是个坏主意。如果你认为你需要这个,也许你可以用不同的方式处理它。执行此操作的常见方法可能是改用 cookie。
我们可以修改请求 headers 使用:
- .setRequestHeader() method of XMLHttpRequest() object (in same or allowed origins)。
- 在浏览器控制台中编辑 headers 或使用一些补充(不实用)。
- 从服务器端执行请求,例如使用 CURL、wget 或某些库(client->serverProxy->url 和自定义 headers)。
不可能(使用javascript)改变浏览器在像<a href=""></a>
这样的请求中发送的headers,因为至少现在,http内容协商是浏览器的内部能力(除了部分使用 XMLHttpRequest 或 allowed origins)。
然后,在我看来,正如@Evert 所说,您有两种实用的方法(实际上是第三种)来实现您的目标,即执行服务器代理或使用 cookie。在这里你有一个非常简单的方法使用 window.localStorage:
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, to emulate your example open this https://qk99o4vy7q.codesandbox.io/a.html (represents A) and open in a new tab 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 Contains a space-separated list of URLs to which, when the hyperlink is followed, POST requests with the body PING will be sent by the browser (in the background). Typically used for tracking.
打开控制台->网络,全部删除,运行片段,点击link,如果你的浏览器支持,你会看到浏览器发送一个POST 请求 trackPing.py
(我想在 SO 中不存在), post 是无效的,但您可以跟踪环境变量,例如 request.environ['REMOTE_ADDR']
或其他东西。
首先,对不起我的英语。
编辑:
阅读您的编辑后,我意识到我的答案根本不合适,因为有制表符。
无法直接修改浏览器发起get请求的方式。知道这一点,你的可能性是:
- 使用 GET 参数。我知道你尽量避免这种情况。
- 正如@Evert 所说,使用 ServiceWorkers。这是在离开浏览器之前修改请求的最干净的方法。
- 最后一种方法(一种简单的方法)类似于@Emeeus 的方法,但不是使用
localStorage
,其值在选项卡之间共享,你应该使用sessionStorage
,其值与制表符无关。此外,您不应存储整个路线,而应仅存储一个随机 ID。此 ID 将用作特定选项卡请求链的标识。然后,一旦你的网络服务器 returns 每个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)
希望对您有所帮助。
我不知道这是否有帮助,但我想也许 Ajax 可以, 比如在 onclick 事件侦听器中设置额外的 header,至于请求 id,如果它不是那么敏感,那么你可以使用 cookie 作为容器,或者更好的东西......