我有在多个队列工作器上运行的作业,其中包含一些使用Guzzle的HTTP请求。但是,当我在后台进程中运行这些作业时,此作业中的try-catch块似乎不会拾取GuzzleHttp\Exception\Request estException
。正在运行的进程是一个php artisan队列:work
,它是一个Laravel队列系统工作器,用于监控队列并拾取作业。
相反,抛出的异常是带有以下消息的GuzzleHttp\Promise\RejtionException
之一:
promise被拒绝,原因如下:cURL 错误 28:操作在 30001 毫秒后超时,收到 0 个字节(请参阅 https://curl.haxx.se/libcurl/c/libcurl-errors.html)
这实际上是一个伪装的GuzzleHttp\Exception\ConnectException
(参见https://github.com/guzzle/promises/blob/master/src/RejectionException.php#L22),因为如果我在由访问URL触发的常规PHP进程中运行类似的作业,我确实会得到ConnectException
,正如消息所预期的那样:
cURL错误28:操作在100毫秒后超时,收到0个字节中的0个(参见https://curl.haxx.se/libcurl/c/libcurl-errors.html)
try {
$c = new \GuzzleHttp\Client([
'timeout' => 0.1
]);
$response = (string) $c->get('https://example.com')->getBody();
} catch(GuzzleHttp\Exception\RequestException $e) {
// This occasionally gets catched when a ConnectException (child) is thrown,
// but it doesnt happen with RejectionException because it is not a child
// of RequestException.
}
当在工作进程中运行时,上面的代码会抛出拒绝异常
或ConnectException
,但在通过浏览器手动测试时总是抛出ConnectException
(据我所知)。
因此,基本上我得出的结论是,这个RejectiveException
包装了来自ConnectException
的消息,但是我没有使用Guzzle的异步功能。我的请求只是串联完成的。唯一不同的是,多个PHP进程可能正在进行Guzzle HTTP调用,或者作业本身正在超时(这应该会导致一个不同的例外,即Laravel的Illuminate\Queue\MaxAttemptsExceedException
),但我不明白这如何导致代码的行为不同。
在Guzzle软件包中,我找不到任何代码使用<code>php_sapi_name()/<code>php\u sapi(确定使用的接口)在从CLI运行时执行不同的东西,而不是浏览器触发器。
为什么Guzzle会在我的工作进程上抛出RejectionException,而在通过浏览器触发的常规PHP脚本上抛出ConnectException?
遗憾的是,我无法创建一个最小的可重复示例。我在我的哨兵问题跟踪器中看到了许多错误消息,上面显示了一个例外。源代码称为启动Artisan命令:horizon:work
(这是Laravel horizon,它监督Laravel队列)。我再次检查了PHP版本之间是否存在差异,但网站和工作进程都运行相同的PHP7.3.14
,这是正确的:
PHP 7.3.14-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: Jan 23 2020 13:59:16) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.14, Copyright (c) 1998-2018 Zend Technologies
with Zend OPcache v7.3.14-1+ubuntu18.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies
cURL 7.58.0
GuzzleHttp\Promise\RejectionException: The promise was rejected with reason: cURL error 28: Operation timed out after 30000 milliseconds with 0 bytes received (see https://curl.haxx.se/libcurl/c/libcurl-errors.html)
#44 /vendor/guzzlehttp/promises/src/functions.php(112): GuzzleHttp\Promise\exception_for
#43 /vendor/guzzlehttp/promises/src/Promise.php(75): GuzzleHttp\Promise\Promise::wait
#42 /vendor/guzzlehttp/guzzle/src/Client.php(183): GuzzleHttp\Client::request
#41 /app/Bumpers/Client.php(333): App\Bumpers\Client::callRequest
#40 /app/Bumpers/Client.php(291): App\Bumpers\Client::callFunction
#39 /app/Bumpers/Client.php(232): App\Bumpers\Client::bumpThread
#38 /app/Models/Bumper.php(206): App\Models\Bumper::post
#37 /app/Jobs/PostBumper.php(59): App\Jobs\PostBumper::handle
#36 [internal](0): call_user_func_array
#35 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(32): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}
#34 /vendor/laravel/framework/src/Illuminate/Container/Util.php(36): Illuminate\Container\Util::unwrapIfClosure
#33 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(90): Illuminate\Container\BoundMethod::callBoundMethod
#32 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(34): Illuminate\Container\BoundMethod::call
#31 /vendor/laravel/framework/src/Illuminate/Container/Container.php(590): Illuminate\Container\Container::call
#30 /vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(94): Illuminate\Bus\Dispatcher::Illuminate\Bus\{closure}
#29 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(130): Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
#28 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(105): Illuminate\Pipeline\Pipeline::then
#27 /vendor/laravel/framework/src/Illuminate/Bus/Dispatcher.php(98): Illuminate\Bus\Dispatcher::dispatchNow
#26 /vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(83): Illuminate\Queue\CallQueuedHandler::Illuminate\Queue\{closure}
#25 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(130): Illuminate\Pipeline\Pipeline::Illuminate\Pipeline\{closure}
#24 /vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(105): Illuminate\Pipeline\Pipeline::then
#23 /vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(85): Illuminate\Queue\CallQueuedHandler::dispatchThroughMiddleware
#22 /vendor/laravel/framework/src/Illuminate/Queue/CallQueuedHandler.php(59): Illuminate\Queue\CallQueuedHandler::call
#21 /vendor/laravel/framework/src/Illuminate/Queue/Jobs/Job.php(88): Illuminate\Queue\Jobs\Job::fire
#20 /vendor/laravel/framework/src/Illuminate/Queue/Worker.php(354): Illuminate\Queue\Worker::process
#19 /vendor/laravel/framework/src/Illuminate/Queue/Worker.php(300): Illuminate\Queue\Worker::runJob
#18 /vendor/laravel/framework/src/Illuminate/Queue/Worker.php(134): Illuminate\Queue\Worker::daemon
#17 /vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(112): Illuminate\Queue\Console\WorkCommand::runWorker
#16 /vendor/laravel/framework/src/Illuminate/Queue/Console/WorkCommand.php(96): Illuminate\Queue\Console\WorkCommand::handle
#15 /vendor/laravel/horizon/src/Console/WorkCommand.php(46): Laravel\Horizon\Console\WorkCommand::handle
#14 [internal](0): call_user_func_array
#13 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(32): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}
#12 /vendor/laravel/framework/src/Illuminate/Container/Util.php(36): Illuminate\Container\Util::unwrapIfClosure
#11 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(90): Illuminate\Container\BoundMethod::callBoundMethod
#10 /vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(34): Illuminate\Container\BoundMethod::call
#9 /vendor/laravel/framework/src/Illuminate/Container/Container.php(590): Illuminate\Container\Container::call
#8 /vendor/laravel/framework/src/Illuminate/Console/Command.php(201): Illuminate\Console\Command::execute
#7 /vendor/symfony/console/Command/Command.php(255): Symfony\Component\Console\Command\Command::run
#6 /vendor/laravel/framework/src/Illuminate/Console/Command.php(188): Illuminate\Console\Command::run
#5 /vendor/symfony/console/Application.php(1012): Symfony\Component\Console\Application::doRunCommand
#4 /vendor/symfony/console/Application.php(272): Symfony\Component\Console\Application::doRun
#3 /vendor/symfony/console/Application.php(148): Symfony\Component\Console\Application::run
#2 /vendor/laravel/framework/src/Illuminate/Console/Application.php(93): Illuminate\Console\Application::run
#1 /vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(131): Illuminate\Foundation\Console\Kernel::handle
#0 /artisan(37): null
Client::callRequest()
函数只包含一个 Guzzle 客户端,我称之为 $client-
考虑下面的testcase,它发出一个HTTP请求(应该返回一个常规的200响应):
try {
$c = new \GuzzleHttp\Client([
'base_uri' => 'https://example.com'
]);
$handler = $c->getConfig('handler');
$handler->push(\GuzzleHttp\Middleware::mapResponse(function(ResponseInterface $response) {
// Create a fake connection exception:
$e = new \GuzzleHttp\Exception\ConnectException('abc', new \GuzzleHttp\Psr7\Request('GET', 'https://example.com/2'));
// These 2 lines both cascade as `ConnectException`:
throw $e;
return \GuzzleHttp\Promise\rejection_for($e);
// This line cascades as a `RejectionException`:
return \GuzzleHttp\Promise\rejection_for($e->getMessage());
}));
$c->get('');
} catch(\Exception $e) {
var_dump($e);
}
现在我最初做的是调用
rejection_for($e-
在评论区与作者讨论,作为我回答的开始:
问题:
您是否有定制的guzzle中间件(提示:HandlerStack)?
作者的回答:
是的,各种各样。但中间件基本上是一个请求/响应修改器,即使我在那里发出的大量请求也是同步完成的。
根据这个,这是我的论文:
您的一个中间件中有一个超时,称为guzzle。所以,让我们尝试实现一个可重复的案例。
这里我们有一个自定义中间件,它调用guzzle并返回一个拒绝失败以及子调用的异常消息。这很棘手,因为由于内部的错误处理,它在stack-trace中是不可见的。
function custom_middleware(string $baseUri = 'http://127.0.0.1:8099', float $timeout = 0.2)
{
return function (callable $handler) use ($baseUri, $timeout) {
return function ($request, array $options) use ($handler, $baseUri, $timeout) {
try {
$client = new GuzzleHttp\Client(['base_uri' => $baseUri, 'timeout' => $timeout,]);
$client->get('/a');
} catch (Exception $exception) {
return \GuzzleHttp\Promise\rejection_for($exception->getMessage());
}
return $handler($request, $options);
};
};
}
这是一个如何使用它的测试示例:
$baseUri = 'http://127.0.0.1:8099'; // php -S 127.0.0.1:8099 test.php << includes a simple sleep(10); statement
$timeout = 0.2;
$handler = \GuzzleHttp\HandlerStack::create();
$handler->push(custom_middleware($baseUri, $timeout));
$client = new Client([
'handler' => $handler,
'base_uri' => $baseUri,
]);
try {
$response = $client->get('/b');
} catch (Exception $exception) {
var_dump(get_class($exception), $exception->getMessage());
}
一旦我对此进行了测试,我就会收到
$ php test2.php
string(37) "GuzzleHttp\Promise\RejectionException"
string(174) "The promise was rejected with reason: cURL error 28: Operation timed out after 202 milliseconds with 0 bytes received (see https://curl.haxx.se/libcurl/c/libcurl-errors.html)"
所以看起来你的主狂饮呼叫失败了,但实际上是子呼叫失败了。
如果这有助于您确定您的具体问题,请告诉我。如果你能分享你的中间件来进一步调试,我将非常感激。
Guzz对同步和异步请求都使用Promises。唯一的区别是,当您使用同步请求(您的情况)时-它会通过调用etc()
方法立即实现。注意这部分:
对已拒绝的promise调用等待
将引发html" target="_blank">异常。如果拒绝原因是 \Exception
的实例,则引发原因。否则,将引发 GuzzleHttp\Promise\RejectException
,并且可以通过调用异常的 getReason
方法获取原因。
因此,它抛出 RequestException
,这是 \Exception
的一个实例,它总是发生在 4xx 和 5xx HTTP 错误上,除非通过选项禁用引发异常。如您所见,如果原因不是 \Exception
的实例,例如,如果原因是一个字符串,在您的案例中似乎发生,它也可能引发拒绝异常
。奇怪的是,你得到的是RejectException
而不是RequestException,
因为Guzzle在连接超时错误时会抛出ConnectException
。无论如何,如果你在 Sentry 中遍历了 RejectException
堆栈跟踪,并找到在 Promise 上调用 reject()
方法的位置,你可能会找到一个原因。
您好,我想知道您是否有错误4xx或错误5xx
但即便如此,我还是会为找到的类似你的问题的解决方案提供一些替代方案
备选方案1
我想取消这个,我有这个问题,一个新的生产服务器返回了意外的400个响应,与开发和测试环境的预期工作相比;简单安装apt安装php7.0-curl就搞定了。
这是一个全新的Ubuntu 16.04 LTS安装,通过ppa: ondrej/php安装了php,在调试过程中我注意到标头不同。两者都在发送一个包含chucked数据的多部分表单,但是如果没有php7.0-curl,它会发送一个Connection:关闭标头,而不是预期:100-继续;这两个请求都有Transfer-Encode: chunked。
也许你应该试试这个
try {
$client = new Client();
$guzzleResult = $client->put($url, [
'body' => $postString
]);
} catch (\GuzzleHttp\Exception\RequestException $e) {
$guzzleResult = $e->getResponse();
}
var_export($guzzleResult->getStatusCode());
var_export($guzzleResult->getBody());
如果响应代码不是200,Guzzle需要cacching
备选案文3
在我的例子中,因为我在请求的$options['json']中传递了一个空数组,所以即使传递内容类型:application/json请求头,我也无法使用Postman或cURL在服务器上复制500。
无论如何,从请求的选项数组中删除json键解决了这个问题。
我花了30分钟试图找出问题所在,因为这种行为非常不一致。对于我提出的所有其他请求,传递$options['json']=[]不会引起任何问题。这可能是服务器问题,因为我无法控制服务器。
发送关于获得的详细信息的反馈
我在服务层的spring-boot应用程序中使用了Hystrix(Camden.sr7版本),而没有回退方法。Service的方法之一如下所示: 对于这样的响应,不清楚实际上是从哪个方法抛出异常的。如果我将版本更改为brixton.sr5(以前的版本),它将返回清晰的响应: 因此Hystrix的新版本(实际上是spring-cloud-dependencies的新版本)不会抛出HystrixRun
既然我们可以在Javascript中使用关键字抛出任何东西,那么我们就不能直接抛出一个错误消息字符串吗? 有人知道这里面有什么陷阱吗? 让我对此添加一些背景:在JavaScript世界中,人们通常依赖参数检查而不是使用try-catch机制,因此只使用抛出致命错误是有意义的。不过,为了能够捕捉一些系统错误,我必须为我自己的错误使用一个不同的类,而不是创建错误的子类,我认为我应该只使用String。
问题内容: 我的目标很简单:启动rsync并不要等待。 Debian上的Python 2.7.9 样例代码: (我之所以注释掉执行命令,只是因为我实际上将所有试验都保留在代码中,以便我知道自己已经做过什么以及尚未完成什么。显然,我会以正确的方式运行脚本行未注释。) 这是怎么回事…我可以在服务器上观看传输,完成传输后,我会在屏幕上看到“完成”字样。 我想发生的是在发出命令并立即开始传输后立即打印“完
问题内容: 我有一个需要使用Jackson从JSON反序列化的类。类结构如下所示: 反序列化对象通常效果很好;除了,它与错误代码互操作,当列表为空时,错误代码发出错误的值。也就是说,不是发出: 它发出: 杰克逊遇到以下情况时会抛出此异常: 当然,所有这些都是有道理的。输入错误。 但是,这种情况下的空列表与任何代码都不相关;如果是的话,null我不在乎。null如果无法反序列化该属性,是否有任何方法
我有一个类命名为,所以当我调用抛出一个而不是。 注意-我已将大写字母改为小写字母 当我使用完全不同的字符串ABC我得到作为预期。 请在下面找到代码片段。 // 输出: [![Java8机器上的输出][1][1] 这是不重复的,因为我正在寻找不同的例外情况下的名称更改方案,请再次检查