여러 큐 작업자에서 실행되며 Guzzle을 사용하는 일부 HTTP 요청이 포함 된 작업이 있습니다. 그러나 GuzzleHttp\Exception\RequestException
백그라운드 작업에서 이러한 작업을 실행할 때이 작업 내의 try-catch 블록이 나타나지 않는 것 같습니다 . 실행중인 프로세스는 php artisan queue:work
큐를 모니터하고 작업을 선택하는 Laravel 큐 시스템 작업자입니다.
대신, 예외는 GuzzleHttp\Promise\RejectionException
다음 메시지 중 하나입니다 .
cURL 오류 28 : 0 바이트가 수신 된 30001 밀리 초 후에 작업 시간이 초과되었습니다 ( https://curl.haxx.se/libcurl/c/libcurl-errors.html 참조 ).
이것은 실제로 위장되어 있습니다 GuzzleHttp\Exception\ConnectException
( https://github.com/guzzle/promises/blob/master/src/RejectionException.php#L22 참조 ). 정규적인 PHP 프로세스에서 비슷한 작업을 실행하면 URL, ConnectException
메시지에 의도 한대로 얻습니다 .
cURL 오류 28 : 0 바이트 중 0 개를 수신하여 100 밀리 초 후에 작업 시간이 초과되었습니다 ( 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.
}
위의 코드 는 작업자 프로세스에서 RejectionException
또는 실행될 때 ConnectException
항상 ConnectException
브라우저를 통해 수동으로 테스트 할 때 (내가 알 수있는 것에서)를 던집니다 .
그래서 기본적으로 내가 얻는 것은 이것이 RejectionException
메시지를 래핑하고 ConnectException
있지만 Guzzle의 비동기 기능을 사용하지 않는다는 것입니다. 내 요청은 단순히 연속적으로 이루어집니다. 유일한 차이점은 여러 PHP 프로세스가 Guzzle HTTP 호출을 수행하거나 작업 자체가 시간 초과되고 있다는 것입니다 (Laravel과 다른 예외가 발생해야 함 Illuminate\Queue\MaxAttemptsExceededException
). 그러나 이것이 어떻게 코드가 다르게 작동하는지 알 수 없습니다.
브라우저 트리거와 달리 CLI에서 실행할 때 php_sapi_name()
/를 사용하여 PHP_SAPI
(사용 된 인터페이스를 결정하는) Guzzle 패키지 내에서 코드를 찾지 못했습니다 .
tl; dr
왜 Guzzle이 RejectionException
작업자 프로세스에서 나를 던지지 만 ConnectException
브라우저를 통해 트리거되는 일반 PHP 스크립트 에서는 왜 발생합니까?
편집 1
안타깝게도 최소한의 재현 가능한 예를 만들 수는 없습니다. Sentry 이슈 트래커에 많은 오류 메시지가 표시되는데 위에 표시된 정확한 예외가 있습니다. 소스는 라 Starting Artisan command: horizon:work
라벨 호라이즌 (Laravel Horizon)이며 라 라벨 큐를 감독합니다. PHP 버전간에 불일치가 있는지 다시 확인했지만 웹 사이트와 작업자 프로세스 모두 동일한 PHP 7.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 버전은
cURL 7.58.0
입니다. - Guzzle 버전은
guzzlehttp/guzzle 6.5.2
- 라 라벨 버전은
laravel/framework 6.12.0
편집 2 (스택 추적)
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가 포함되어 있습니다 $client->request($request['method'], $request['url'], $request['options']);
(따라서 사용하지 마십시오 requestAsync()
). 나는이 문제를 일으키는 작업을 병렬로 실행하는 것과 관련이 있다고 생각합니다.
편집 3 (해결 방법을 찾았습니다)
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->getMessage())
자체 작성하는 호출이었습니다 RejectionException
. 전화 rejection_for($e)
는 올바른 해결책이었습니다. 이 rejection_for
함수가 단순과 동일한 경우에만 대답해야합니다 throw $e
.
HandlerStack
있습니까 (힌트 :) ?