我正在使用PHP中的cURL实现,并利用cURL_multi_init()和cURL_multi_exec()并行执行一批批请求。我已经做了一段时间了,并且理解其中的这一部分
但是,请求主体包含一个签名,该签名是用时间戳计算的。从生成此签名的那一刻起,在服务器拒绝请求之前,我发出请求的时间窗口是有限的。大多数时候这都很好。然而,在某些情况下,我需要进行大量上传(5+GB)。
如果我将请求批处理到一个由100、200、1000、20000或介于两者之间的池中,并且我正在将大量数据上传到服务器,那么执行的初始请求将成功完成。然而,在签名中的时间戳到期之前,稍后的请求不会启动,因此服务器会立即拒绝这些请求。
我使用的电流是这样的:
- 提前进行任何处理
- 将尚未执行的cURL句柄添加到批处理中
- 让cURL处理所有请求的执行
- 查看返回的数据并对其进行解析
我感兴趣的是找到一种执行回调函数的方法,该函数可以按需生成签名,并在PHP/cURL执行特定请求时更新请求体。我知道您可以将回调函数绑定到cURL句柄,该句柄将在请求发生时重复执行,并且您可以一直访问cURL句柄。
所以我的问题是:有没有办法为cURL句柄配置onBefore和/或onAfter回调?可以在cURL执行请求之前立即执行的东西,然后可以在响应返回之后立即执行的事情,以便可以解析响应数据。
我想做一些更面向事件的事情,比如:
- 向批处理中添加一个尚未执行的cURL句柄,在cURL(而不是我自己)执行请求(之前和之后)时分配一个回调函数来执行
- 获取批处理请求的结果,并对数据执行我想要的任何操作
不,这在cURL的内置函数中是不可能的。然而,围绕本机函数实现一个包装器来做您想要做的事情是微不足道的。
例如,模糊地实现观察者模式:
<?php
class CurlWrapper {
private $ch;
private $listeners;
public function __construct($url) {
$this->ch = curl_init($url);
$this->setopt(CURLOPT_RETURNTRANSFER, true);
}
public function setopt($opt, $value) {
$this->notify('setopt', array('option' => $opt, 'value' => $value));
curl_setopt($this->ch, $opt, $value);
}
public function setopt_array($opts) {
$this->notify('setopt_array', array('options' => $opts));
curl_setopt_array($this->ch, $opts);
}
public function exec() {
$this->notify('beforeExec', array());
$ret = curl_exec($this->ch);
$this->notify('afterExec', array('result' => $ret));
return $ret;
}
public function attachListener($event, $fn) {
if (is_callable($fn)) {
$this->listeners[$event][] = $fn;
}
}
private function notify($event, $data) {
if (isset($this->listeners[$event])) {
foreach ($this->listeners[$event] as $listener) {
$listener($this, $data);
}
}
}
}
$c = new CurlWrapper('http://stackoverflow.com');
$c->setopt(CURLOPT_HTTPGET, true);
$c->attachListener('beforeExec', function($handle, $data) {
echo "before exec'n";
});
$result = $c->exec();
echo strlen($result), "'n";
您可以使用addListener
将事件侦听器(必须是可调用的)添加到对象中,它们将在相关时刻自动被调用。
显然,你需要做更多的工作来满足你的需求,但我认为这不是一个糟糕的开始。
任何与cURL有关的东西都不是高级PHP。这是"高级杂耍"。
如果你有大量的数据通过cURL,我建议你根本不使用cURL(实际上,我总是建议你不要使用cURL)
我会研究套接字实现。好的不容易找到,但自己写也没那么难。
好吧,你说请求是并行的,我不确定这到底意味着什么,但这并不太重要。
顺便说一句,我将解释我所说的异步是什么意思。如果打开一个原始TCP套接字,则可以在连接上调用socket_set_blocking函数,这意味着读/写操作不会阻塞。您可以使用其中的几个连接,并在循环中向每个连接写入少量数据,这样您就可以"一次"发送请求。
我问你是否必须等到整个消息被消费后,端点才能验证签名,原因是即使Curl"一次发送所有请求",上传所需的时间也总是有可能意味着验证失败。大概一次上传2000个请求比上传5个慢,所以你会认为前一种情况会有更多的失败吗?类似地,如果您的请求是同步处理的(即一次一个),那么您会因为相同的原因看到相同的错误,尽管在这种情况下,预计稍后的请求会失败。也许你需要考虑在特定的时间范围内上传特定大小的消息所需的数据上传速率,然后尝试计算最佳的多有效载荷大小。也许最好的方法是最简单的:一次上传一个,并在每次上传之前计算签名?
更好的方法可能是将签名放在消息头中,这样可以在上传过程中更早地读取签名。