From 5e5d284a9b5b75bd1ca61abbec8350f8b92072fb Mon Sep 17 00:00:00 2001 From: Zach Borboa Date: Mon, 21 Aug 2023 20:44:45 -0700 Subject: [PATCH] Implement Curl::setError() and MultiCurl::setError() --- README.md | 2 + examples/curl_set_error.php | 44 ++++++++++++++++++++ examples/multi_curl_set_error.php | 44 ++++++++++++++++++++ src/Curl/BaseCurl.php | 22 ++++++++++ src/Curl/Curl.php | 23 +++++++--- src/Curl/MultiCurl.php | 3 ++ tests/PHPCurlClass/PHPCurlClassTest.php | 31 ++++++++++++++ tests/PHPCurlClass/PHPMultiCurlClassTest.php | 35 ++++++++++++++++ 8 files changed, 199 insertions(+), 5 deletions(-) create mode 100644 examples/curl_set_error.php create mode 100644 examples/multi_curl_set_error.php diff --git a/README.md b/README.md index fac5ed8..288eee7 100644 --- a/README.md +++ b/README.md @@ -278,6 +278,7 @@ Curl::setDefaultTimeout() Curl::setDefaultUserAgent() Curl::setDefaultXmlDecoder() Curl::setDigestAuthentication($username, $password = '') +Curl::setError($function) Curl::setFile($file) Curl::setFollowLocation($follow_location = true) Curl::setForbidReuse($forbid_reuse = true) @@ -340,6 +341,7 @@ MultiCurl::setCookieJar($cookie_jar) MultiCurl::setCookieString($string) MultiCurl::setCookies($cookies) MultiCurl::setDigestAuthentication($username, $password = '') +MultiCurl::setError($function) MultiCurl::setFile($file) MultiCurl::setFollowLocation($follow_location = true) MultiCurl::setForbidReuse($forbid_reuse = true) diff --git a/examples/curl_set_error.php b/examples/curl_set_error.php new file mode 100644 index 0000000..0464f25 --- /dev/null +++ b/examples/curl_set_error.php @@ -0,0 +1,44 @@ +setRetry($max_retries); + +$curl->beforeSend(function ($instance) { + echo 'about to make request to ' . $instance->url . "\n"; +}); + +$curl->error(function ($instance) { + echo 'not lucky this round' . "\n"; +}); + +$curl->success(function ($instance) { + echo + 'success!' . "\n" . + 'got number ' . $instance->response->args->number . ' ' . + 'after ' . $instance->attempts . ' attempt(s).' . "\n"; +}); + +$curl->setError(function ($instance) { + $random_number = (int)$instance->response->args->number; + $lucky = $random_number === 7; + $instance->error = !$lucky; + + if (!$lucky) { + $instance->setUrl('https://httpbin.org/get?number=' . random_int(0, 10)); + } +}); + +$curl->get('https://httpbin.org/get?number=' . random_int(0, 10)); + +// $ php curl_set_error.php +// about to make request to https://httpbin.org/get?number=3 +// about to make request to https://httpbin.org/get?number=1 +// about to make request to https://httpbin.org/get?number=7 +// success! +// got number 7 after 3 attempt(s). diff --git a/examples/multi_curl_set_error.php b/examples/multi_curl_set_error.php new file mode 100644 index 0000000..c98d987 --- /dev/null +++ b/examples/multi_curl_set_error.php @@ -0,0 +1,44 @@ +setRetry($max_retries); + +$multi_curl->beforeSend(function ($instance) { + echo 'about to make request to ' . $instance->url . "\n"; +}); + +$multi_curl->error(function ($instance) { + echo 'not lucky this round' . "\n"; +}); + +$multi_curl->success(function ($instance) { + echo + 'success!' . "\n" . + 'got number ' . $instance->response->args->number . ' ' . + 'after ' . $instance->attempts . ' attempt(s).' . "\n"; +}); + +$multi_curl->setError(function ($instance) { + $random_number = (int)$instance->response->args->number; + $lucky = $random_number === 7; + $instance->error = !$lucky; + + if (!$lucky) { + $instance->setUrl('https://httpbin.org/get?number=' . random_int(0, 10)); + } +}); + +$multi_curl->addGet('https://httpbin.org/get?number=' . random_int(0, 10)); +$multi_curl->start(); + +// $ php multi_curl_set_error.php +// about to make request to https://httpbin.org/get?number=4 +// about to make request to https://httpbin.org/get?number=7 +// success! +// got number 7 after 2 attempt(s). diff --git a/src/Curl/BaseCurl.php b/src/Curl/BaseCurl.php index 589c1f8..51f7119 100644 --- a/src/Curl/BaseCurl.php +++ b/src/Curl/BaseCurl.php @@ -7,6 +7,7 @@ namespace Curl; abstract class BaseCurl { public $beforeSendCallback = null; + public $errorDecider = null; public $successCallback = null; public $errorCallback = null; public $completeCallback = null; @@ -138,6 +139,27 @@ abstract class BaseCurl $this->setOpt(CURLOPT_USERPWD, $username . ':' . $password); } + /** + * Set Error + * + * Use this function to override the default implemention for determining + * whether or not the request errored. The instance is passed as the first + * argument to the function and the instance has attributes like + * $instance->httpStatusCode and $instance->response to help decide if the + * request errored. Set $instance->error to true or false within the + * function. + * + * When $instance->error is true indicating a request error, the error + * callback set by Curl::error() is called. When $instance->error is false, + * the success callback set by Curl::success() is called. + * + * @param $function callable + */ + public function setError($function) + { + $this->errorDecider = $function; + } + /** * Set File * diff --git a/src/Curl/Curl.php b/src/Curl/Curl.php index 57c693a..92bfd46 100644 --- a/src/Curl/Curl.php +++ b/src/Curl/Curl.php @@ -526,11 +526,6 @@ class Curl extends BaseCurl $this->curlErrorMessage = $curl_error_message; } - $this->httpStatusCode = $this->getInfo(CURLINFO_HTTP_CODE); - $this->httpError = in_array((int) floor($this->httpStatusCode / 100), [4, 5], true); - $this->error = $this->curlError || $this->httpError; - $this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0; - // NOTE: CURLINFO_HEADER_OUT set to true is required for requestHeaders // to not be empty (e.g. $curl->setOpt(CURLINFO_HEADER_OUT, true);). if ($this->getOpt(CURLINFO_HEADER_OUT) === true) { @@ -539,6 +534,24 @@ class Curl extends BaseCurl $this->responseHeaders = $this->parseResponseHeaders($this->rawResponseHeaders); $this->response = $this->parseResponse($this->responseHeaders, $this->rawResponse); + $this->httpStatusCode = $this->getInfo(CURLINFO_HTTP_CODE); + $this->httpError = in_array((int) floor($this->httpStatusCode / 100), [4, 5], true); + + if ($this->errorDecider === null) { + $this->error = $this->curlError || $this->httpError; + } else { + $this->error = null; + $this->call($this->errorDecider); + if (!in_array($this->error, [true, false], true)) { + trigger_error( + '$instance->error MUST be set to true or false inside the setError() function', + E_USER_WARNING + ); + } + } + + $this->errorCode = $this->error ? ($this->curlError ? $this->curlErrorCode : $this->httpStatusCode) : 0; + $this->httpErrorMessage = ''; if ($this->error) { if (isset($this->responseHeaders['Status-Line'])) { diff --git a/src/Curl/MultiCurl.php b/src/Curl/MultiCurl.php index f9005d6..043c805 100755 --- a/src/Curl/MultiCurl.php +++ b/src/Curl/MultiCurl.php @@ -844,6 +844,9 @@ class MultiCurl extends BaseCurl if ($curl->beforeSendCallback === null) { $curl->beforeSend($this->beforeSendCallback); } + if ($curl->errorDecider === null) { + $curl->setError($this->errorDecider); + } if ($curl->successCallback === null) { $curl->success($this->successCallback); } diff --git a/tests/PHPCurlClass/PHPCurlClassTest.php b/tests/PHPCurlClass/PHPCurlClassTest.php index 24c7d01..460ea9d 100644 --- a/tests/PHPCurlClass/PHPCurlClassTest.php +++ b/tests/PHPCurlClass/PHPCurlClassTest.php @@ -4920,4 +4920,35 @@ class PHPCurlClassTest extends \PHPUnit\Framework\TestCase $this->assertEquals('foo', $test->curl->response->{'abc'}); $this->assertEquals('bar', $test->curl->response->{'123'}); } + + public function testSetErrorAttemptCount() + { + $test = new Test(); + $test->curl->setRetry(10); + $test->curl->setError(function ($instance) { + if ($instance->attempts < 5) { + $instance->error = true; + } else { + $instance->error = false; + } + }); + $test->server('json_response', 'GET'); + $this->assertEquals(5, $test->curl->attempts); + $this->assertEquals(4, $test->curl->retries); + $this->assertFalse($test->curl->error); + } + + public function testSetErrorResponseMessage() + { + $test = new Test(); + $test->curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); + $test->curl->setRetry(5); + $test->curl->setError(function ($instance) { + $instance->error = $instance->response->message !== '202 Accepted'; + }); + $test->server('retry', 'GET', ['failures' => 3]); + $this->assertEquals(4, $test->curl->attempts); + $this->assertEquals(3, $test->curl->retries); + $this->assertFalse($test->curl->error); + } } diff --git a/tests/PHPCurlClass/PHPMultiCurlClassTest.php b/tests/PHPCurlClass/PHPMultiCurlClassTest.php index 0ec41de..dd96d83 100644 --- a/tests/PHPCurlClass/PHPMultiCurlClassTest.php +++ b/tests/PHPCurlClass/PHPMultiCurlClassTest.php @@ -5452,4 +5452,39 @@ class PHPMultiCurlClassTest extends \PHPUnit\Framework\TestCase $this->assertEquals(5, $instance->retries); $this->assertFalse($instance->error); } + + public function testSetErrorAttemptCount() + { + $multi_curl = new MultiCurl(); + $multi_curl->setRetry(10); + $multi_curl->setError(function ($instance) { + if ($instance->attempts < 5) { + $instance->error = true; + } else { + $instance->error = false; + } + }); + $multi_curl->setHeader('X-DEBUG-TEST', 'json_response'); + $instance = $multi_curl->addGet(Test::TEST_URL); + $multi_curl->start(); + $this->assertEquals(5, $instance->attempts); + $this->assertEquals(4, $instance->retries); + $this->assertFalse($instance->error); + } + + public function testSetErrorResponseMessage() + { + $multi_curl = new MultiCurl(); + $multi_curl->setOpt(CURLOPT_COOKIEJAR, '/dev/null'); + $multi_curl->setRetry(5); + $multi_curl->setError(function ($instance) { + $instance->error = $instance->response->message !== '202 Accepted'; + }); + $multi_curl->setHeader('X-DEBUG-TEST', 'retry'); + $instance = $multi_curl->addGet(Test::TEST_URL, ['failures' => 3]); + $multi_curl->start(); + $this->assertEquals(4, $instance->attempts); + $this->assertEquals(3, $instance->retries); + $this->assertFalse($instance->error); + } }