由于安全问题,sendBeacon API 暂时无法正常工作,是否有任何解决方法?

18

我有以下代码使用sendBeacon方法发送异步HTTP请求:

var data = {
 name: 'test',
 uniqueId: Math.random()
};
var blob = new Blob([JSON.stringify(data)], {type : 'application/json'});
navigator.sendBeacon('http://example.in/data/post', blob);

这段代码长期以来一直运行良好。但由于Chrome存在安全问题 https://bugs.chromium.org/p/chromium/issues/detail?id=490015, 我们看到错误信息 "Failed to execute 'sendBeacon' on 'Navigator': sendBeacon() with a Blob whose type is not CORS-safelists MIME-type is disallowed experimentally. See http://crbug.com/490015 for details."

在修复此问题之前,是否有任何解决方法可以通过修改请求头使用相同的sendBeacon API来发送JSON数据?对于依赖此API的网站,继续使用将非常有用。关于使用XHR来发送数据的建议并不实用。


这个问题已经被浏览器供应商解决了。请查看下面的答案以及问题背景。 - webblover
很好的帖子和回答。你如何监控错误?将来可能由于XXX原因,sendBeacon()再次无法正常工作。 - jumping_monkey
2
就监控错误而言,一种通用方法(可行的方法)是在任何浏览器中持久化浏览器终端日志。这可能会为您提供有关使用(ping)类型而不是常规XHR类别的API调用在浏览器级别上存在的问题的一些想法。就其未来的缺陷而言,我非常担心。我们将倒退。如果此API未来没有得到一致支持,它很可能会破坏许多与卸载事件相关的潜在用例,这些用例与众多Web应用程序相关。 - webblover
3个回答

22

现在在sendBeacon中Content-Type头中允许的值只有:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

我在我们的项目中遇到了类似的问题,最终我将数据发送为"text/plain; charset=UTF-8"并在服务器端读取流来获取json内容。

客户端:

const blob = new Blob([JSON.stringify(myData)], { type: 'text/plain; charset=UTF-8' });
navigator.sendBeacon(appData.ReleaseSessionUrl, blob);

服务器:

using (var reader = new StreamReader(this.Request.InputStream))
{
   var jsonData = reader.ReadToEnd();
   var sessionData = JsonConvert.DeserializeObject<MyDataType>(jsonData);
}

我不确定这是否对您有所帮助。

https://github.com/GoogleCloudPlatform/stackdriver-errors-js/issues/10


虽然没有直接回答我的用例,但它还是有帮助的。 - webblover

3
请注意,该方法似乎在大多数浏览器上存在问题。以下是关于此主题的一篇大型数据研究文章:传送门

3

简短回答:该问题已经在2021年得到解决。

详细回答(背景):我曾经在开发timeonsite JS时遇到了这个问题。它在Chrome、Firefox和许多其他浏览器中都能正常工作。但是,几个月后,突然出现了上述的CORS白名单错误,导致我们无法实时保存站点数据。这迫使我们退回到Localstorage,这将导致每个用户会话期间(N-1)页视图和随后的损失的网站停留时间数据,这是一个关键的网络分析指标。我们一直在期待浏览器供应商解决这个问题。这是我们用于捕获实时数据的配置,直接依赖于sendBeacon() API

<script type="text/javascript">
    var Tos;
    (function(d, s, id, file) {
        var js, fjs = d.getElementsByTagName(s)[0];
        if (d.getElementById(id)) return;
        js = d.createElement(s);
        js.id = id;
        js.onload = function() {
            // save with XMLHttpRequest (sync=true is blocking approach) or sendBeacon(preferred approach)
            var config = {
                trackBy: 'seconds',
                callback: function(data) {
                    console.log(data);

                    // give your endpoint URL/ server-side URL that is going to handle your TOS data which is of POST method. Eg. PHP, nodejs or python URL which saves this data to your DB
                    var endPointUrl = 'http://localhost:4500/tos'; // replace with your endpoint URL

                    if (data && data.trackingType) {
                        if (data.trackingType == 'tos') {
                            if (Tos.verifyData(data) != 'valid') {
                                console.log('Data abolished!');
                                return; 
                            }
                        }
                        
                        // make use of sendBeacon if this API is supported by your browser.
                        if (navigator && typeof navigator.sendBeacon === 'function') {
                            data.trasferredWith = 'sendBeacon';
                            var blob = new Blob([JSON.stringify(data)], {type : 'application/json'});
                            navigator.sendBeacon(endPointUrl, blob);
                        }

                        /*else {

                            // XMLHttpRequest begins..
                            // XMLHttpRequest with sync=true (blocking approach)
                            // XMLHttpRequest code block here to post your data
                        }*/
                    }
                }
            };

            if (TimeOnSiteTracker) {
                Tos = new TimeOnSiteTracker(config);
            }
        };
        js.src = file;fjs.parentNode.insertBefore(js, fjs);
    } (document, 'script', 'TimeOnSiteTracker', 'https://cdnjs.cloudflare.com/ajax/libs/timeonsite/1.1.0/timeonsitetracker.min.js'));
</script> 

正如您在上面看到的,由于CORS安全列表问题,我们过去几年一直注释掉了sendBeacon()代码块,并依赖于XMLHTTPRequest和async=false来发布数据,这是一种阻塞方法,在许多浏览器上特别是移动设备上并不太可靠。

最近,浏览器供应商似乎已经解决了这个问题,sendBeacon() API可以再次使用。我测试了许多浏览器,它似乎工作得很好。因此,该问题被标记为“已解决”截至2021年。我希望您添加那些适用于beforeunload/unload窗口事件的版本/年份明确提及的设备/浏览器。


1
Chrome:版本-95.0.4638.69,(2021)正常工作 - webblover
2
Firefox: 版本-94.0.1 (2021) 正常工作 - webblover

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接