Skip to content

Commit

Permalink
Merge pull request #223 from jolicode/waitfor
Browse files Browse the repository at this point in the history
Rework the wait_for feature
  • Loading branch information
lyrixx authored Jan 10, 2024
2 parents bfd7533 + 97df068 commit 58d6a9a
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 341 deletions.
7 changes: 1 addition & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,8 @@

* Add `force` argument to `fingerprint()` method to force run the callable, even if fingerprint is same
* Allow to override `AsTask` and `AsContext` attributes
* Add `wait_for()`, `wait_for_port()`, `wait_for_url()`, `wait_for_http_status()` functions
* Fix directory for fingerprinted test
* Add `wait_for()` function to wait for a condition to be true by using a
callback
* Add `wait_for_port()` function to wait for a port to be open
* Add `wait_for_url()` function to wait for an URL to be accessible
* Add `wait_for_http_status()` function to wait for an URL to return a specific
HTTP status code (can check the response content too)

## 0.10.0 (2023-11-14)

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ Discover more by reading the docs:
* [Handling signals](doc/12-signals.md)
* [Repacking your application in a new phar](doc/13-repack.md)
* [Fingerprinting and code execution when a hash changes](doc/14-fingerprint.md)
* [Wait For Utilities (Network util to wait for port, http code, or anything with callback)](doc/15-wait-for.md)

## Questions and answers

Expand Down
118 changes: 62 additions & 56 deletions doc/15-wait-for.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Wait For Utilities (Network util to wait for port, http code, or anything with callback)

`wait_for` and `wait_for_*` is designed to help developers handle various network-related scenarios by providing functions to wait for specific conditions, such as port accessibility, URL availability, and HTTP status codes. It offers flexibility and ease of use in scenarios where waiting for network-related events is necessary.
`wait_for` and `wait_for_*` are designed to help developers handle various
network-related scenarios by providing functions to wait for specific
conditions, such as port accessibility, URL availability, and HTTP status codes.
It offers flexibility and ease of use in scenarios where waiting for
network-related events is necessary.

## Before you begin

Expand All @@ -18,13 +22,15 @@ The following parameters are common to most of the functions:

## How to handle when the condition is met or when timeout is reached

The `wait_for` and `wait_for_*` functions throw a `WaitForTimeoutException` exception when the timeout is reached. You can catch this exception and handle it accordingly.
The `wait_for` and `wait_for_*` functions throw a `WaitForTimeoutException`
exception when the timeout is reached. You can catch this exception and handle
it accordingly.

### Example:

