json 带有EventSource的服务器发送事件(SSE)能否通过POST传递参数

hrirmatl  于 2023-08-08  发布在  其他
关注(0)|答案(4)|浏览(375)

我正在使用Html5服务器发送事件。服务器端是Java Servlet。我有一个JSON数组数据要传递到服务器。

var source = new EventSource("../GetPointVal?id=100&jsondata=" + JSON.stringify(data));

字符串
如果数组的大小很小,服务器端可以获取查询字符串。但是如果数组的大小很大。(可能超过数千个字符),服务器无法获取查询字符串。是否可以在new EventSource(...)中使用POST方法将json数组传递给服务器,以避免querystring长度限制?

rfbsl7qr

rfbsl7qr1#

不,SSE标准不允许POST。
(For没有技术原因,据我所知-我认为这只是设计师从未看到用例:它不仅仅是大数据,但是如果你想做一个自定义的身份验证方案,出于安全原因,不要把密码放在GET数据中。)
XMLHttpRequest(即 AJAX )确实允许POST,所以一个选择是回到较旧的long-poll/comet方法。(我的书Data Push Apps with HTML5 SSE详细介绍了如何做到这一点。
另一种方法是预先POST所有数据,并将其存储在HttpSession中,然后调用SSE进程,该进程可以使用该会话数据。(SSE确实支持cookie,所以JSESSIONID cookie应该可以正常工作。
P.S. standard并没有明确表示POST不能使用。但是,与XMLHttpRequest不同的是,没有参数来指定要使用的http方法,也没有办法指定要发布的数据。

mftmpeh8

mftmpeh82#

虽然您不能使用EventSource API来执行此操作,但没有技术原因导致服务器不能实现POST请求。诀窍是让客户端发送请求。例如,This answer discusses sse.js作为EventSource的替代品。

7y4bm7vi

7y4bm7vi3#

或者,可以从用另一个PHP自定义的文件中读取数据
http://..../command_receiver.php?command=blablabla
command_receiver.php

<?php
$cmd = $_GET['command'];
$fh = fopen("command.txt","w");
fwrite($fh, $cmd);
fclose($fh);
?>

字符串
demo2_sse.php

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');

//$a = $_GET["what"];
$time = microtime(true); //date('r');

$fa = fopen("command.txt", "r");
$content = fread($fa,filesize("command.txt"));
fclose($fa);

echo "data: [{$content}][{$time}]\n\n";
flush();
?>


并且EventSource包含在任意命名的html中,如下所示

<!DOCTYPE html>
<html>
<body>
<h1>Getting server updates</h1>
var source = new EventSource("demo2_sse.php");
source.onmessage = function (event) {
        mycommand = event.data.substring(1, event.data.indexOf("]"));
       mytime = event.data.substring(event.data.lastIndexOf("[") + 1, event.data.lastIndexOf("]"));
}
</script>
</body>
</html>

slhcrj9b

slhcrj9b4#

我有一个新颖的方法来解决这个问题,对于那些想把这个限制在一个请求。事件流请求将cookie传递给服务器,就像任何 AJAX 请求一样。我的方法是在发出请求之前设置包含数据的cookie,然后在第一次响应时立即丢弃cookie。
现在有一些限制与饼干要知道。首先,cookie的最大长度是4096字节,包括名称以及潜在的3字节开销。其次,每个浏览器的cookie安全数量为50。不同的浏览器有不同的最大金额,从谷歌的180到Android的50。
我将由您来决定如何围绕这些限制实现逻辑,但我将提供一个实现示例。请注意,此实现使用js-cookies来操作cookie。我们还在服务器端使用了cookie-parser中间件。
浏览器JS

const productIDs = ["b0708d2c-fe46-4251-96e0-1cfb3cd05eb0", "244d1e73-b5b4-4c59-8d4e-006fc1b190fe"];
const size = new TextEncoder().encode(JSON.stringify(productIDs)).length;
const chunkSize = Math.floor(1024 * 3.5);
const segments = Math.ceil(size / chunkSize);
const currentCookies = Object.keys(Cookies.get()).length;

if (segments + currentCookies <= 50){
    let payloads = [];
    for (let segment = 0; segment < segments; segment++){
        const payloadLength = Math.ceil(productIDs.length / segments);
        const startPosition = segment * payloadLength;
        payloads[segment] = productIDs.slice(startPosition, startPosition + payloadLength);
    }

    for (const [index, payload] of Object.entries(payloads)){
        Cookies.set(`qp[${index}]`, JSON.stringify(payload));
    }

    const source = new EventSource("start-processing");
    source.addEventListener("connected", () => {
        for (const [index, payload] of Object.entries(payloads)){
            Cookies.remove(`qp[${index}]`);
        }
    });
}else{
    console.log(`ERROR: Cookie limit was reached.`);
}

字符串
如您所见,我们将chunkSize设置为3584字节。这似乎是一个相当安全的填充量,但你可以调整它,以适应您的需要。请记住,此方法将在所有有效负载数组中均匀分布数组值。这意味着你不太可能精确地达到那个chunkSize值。
您可以将最大cookie从50调整到任何您想要的。如果你不关心移动的或传统,只是想支持现代桌面浏览器,150是支持Firefox的最低要求。查看this page以了解有关浏览器限制的更多信息。
在我们将productID拆分到它们自己的单独数组中之后,我们为每个块生成cookie。在本例中,我将其标记为qp,并在其后跟随一个索引到该块的[0]标识符。
最后,我们启动事件流。您可能希望有一个作为即时响应发生的事件,以便您的浏览器知道要清除新创建的Cookie。在某个地方设置一个独立的函数来清除这些cookie也是明智的,以防响应永远不会到来,或者浏览器在响应通过之前被刷新。
我建议将以下代码添加到上述代码块的开头,以便清理可能遗留的任何先前数据。

for (const cookie of Object.keys(Cookies.get()).filter(x => x.startsWith("qp"))){
    Cookies.remove(cookie);
}


服务器端JS(Express)

async (request, response) => {
    response.writeHead(200, {
        'Content-Type': 'text/event-stream', 
        'Connection': 'keep-alive',
        'Cache-Control': 'no-cache'
    });

    const productIDs = Object.entries(request.cookies).filter(([name, value]) => name.startsWith("qp")).map(([index, value]) => JSON.parse(value)).flat();

    const sendEvent = (event, data) => response.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
            
    sendEvent("connected", {});
}


很简单的东西在这里。那条相当长的一条线是一种非常简单的方法,可以获得所有的数组,并将它们展平成一个数组。只要确保startsWith("qp")部分设置为您的cookie名称,并确保您网站上的其他cookie都不会以该字符串开头。
sendEvent()是一个非常简单的响应客户端的函数。只需确保立即响应connected事件,以确保cookie被正确清理。
就这样!这确实是一个简单的方法,虽然它在cookie限制方面有其缺陷,但我觉得预先警告的开发人员将能够判断是否使用该技术。一旦成为一个易于重用的函数,在任何需要的地方实现它都是小菜一碟。

相关问题