Nodejs Etager 一个基于Etag的简易的tracker实现
Nodejs Etager 一个基于Etag的简易的tracker实现
通过Etag而不是cookie来追踪用户的访问有个好处,就是客户端不用部署js,省事儿。
看图,可以看到:当用户第一次访问带有tracker.jpg这个假图片的网页时,后台会分配一个uuid给这个用户,当第二次访问任何其他带有这个图像的url的时候,由于返回了相同的Etag,浏览器会把这个请求设成304,不会从服务器端再下载任何内容(本身这个tracker.jpg就是0b的)
所以,当这个用户清空他的浏览器缓存之前,我们可以一直非常低成本的追踪同一个用户的访问路径。是个非常有趣的尝试。~
Nodejs Etager 一个基于Etag的简易的tracker实现
通过Etag而不是cookie来追踪用户的访问有个好处,就是客户端不用部署js,省事儿。
原理说明
看图,可以看到:当用户第一次访问带有tracker.jpg
这个假图片的网页时,后台会分配一个UUID给这个用户。当第二次访问任何其他带有这个图像的URL的时候,由于返回了相同的Etag,浏览器会把这个请求设成304,不会从服务器端再下载任何内容(本身这个tracker.jpg
就是0B的)。
因此,当这个用户清空他的浏览器缓存之前,我们可以一直非常低成本地追踪同一个用户的访问路径。这是一个非常有趣的尝试。
示例代码
以下是一个简单的Node.js实现示例:
const express = require('express');
const uuid = require('uuid');
const app = express();
// 存储用户信息的字典
const userDict = {};
app.get('/tracker.jpg', (req, res) => {
const url = req.url;
let etag = userDict[url];
if (!etag) {
// 如果没有分配过UUID,则生成一个新的UUID并设置为ETag
etag = uuid.v4();
userDict[url] = etag;
}
// 设置响应头,包含ETag
res.set('ETag', `"${etag}"`);
// 检查客户端是否已经缓存了该资源
if (req.fresh) {
res.status(304).send();
} else {
res.status(200).type('jpg').send('');
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
代码解析
- 引入依赖:使用
express
作为Web框架,并使用uuid
库来生成唯一的标识符。 - 创建应用实例:使用
express
创建一个Web应用实例。 - 存储用户信息:使用一个字典对象
userDict
来存储每个URL对应的UUID。 - 处理GET请求:
- 当客户端请求
/tracker.jpg
时,首先检查该URL是否已经有分配的UUID。 - 如果没有,则生成一个新的UUID,并将其存储在
userDict
中。 - 设置响应头中的ETag值。
- 使用
req.fresh
检查客户端是否已经缓存了该资源。如果是,则返回状态码304;否则,返回状态码200。
- 当客户端请求
- 启动服务器:在指定的端口上启动服务器。
总结
通过这种方式,我们可以通过Etag来追踪用户的访问路径,而不需要依赖于复杂的JavaScript代码。这种方法简单、高效,并且可以持续追踪用户行为直到他们清空浏览器缓存。
更多细节和源代码可以查看GitHub仓库: https://github.com/turingou/etager
实现方式有意思
Node.js Etager 是一个利用 Etag 来追踪用户访问的简单实现。这种方式的好处在于不需要客户端部署 JavaScript,使得追踪更加便捷。
实现原理
- 第一次访问:当用户首次访问页面时,服务器为该用户生成一个 UUID,并将此 UUID 和 Etag 相关联。
- 后续访问:当用户再次访问含有
tracker.jpg
的页面时,浏览器会检测到 Etag 值相同,并返回 304 状态码,即未修改状态。因此不会重新下载图像,但仍可以追踪到用户的行为。
示例代码
以下是一个简单的 Node.js 实现:
const express = require('express');
const uuid = require('uuid');
const crypto = require('crypto');
const app = express();
// 模拟存储用户信息
let users = {};
app.get('/tracker.jpg', (req, res) => {
const etag = req.headers['if-none-match'];
// 生成或获取用户的UUID
let userId = users[req.ip];
if (!userId) {
userId = uuid.v4();
users[req.ip] = userId;
}
// 计算Etag
const etagValue = crypto.createHash('sha256').update(userId).digest('hex');
// 如果Etag匹配,则返回304状态码
if (etag === etagValue) {
res.status(304);
} else {
// 否则返回200状态码,并设置Etag
res.set('ETag', etagValue);
res.status(200);
}
// 返回0字节的图像
res.end();
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
解释
- uuid:用于生成唯一标识符。
- crypto:用于计算 Etag 值,这里使用 SHA-256 哈希算法。
- req.headers[‘if-none-match’]:获取客户端发送的 Etag 值。
- res.set(‘ETag’, etagValue):设置响应头中的 Etag 值。
- res.status(304):如果 Etag 匹配,则返回 304 状态码,表示资源未被修改。
通过这种方式,可以在不依赖 JavaScript 的情况下,实现对用户的低成本追踪。