我已经阅读了很多关于使用jQuery取消POST请求的类似问题,但似乎没有一个与我的接近.
我有你的日常表单,有一个PHP页面作为一个动作:
<form action="results.php">
<input name="my-input" type="text">
<input type="submit" value="submit">
</form>
Run Code Online (Sandbox Code Playgroud)
处理results.php
在服务器端,基于形式给出的职位信息,需要很长的时间(30秒甚至更多,我们预计同比增长,因为我们的搜索空间将在未来几周内增加以及).我们正在访问包含所有数据的Basex服务器(版本7.9,不可升级).用户生成的XPath代码以表单形式提交,然后操作URL将XPath代码发送到返回结果的Basex服务器.从可用性的角度来看,我已经显示了一个"加载"屏幕,因此用户至少知道正在生成结果:
$("form").submit(function() {
$("#overlay").show();
});
<div id="overlay"><p>Results are being generated</p></div>
Run Code Online (Sandbox Code Playgroud)
但是,我还想让用户选择按下按钮取消请求,并在用户关闭页面时取消请求.请注意,在前一种情况下(按钮单击),这也意味着用户应该保持在同一页面上,可以编辑他们的输入,并立即重新提交他们的请求.最重要的是,当他们取消请求时,他们也可以立即重新发送它:服务器应该真正中止,并且在能够处理新查询之前不完成查询.
我想到这样的事情:
$("form").submit(function() {
$("#overlay").show();
});
$("#overlay button").click(abortRequest);
$(window).unload(abortRequest);
function abortRequest() {
// abort correct request
}
<div id="overlay">
<p>Results are being generated</p>
<button>Cancel</button>
</div>
Run Code Online (Sandbox Code Playgroud)
但正如您所看到的,我并不完全确定如何填写abortRequest
以确保后续请求被中止和终止,以便可以发送新查询.请填写空白!或者我需要.preventDefault()
提交表单,而是从jQuery执行ajax()调用?
正如我所说,我也想停止进程服务器端,从我读到的我需要exit()
这个.但是我怎么能有exit
另外一个PHP函数呢?例如,假设results.php
我有一个处理脚本,我需要退出该脚本,我会这样做吗?
<?php
if (isset($_POST['my-input'])) {
$input = $_POST['my-input'];
function processData() {
// A lot of processing
}
processData()
}
if (isset($_POST['terminate'])) {
function terminateProcess() {
// exit processData()
}
}
Run Code Online (Sandbox Code Playgroud)
然后在需要终止进程时执行新的ajax请求?
$("#overlay button").click(abortRequest);
$(window).unload(abortRequest);
function abortRequest() {
$.ajax({
url: 'results.php',
data: {terminate: true},
type: 'post',
success: function() {alert("terminated");});
});
}
Run Code Online (Sandbox Code Playgroud)
我做了更多的研究,我找到了答案.它提到了connection_aborted()和session_write_close(),我并不完全确定哪个对我有用.我确实使用了SESSION变量,但是当取消进程时我不需要写出值(虽然我想保持SESSION变量处于活动状态).
这会是这样的吗?如果是这样,我如何让一个PHP函数终止另一个?
我也读过Websockets,它似乎可以工作,但我不喜欢设置Websocket服务器的麻烦,因为这需要我联系我们需要对新软件包进行大量测试的IT人员.我宁愿将它保留给PHP和JS,除了jQuery之外没有第三方库.
考虑到大多数评论和答案表明我想要的是不可能的,我也有兴趣听取替代方案.首先想到的是分页Ajax调用(类似于许多提供搜索结果,图像,无限卷轴中的内容的网页).向用户提供具有X个第一结果(例如20)的页面,并且当他们点击按钮"显示接下来的20个结果"时,将附加所示的那些.此过程可以继续,直到显示所有结果.因为它对用户获得所有结果很有用,所以我还将提供"下载所有结果"选项.这将花费很长时间,但至少用户应该能够在页面本身上完成第一个结果.(因此下载按钮不应该破坏Ajax页面加载.)这只是一个想法,但我希望它能给你们一些灵感.
Himel Nag Rana 演示了如何取消待处理的 Ajax 请求。正如我之前在另一篇文章中所讨论的,有几个因素可能会干扰和延迟后续请求。
TL;DR: 1. 尝试检测从长时间运行的任务本身中取消请求是非常不方便的,2. 作为解决方法,您应该session_write_close()
在长时间运行的任务中尽早关闭会话 ( ),以便以免阻止后续请求。
connection_aborted()
不能使用。该函数应该在长任务期间(通常在循环内)定期调用。不幸的是,在您的情况下只有一个重要的原子操作:对数据后端的查询。
如果您应用了 Himel Nag Rana 和我本人建议的程序,您现在应该能够取消 Ajax 请求并立即允许新的请求继续进行。唯一剩下的问题是之前的(取消的)请求可能会在后台继续运行一段时间(不会阻止用户,只是浪费服务器上的资源)。
该问题可以改写为“如何从外部中止特定进程”。
正如Christian Bonato 正确建议的那样,这是一个可能的实现。为了演示,我将依赖Symphony 的Process
组件,但如果您愿意,您可以设计一个更简单的自定义解决方案。
基本方法是:
生成一个新进程来运行查询,将 PID 保存在会话中。等待完成,然后将结果返回给客户端
如果客户端中止,它会向服务器发出信号以终止该进程。
<?php // query.php
use Symfony\Component\Process\PhpProcess;
session_start();
if(isset($_SESSION['queryPID'])) {
// A query is already running for this session
// As this should never happen, you may want to raise an error instead
// of just silently killing the previous query.
posix_kill($_SESSION['queryPID'], SIGKILL);
unset($_SESSION['queryPID']);
}
$queryString = parseRequest($_POST);
$process = new PhpProcess(sprintf(
'<?php $result = runQuery(%s); echo fetchResult($result);',
$queryString
));
$process->start();
$_SESSION['queryPID'] = $process->getPid();
session_write_close();
$process->wait();
$result = $process->getOutput();
echo formatResponse($result);
?>
Run Code Online (Sandbox Code Playgroud)
<?php // abort.php
session_start();
if(isset($_SESSION['queryPID'])) {
$pid = $_SESSION['queryPID'];
posix_kill($pid, SIGKILL);
unset($pid);
echo "Query $pid has been aborted";
} else {
// there is nothing to abort, send a HTTP error code
header($_SERVER['SERVER_PROTOCOL'] . ' 599 No pending query', true, 599);
}
?>
Run Code Online (Sandbox Code Playgroud)
// javascript
function abortRequest(pendingXHRRequest) {
pendingXHRRequest.abort();
$.ajax({
url: 'abort.php',
success: function() { alert("terminated"); });
});
}
Run Code Online (Sandbox Code Playgroud)
生成一个进程并跟踪它确实很棘手,这就是我建议使用现有模块的原因。通过 Composer集成一个 Symfony 组件应该相对容易:首先安装 Composer,然后安装 Process 组件 ( composer require symfony/process
)。
手动实现可能如下所示(请注意,这是未经测试的、不完整的并且可能不稳定,但我相信您会明白的):
<?php // query.php
session_start();
$queryString = parseRequest($_POST); // $queryString should be escaped via escapeshellarg()
$processHandler = popen("/path/to/php-cli/php asyncQuery.php $queryString", 'r');
// fetch the first line of output, PID expected
$pid = fgets($processHandler);
$_SESSION['queryPID'] = $pid;
session_write_close();
// fetch the rest of the output
while($line = fgets($processHandler)) {
echo $line; // or save this line for further processing, e.g. through json_encode()
}
fclose($processHandler);
?>
Run Code Online (Sandbox Code Playgroud)
<?php // asyncQuery.php
// echo the current PID
echo getmypid() . PHP_EOL;
// then execute the query and echo the result
$result = runQuery($argv[1]);
echo fetchResult($result);
?>
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
636 次 |
最近记录: |