从 PHP 发送 HTTP 请求而不等待响应?
Posted
技术标签:
【中文标题】从 PHP 发送 HTTP 请求而不等待响应?【英文标题】:Send HTTP request from PHP without waiting for response? 【发布时间】:2012-12-30 20:40:30 【问题描述】:我想要从 php 发送一个 HTTP GET 请求。示例:
http://tracker.example.com?product_number=5230&price=123.52
这个想法是做服务器端的网络分析:而不是发送跟踪 信息从 javascript 到服务器,服务器发送跟踪 信息直接发送到另一台服务器。
要求:
请求应该花费尽可能少的时间,以便不 明显延迟 PHP 页面的处理。
tracker.example.com
的回复不需要是
检查。例如,一些可能的回应来自
tracker.example.com
:
200:没关系,但不需要检查。
404: 运气不好,但是 - 再次 - 不需要检查。
301: 虽然重定向是合适的,但它会延迟 PHP页面的处理,所以不要这样做。
简而言之:所有响应都可以被丢弃。
解决方案的想法:
在现已删除的答案中,有人建议调用命令行 curl 在 shell 进程中来自 PHP。这似乎是个好主意, 只是我不知道是否在下分叉了很多shell进程 负重是明智之举。
我找到了php-ga,一个做服务器端谷歌的包 来自 PHP 的分析。在项目的页面上,它是 提到:“可以配置为 [...] 使用非阻塞请求。” 到目前为止我还没有找到时间研究什么方法 php-ga 内部使用,不过这个方法可以!
简而言之:做通用服务器端的最佳解决方案是什么 来自 PHP 的跟踪/分析。
【问题讨论】:
madflow:因为在跟踪中不应减慢页面加载时间。如果跟踪器失败,那么运气不好 - 但不需要每次都检查。 @Will:请注意,我要求的是一个方法,而不一定是一个包。例如,php-ga 用于执行非阻塞请求的方法可能也适用于跟踪解决方案,例如在我的情况下,不基于 Google Analytics。 @Will:有一个可以确定的答案,但这需要更多关于可能的性能问题的讨论。不幸的是,它已被删除。这个想法是从 PHP 调用命令行curl
。如果有链接,我相信它可以删除。 curl
是标准 UNIX Web 服务器工具链的一部分(顺便说一下,curllib
可以通过 PHP 访问)。此外,一旦我找到时间,我想研究一下 php-ga 并研究用于非阻塞请求的 method。但也许 Stack Overflow 上的某个人知道这一点?
@feklee 抱歉让您久等了,做出了回答(对内容不是很自豪)。如果您愿意,我可以编辑并提供更多信息(可能是一些代码示例)。我从周五晚上开始回答,但最终让位于大量信息(以一种不太结构化的方式)。
可能有用:segment.io/blog/how-to-make-async-requests-in-php 和 Asynchronous PHP calls?/(也许第二个甚至是重复的?以后会看更多。)
【参考方案1】:
不幸的是,PHP 的定义是阻塞。虽然这适用于您通常要处理的大多数功能和操作,但当前情况有所不同。
我喜欢称之为HTTP-Ping 的过程要求您只触摸一个特定的URI,强制特定的服务器引导它的内部逻辑。某些功能允许您通过不等待响应来实现与此 HTTP-ping 非常相似的功能。
请注意,ping url 的过程是一个两步过程:
-
解析 DNS
提出请求
虽然在解析 DNS 并建立连接后发出请求应该相当快,但没有很多方法可以让 DNS 解析得更快。
进行 http-ping 的一些方法是:
-
cURL,通过将 CONNECTION_TIMEOUT 设置为低值
fsockopen写完后立即关闭
stream_socket_client(与 fsockopen 相同)并添加
STREAM_CLIENT_ASYNC_CONNECT
虽然cURL
和fsockopen
在解析DNS 时都处于阻塞状态。我注意到 fsockopen 明显更快,即使在最坏的情况下也是如此。
另一方面,stream_socket_client
应该解决有关 DNS 解析的问题,并且应该是这种情况下的最佳解决方案,但我还没有设法让它工作。
一个最终的解决方案是启动另一个为您执行此操作的线程/进程。对此进行系统调用应该可以工作,但forking 当前进程也应该这样做。不幸的是,在您无法控制 PHP 运行环境的应用程序中,两者都不是真正安全的。
系统调用经常被阻止,默认情况下不启用 pcntl。
【讨论】:
其实我可以通过指定IP来避免DNS查找。只是我还没有想到。 @feklee 想在帖子中提及它,但接近尾声时我已经想知道您在服务器上拥有哪些访问权限。不过要小心 DNS 更改! 我不完全理解访问权限与指定 IP 有什么关系(除此之外我也可以在/etc/hosts
中指定它),但我确实拥有完全访问权限(root)。无论如何,一个通用的答案显然最适合 Stack Overflow。
Khez 我不得不说这是一个很好的解决方案。它甚至可能在没有插件的情况下在 php 中实现非常基本的异步
@DaMaxContent 好吧,异步已经是 hack 规范的一部分,如果 PHP 7.x 会采用它,我不会感到惊讶......但我很高兴你找到了“漂亮的解决方案” " :D【参考方案2】:
我会这样调用 tracker.example.com:
get_headers('http://tracker.example.com?product_number=5230&price=123.52');
在跟踪脚本中:
ob_end_clean();
ignore_user_abort(true);
ob_start();
header("Connection: close");
header("Content-Length: " . ob_get_length());
ob_end_flush();
flush();
// from here the response has been sent. you can now wait as long as you want and do some tracking stuff
sleep(5); //wait 5 seconds
do_some_stuff();
exit;
【讨论】:
ignore_user_abort()
需要一个值(true),否则它只会返回其当前值。
它在我的本地 wamp 服务器上运行良好,但在我的服务器上却不行。我认为我的服务器需要配置【参考方案3】:
我实现了快速GET请求到url而不等待响应的功能:
function fast_request($url)
$parts=parse_url($url);
$fp = fsockopen($parts['host'],isset($parts['port'])?$parts['port']:80,$errno, $errstr, 30);
$out = "GET ".$parts['path']." HTTP/1.1\r\n";
$out.= "Host: ".$parts['host']."\r\n";
$out.= "Content-Length: 0"."\r\n";
$out.= "Connection: Close\r\n\r\n";
fwrite($fp, $out);
fclose($fp);
【讨论】:
如果您想在请求中添加查询参数,请将“GET”行更改为:$out = "GET ".$parts['path'] . "?" . $parts['query'] . " HTTP/1.1\r\n";
【参考方案4】:
我们使用 fsockopen
和 fwrite
组合,然后它在一天停止工作。或者它是一种间歇性的。经过一些研究和测试,如果你启用了 fopen 包装器,我最终使用了 file_get_contents
和 stream_context_create
函数,超时设置为 100 秒。 timeout 参数可以接收浮点值(https://www.php.net/manual/en/context.http.php)。我将它包装在一个 try...catch 块中,这样它就会默默地失败。它非常适合我们的目的。如果需要,您可以在 catch 中记录内容。如果您不希望函数阻塞运行时,超时是关键。
function fetchWithoutResponseURL( $url )
$context = stream_context_create([
"http" => [
"method"=>"GET",
"timeout" => .01
]
]
);
try
file_get_contents($url, 0, $context);
catch( Exception $e )
// Fail silently
【讨论】:
如果 SSL 握手和其他连接准备步骤未在 10 毫秒内完成,则将无法启动连接。实际上,如果您将目标脚本中的一段代码作为目标,并且它无法在这个有限的时间范围内运行,那么所有依赖于连接超时的解决方案都会失败。为确保目标脚本完全运行,您必须等到连接关闭。【参考方案5】:您可以使用shell_exec
和命令行 curl。
例如,见this question
【讨论】:
这看起来是一个不错的解决方案。只是我想知道当许多用户访问页面时产生大量 shell 进程是否会出现问题。 你能充实一下这个答案吗? (参见对问题所做的修改)【参考方案6】:在研究类似问题时来到这里。如果您有一个方便的数据库连接,另一种可能性是将请求详细信息快速填充到一个表中,然后有一个单独的基于 cron 的进程定期扫描该表以查找要处理的新记录,并发出跟踪请求,从而释放您的 Web 应用程序不必自己发出 HTTP 请求。
【讨论】:
实际上,在有问题的应用程序中有类似的东西。但是,它不是基于 Cron 的(我尽量避免使用 Cron)。它是一个从数据库读取的 URL 下载的守护程序,在无限循环中。但是,对于给定的用例,使用该守护程序和相关数据库感觉有点过头了。【参考方案7】:对于那些使用 wordrpess 作为后端的人 - 很简单:
wp_remote_get( $url, array(blocking=>false) );
【讨论】:
谢谢,这是一个非常好的提示,正是我想要的!提交一封电子邮件以通过 CURL 通过 Autopilot 发送需要 2 到 12 秒,但使用这种方法可以将时间始终缩短到 2 秒左右。当然,这意味着我们看不到 POST 请求的结果——但无论如何,我从来没有对 Autopilot 的响应做任何事情,所以这对我来说无关紧要。【参考方案8】:<?php
// Create a stream
$opts = array(
'http'=>array(
'method'=>"GET",
'header'=>"Accept-language: en"
)
);
$context = stream_context_create($opts);
// Open the file using the HTTP headers set above
$file = file_get_contents('http://tracker.example.com?product_number=5230&price=123.52', false, $context);
?>
【讨论】:
或许可以解释一下您的代码是如何工作的,以及为什么它可以满足提问者的需求? 我很确定这会完成整个请求并在继续之前等待响应,不是吗?【参考方案9】:您实际上可以直接使用CURL
来执行此操作。
我已经使用非常短的超时 (CURLOPT_TIMEOUT_MS
) 和/或使用 curl_multi_exec
来实现它。
请注意:最终我退出了此方法,因为并非每个请求都正确发出。这可能是我自己的服务器造成的,虽然我无法排除 curl 失败的选项。
【讨论】:
请求失败是不是因为实际上必须在发送请求之前建立连接? AFAICS,对于手头的问题,将 CURLOPT_TIMEOUT_MS 设置为极低的值是不明智的。【参考方案10】:我需要做类似的事情,只需 ping 一个 url 并丢弃所有响应。我使用了 proc_open 命令,它可以让您使用 proc_close 立即结束进程。我假设你的服务器上安装了 lynx:
<?php
function ping($url)
$proc = proc_open("lynx $url",[],$pipes);
proc_close($proc);
?>
【讨论】:
等待命令完全执行以上是关于从 PHP 发送 HTTP 请求而不等待响应?的主要内容,如果未能解决你的问题,请参考以下文章
如何从 Django 发送异步 HTTP 请求并在 python2.7 中等待结果?