clusev/app/Services/HealthService.php

67 lines
2.3 KiB
PHP

<?php
namespace App\Services;
use App\Models\HealthCheck;
use Illuminate\Support\Facades\Http;
use Throwable;
/**
* Runs an uptime probe NATIVELY (Laravel HTTP client / a PHP TCP stream) — no SSH, no shell, so the
* target is never interpolated into a command. An HTTP probe reports only up/down + status code +
* latency (never the response body), so it can't exfiltrate an internal endpoint's content. Redirects
* are disabled so a 30x can't bounce the probe to an unseen target.
*/
class HealthService
{
/**
* @return array{ok:bool, latency:?int, detail:string}
*/
public function probe(HealthCheck $check, int $timeout = 5): array
{
return $check->type === 'tcp'
? $this->tcp((string) $check->target, (int) $check->port, $timeout)
: $this->http((string) $check->target, $timeout);
}
private function http(string $url, int $timeout): array
{
$start = microtime(true);
try {
$res = Http::timeout($timeout)->withoutRedirecting()->get($url);
$latency = (int) round((microtime(true) - $start) * 1000);
$status = $res->status();
// 2xx/3xx = up; a 4xx/5xx means the endpoint answered but is unhealthy.
return ['ok' => $status >= 200 && $status < 400, 'latency' => $latency, 'detail' => 'HTTP '.$status];
} catch (Throwable $e) {
return ['ok' => false, 'latency' => null, 'detail' => $this->reason($e->getMessage())];
}
}
private function tcp(string $host, int $port, int $timeout): array
{
$start = microtime(true);
$errno = 0;
$errstr = '';
$sock = @stream_socket_client('tcp://'.$host.':'.$port, $errno, $errstr, $timeout);
if (! $sock) {
return ['ok' => false, 'latency' => null, 'detail' => $errstr !== '' ? $errstr : 'connection failed'];
}
fclose($sock);
$latency = (int) round((microtime(true) - $start) * 1000);
return ['ok' => true, 'latency' => $latency, 'detail' => 'TCP '.$port.' open'];
}
/** Keep the failure reason short + on one line for the compact status row. */
private function reason(string $msg): string
{
$msg = trim(preg_split('/\R/', $msg)[0] ?? $msg);
return mb_strlen($msg) > 80 ? mb_substr($msg, 0, 80).'…' : $msg;
}
}