```php
try {
$result = wait_for(...); // wait_for_port, wait_for_url, wait_for_http_status, etc.
wait_for(...); // wait_for_port, wait_for_url, wait_for_http_status, etc.
} catch (WaitForTimeoutException $e) {
// Handle timeout
}
Expand All @@ -36,89 +42,89 @@ try {

#### Explanation:

The `wait_for` method is a general-purpose waiting function. It takes a callback function as its first parameter, representing the condition to be met. The function will repeatedly call this callback until the condition is met or the specified timeout is reached.

#### Examples:

1. **Waiting for a custom condition to be met:**
```php
$result = wait_for(
function () {
// Your custom condition/callback logic here
return true; // Change this based on your condition
},
$timeout = 10,
$quiet = false,
$intervalMs = 100,
$message = 'Waiting for something to happen...',
);
```

2. **Waiting for a simple condition using a closure:**
```php
$result = wait_for(
fn () => file_exists('/path/to/file.txt'),
$timeout = 5,
$quiet = false,
$intervalMs = 200,
$message = 'Waiting for file.txt to be created...',
);
```
The `wait_for` method is a general-purpose waiting function. It takes a callback
function as its first parameter, representing the condition to be met. The
function will repeatedly call this callback until the condition is met or the
specified timeout is reached.

```php
wait_for(
callback: function () {
// Your custom condition/callback logic here
return true; // Change this based on your condition
},
timeout: 10,
quiet: false,
intervalMs: 100,
message: 'Waiting for something to happen...',
);
```

> [!NOTE]
> you can also return null if you want to abort the waiting process. The
> helper will throw an exception if the callback returns null.
### `wait_for_port`

#### Explanation:

The `wait_for_port` method waits for a network port to be accessible. It checks if a connection can be established to the specified port on a given host within the specified timeout. The method allows customization by providing options such as the host.
The `wait_for_port` method waits for a network port to be accessible. It checks
if a connection can be established to the specified port on a given host within
the specified timeout. The method allows customization by providing options such
as the host.

#### Example:

```php
$result = wait_for_port(
$port = 8080,
$host = '127.0.0.1',
$timeout = 15,
$quiet = false,
$intervalMs = 500,
$message = 'Waiting for port localhost:8080 to be accessible...',
wait_for_port(
port: 8080,
host: '127.0.0.1',
timeout: 15,
quiet: false,
intervalMs: 500,
message: 'Waiting for port localhost:8080 to be accessible...',
);
```

### `wait_for_url`

#### Explanation:

The `wait_for_url` method waits for a URL to be accessible. It attempts to open a connection to the specified URL within the specified timeout.
The `wait_for_url` method waits for a URL to be accessible. It attempts to open
a connection to the specified URL within the specified timeout.

#### Example:

```php
$result = wait_for_url(
$url = 'https://example.com',
$timeout = 10,
$quiet = false,
$intervalMs = 200,
$message = 'Waiting for https://example.com to be accessible...',
wait_for_url(
url: 'https://example.com',
timeout: 10,
quiet: false,
intervalMs: 200,
message: 'Waiting for https://example.com to be accessible...',
);
```

### `wait_for_http_status`

#### Explanation:

The `wait_for_http_status` method waits for a URL to return a specific HTTP status code. It checks if the URL returns the expected status code within the specified timeout. Additionally, it allows a custom content checker callback to further validate the response content.
The method provide `$contentCheckerCallback` parameter to check the response content and return true if the content is valid, or false if the content is invalid
The `wait_for_http_status` method waits for a URL to return a specific HTTP
status code. It checks if the URL returns the expected status code within the
specified timeout. Additionally, it allows a custom content checker callback to
further validate the response content. The method provide
`$contentCheckerCallback` parameter to check the response content and return
true if the content is valid, or false if the content is invalid

#### Example:

```php
$result = wait_for_http_status(
$url = 'https://example.com/api',
$status = 200,
$contentCheckerCallback = fn (array|string $content) => isset($content['result']), // Type depends on the response content type (array for JSON application/json, string for text/plain, etc.)
$timeout = 10,
$quiet = false,
$intervalMs = 300,
$message = 'Waiting for https://example.com/api to return HTTP 200 with valid content...',
wait_for_http_status(
url: 'https://example.com/api',
status: 200,
timeout: 10,
quiet: false,
intervalMs: 300,
message: 'Waiting for https://example.com/api to return HTTP 200 with valid content...',
);
```
50 changes: 17 additions & 33 deletions examples/wait_for.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
namespace wait_for;

use Castor\Attribute\AsTask;
use Castor\Exception\WaitForExitedBeforeTimeoutException;
use Castor\Exception\WaitForTimeoutReachedException;
use Castor\Exception\WaitFor\ExitedBeforeTimeoutException;
use Castor\Exception\WaitFor\TimeoutReachedException;
use Symfony\Contracts\HttpClient\ResponseInterface;

use function Castor\io;
use function Castor\wait_for;
Expand All @@ -20,9 +21,9 @@ function wait_for_port_task(): void

try {
wait_for_port(port: 80, host: $googleIp, timeout: 2, message: 'Checking if example.com is available...');
} catch (WaitForExitedBeforeTimeoutException $e) {
} catch (ExitedBeforeTimeoutException $e) {
io()->error('example.com is not available. (exited before timeout)');
} catch (WaitForTimeoutReachedException $e) {
} catch (TimeoutReachedException $e) {
io()->error('example.com is not available. (timeout reached)');
}
}
Expand All @@ -32,9 +33,9 @@ function wait_for_url_task(): void
{
try {
wait_for_url(url: 'https://example.com', timeout: 2, message: 'Waiting for Google...');
} catch (WaitForExitedBeforeTimeoutException $e) {
} catch (ExitedBeforeTimeoutException) {
io()->error('example.com is not available. (exited before timeout)');
} catch (WaitForTimeoutReachedException $e) {
} catch (TimeoutReachedException) {
io()->error('example.com is not available. (timeout reached)');
}
}
Expand All @@ -46,49 +47,32 @@ function wait_for_url_with_content_checker_task(): void
wait_for_http_status(
url: 'https://example.com',
status: 200,
contentCheckerCallback: function (string $content) {
return u($content)->containsAny(['Example Domain']);
responseChecker: function (ResponseInterface $response) {
return u($response->getContent())->containsAny(['Example Domain']);
},
timeout: 2,
);
} catch (WaitForTimeoutReachedException $e) {
} catch (TimeoutReachedException) {
io()->error('example.com is not available. (timeout reached)');
}
}

#[AsTask(description: 'Use custom wait for, to check anything')]
function custom_wait_for_task(): void
function custom_wait_for_task(int $sleep = 1): void
{
$tmpFilePath = sys_get_temp_dir() . \DIRECTORY_SEPARATOR . 'castor-wait-for-custom.tmp';
$endTime = time() + 1;
$fiber = new \Fiber(function () use ($endTime, $tmpFilePath) {
while (time() < $endTime) {
\Fiber::suspend();
}

touch($tmpFilePath);
});
$okAt = time() + $sleep;

try {
wait_for(
callback: function () use ($tmpFilePath, $fiber) {
if (!$fiber->isStarted()) {
$fiber->start();
}
if ($fiber->isSuspended()) {
$fiber->resume();
}

return file_exists($tmpFilePath);
callback: function () use ($okAt) {
return time() >= $okAt;
},
timeout: 2,
timeout: 5,
message: 'Waiting for my custom check...',
);
} catch (WaitForExitedBeforeTimeoutException $e) {
} catch (ExitedBeforeTimeoutException) {
io()->error('My custom check failed. (exited before timeout)');
} catch (WaitForTimeoutReachedException $e) {
} catch (TimeoutReachedException) {
io()->error('My custom check failed. (timeout reached)');
}

unlink($tmpFilePath);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?php

namespace Castor\Exception;
namespace Castor\Exception\WaitFor;

class WaitForExitedBeforeTimeoutException extends \Exception
class ExitedBeforeTimeoutException extends \RuntimeException
{
public function __construct()
{
Expand Down
11 changes: 11 additions & 0 deletions src/Exception/WaitFor/TimeoutReachedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Castor\Exception\WaitFor;

class TimeoutReachedException extends \Exception
{
public function __construct(int $timeout)
{
parent::__construct("Timeout of {$timeout} seconds reached while waiting for callback.");
}
}
12 changes: 0 additions & 12 deletions src/Exception/WaitForInvalidCallbackCheckException.php

This file was deleted.

12 changes: 0 additions & 12 deletions src/Exception/WaitForTimeoutReachedException.php

This file was deleted.

Loading

0 comments on commit 58d6a9a

Please sign in to comment.