本文目录导读:

PHP 中实现 Beacon API 主要用于在页面卸载(关闭、刷新、跳转)时,向服务器发送少量的分析数据、日志或事件,且不关心服务器响应。
什么是 Beacon API
Beacon API(navigator.sendBeacon())是浏览器原生提供的异步、非阻塞请求方法,特点是:
- 异步且不阻塞:即使在
unload或beforeunload事件中调用,也能确保请求发送 - 不关心响应:自动忽略服务器返回内容
- 通常使用 POST 方法:数据以
text/plain、application/x-www-form-urlencoded或Blob格式发送 - 可靠性高:即使页面关闭,浏览器仍会尽力发送请求
PHP 端接收 Beacon 请求
PHP 端处理 Beacon 请求与处理普通 POST 请求基本一致,主要根据 Content-Type 读取请求体数据。
1 简单接收示例
<?php
// 获取请求体内容
$rawData = file_get_contents('php://input');
// 根据 Content-Type 解析数据
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
$data = [];
if (strpos($contentType, 'application/json') !== false) {
$data = json_decode($rawData, true) ?: [];
} elseif (strpos($contentType, 'application/x-www-form-urlencoded') !== false) {
parse_str($rawData, $data);
} else {
// text/plain 或其他格式 - 直接接收字符串
$data = ['message' => $rawData];
}
// 处理数据(记录日志、存储统计等)
file_put_contents(
'beacon_log.txt',
date('Y-m-d H:i:s') . ' | ' . json_encode($data) . PHP_EOL,
FILE_APPEND
);
// Beacon 不关心响应,但通常返回空 200 响应
http_response_code(200);
header('Content-Type: application/json');
echo json_encode(['status' => 'received']);
?>
2 专用接收脚本 (beacon.php)
更完整的示例,包含请求源验证:
<?php
header('Content-Type: application/json');
http_response_code(200);
// 获取原始请求数据
$input = file_get_contents('php://input');
if (empty($input)) {
echo json_encode(['status' => 'empty']);
exit;
}
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
$payload = [];
if (strpos($contentType, 'application/json') !== false) {
$payload = json_decode($input, true) ?? [];
} elseif (strpos($contentType, 'application/x-www-form-urlencoded') !== false) {
parse_str($input, $payload);
} else {
// 假设是键值对或纯文本
$payload = ['raw' => $input];
}
// 添加服务器时间戳和 IP
$payload['_received_at'] = date('Y-m-d H:i:s');
$payload['_client_ip'] = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
// 写入日志(生产环境建议使用数据库或消息队列)
$logEntry = json_encode($payload) . PHP_EOL;
file_put_contents('/var/log/beacon/events.log', $logEntry, FILE_APPEND | LOCK_EX);
// 可选:返回一些状态(通常不被使用)
echo json_encode(['status' => 'logged']);
?>
前端 JavaScript 发送 Beacon
1 基本用法
// 发送简单的键值对数据
const data = {
action: 'page_exit',
timeSpent: 120,
page: '/about'
};
navigator.sendBeacon('/beacon.php', JSON.stringify(data));
2 在页面卸载时发送
// 页面卸载时可靠地发送数据
window.addEventListener('beforeunload', function() {
navigator.sendBeacon('/beacon.php', JSON.stringify({
event: 'page_unload',
timestamp: Date.now()
}));
});
// 对移动设备的页面隐藏事件也做处理
document.addEventListener('visibilitychange', function() {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon('/beacon.php', JSON.stringify({
event: 'page_hidden',
timestamp: Date.now()
}));
}
});
3 发送不同格式的数据
// Blob 格式(推荐用于复杂数据)
const blob = new Blob([JSON.stringify({key: 'value'})], {type: 'application/json'});
navigator.sendBeacon('/beacon.php', blob);
// FormData 格式
const formData = new FormData();
formData.append('action', 'click');
formData.append('element', 'button_save');
navigator.sendBeacon('/beacon.php', formData);
// URL 编码格式
const urlEncoded = new URLSearchParams({event: 'scroll', depth: '80%'});
navigator.sendBeacon('/beacon.php', urlEncoded);
常见用例与最佳实践
1 用户行为跟踪
// 发送页面停留时间
let startTime = Date.now();
window.addEventListener('beforeunload', function() {
let timeSpent = Math.floor((Date.now() - startTime) / 1000);
navigator.sendBeacon('/beacon.php', JSON.stringify({
event: 'page_exit',
page: window.location.pathname,
duration: timeSpent,
referrer: document.referrer
}));
});
2 错误日志上报
window.addEventListener('error', function(e) {
navigator.sendBeacon('/beacon.php', JSON.stringify({
event: 'js_error',
message: e.message,
source: e.filename,
line: e.lineno,
column: e.colno
}));
});
3 服务端优化建议
// beacon_handler.php - 优化版
// 1. 不处理大文件,设置接收限制
$maxSize = 1024 * 10; // 10KB
$input = file_get_contents('php://input', false, null, 0, $maxSize);
// 2. 使用快速写入,避免阻塞
$fd = fopen('/tmp/beacon_queue.log', 'a');
if ($fd) {
fwrite($fd, $input . PHP_EOL);
fclose($fd);
}
// 3. 返回 204 No Content(更快)
http_response_code(204);
exit;
注意事项
- 大小限制:Beacon 数据通常限制在 64KB 左右,过大数据可能失败
- 跨域问题:不同域需要 CORS 支持
- 服务器响应:客户端不关心响应,但服务器应尽快返回(推荐 204)
- 并发请求:少量 Beacon 请求,不适合批量大数据
- 请求顺序:Beacon 不保证请求顺序
完整示例
前端 (track.js)
// 页面加载时开始计时
const pageData = {
url: window.location.href,
referrer: document.referrer || '',
userAgent: navigator.userAgent,
language: navigator.language,
screenSize: `${window.screen.width}x${window.screen.height}`,
timestamp: Date.now()
};
// 发送页面加载事件
navigator.sendBeacon('/beacon.php', JSON.stringify({
event: 'page_load',
...pageData
}));
// 页面卸载时发送
window.addEventListener('beforeunload', function() {
navigator.sendBeacon('/beacon.php', JSON.stringify({
event: 'page_unload',
url: window.location.href,
timestamp: Date.now(),
duration: Date.now() - pageData.timestamp
}));
});
后端 (beacon.php)
<?php
// 快速响应,不关心客户端
http_response_code(204);
$input = file_get_contents('php://input');
if (empty($input)) exit;
$data = json_decode($input, true);
if (!is_array($data)) exit;
// 简单验证
$allowedEvents = ['page_load', 'page_unload', 'click', 'scroll'];
if (!in_array($data['event'] ?? '', $allowedEvents)) {
exit;
}
// 记录到日志
$log = date('c') . ' | ' . json_encode($data) . PHP_EOL;
file_put_contents('/path/to/tracking.log', $log, FILE_APPEND | LOCK_EX);
?>
PHP 处理 Beacon API 的核心就是:
- 接收 POST 请求
- 读取
php://input原始数据 - 根据 Content-Type 解析
- 尽快返回响应(204/200)
- 异步处理数据(日志、队列、数据库)
Beacon 特别适合于页面卸载、错误上报、轻量级用户行为跟踪等场景。