Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,26 @@

### Changed

- `EndpointChecker` (the `rl.php` accessibility check used by
`hook_requirements()`) hardened against false negatives behind reverse
proxies and false positives from misconfigured redirects (#50):
- validates the response body is `pong`, not just a 200 status;
- upgrades the probe URL to HTTPS based on `X-Forwarded-Proto` even when
`$settings['reverse_proxy']` isn't configured (safe for a same-site
self-probe);
- on public-URL failure, retries on `http://127.0.0.1` with the original
`Host` header to distinguish "rl.php is broken" from "the proxy /
scheme / DNS chain to the public hostname is broken";
- returns a structured result so the status-report description names the
actual failure mode (`redirected`, `http_error`, `body_mismatch`,
`connection_error`, `file_missing`) instead of a generic "not
accessible";
- caches success for 1 hour but failures for only 5 minutes, so a fixed
misconfig clears without a manual cache rebuild;
- narrows the swallow-all `\Exception` fallback to cURL `errno 6/7`
(DNS / TCP-connect) so SSL handshake errors and timeouts are surfaced.
Public API: existing `EndpointChecker::isAccessible(): bool` is preserved
as a thin wrapper over the new `EndpointChecker::getResult(): array`.
- **BC break (minor):** `ExperimentManagerInterface` now declares three new
methods:
- `purgeExperiment(string $experiment_id)` - removes turns, rewards,
Expand Down
82 changes: 74 additions & 8 deletions rl.install
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

use Drupal\Core\Database\SchemaObjectExistsException;
use Drupal\Core\Database\SchemaObjectDoesNotExistException;
use Drupal\rl\Service\EndpointChecker;

/**
* Implements hook_install().
Expand Down Expand Up @@ -449,17 +450,82 @@ function rl_requirements($phase) {

/** @var \Drupal\rl\Service\EndpointChecker $checker */
$checker = \Drupal::service('rl.endpoint_checker');
$accessible = $checker->isAccessible();
$requirements['rl_php_accessibility'] = [
$result = $checker->getResult();
$readme = 'https://git.drupalcode.org/project/rl/-/blob/1.x/README.md#post-installation-verify-rlphp-access';
$loopback_hint = !empty($result['loopback_ok'])
? t('Note: rl.php is reachable on loopback (@loopback), so the local web server is fine — the failure is in the public hostname → web server path (proxy, scheme, redirect rule).', ['@loopback' => $result['loopback_url'] ?? ''])
: '';

$requirement = [
'title' => t('RL: rl.php accessibility'),
'severity' => $accessible ? REQUIREMENT_OK : REQUIREMENT_ERROR,
'value' => $accessible ? t('rl.php is accessible') : t('rl.php is not accessible'),
];
if (!$accessible) {
$requirements['rl_php_accessibility']['description'] = t('The web server is not serving rl.php. Ensure .htaccess files are being processed (Apache) or rewrite rules are configured (Nginx). See the <a href="@readme" target="_blank">README</a> for configuration instructions.', [
'@readme' => 'https://git.drupalcode.org/project/rl/-/blob/1.x/README.md#post-installation-verify-rlphp-access',
]);

switch ($result['status']) {
case EndpointChecker::STATUS_OK:
$requirement['severity'] = REQUIREMENT_OK;
$requirement['value'] = t('rl.php is accessible');
break;

case EndpointChecker::STATUS_REDIRECTED:
$requirement['severity'] = REQUIREMENT_ERROR;
$requirement['value'] = t('rl.php probe was redirected to an unexpected target');
$requirement['description'] = t('The probe to @url followed a redirect chain to @final and the final body was not "pong". This usually means Drupal generates absolute URLs with the wrong scheme — set <code>$settings[\'reverse_proxy\']</code> and <code>$settings[\'reverse_proxy_trusted_headers\']</code>, or fix the redirect rule that drops the host. @hint See the <a href="@readme" target="_blank">README</a>.', [
'@url' => $result['public_url'] ?? '(unknown URL)',
'@final' => isset($result['redirects']) ? end($result['redirects']) : '(unknown)',
'@hint' => $loopback_hint,
'@readme' => $readme,
]);
break;

case EndpointChecker::STATUS_HTTP_ERROR:
$requirement['severity'] = REQUIREMENT_ERROR;
$requirement['value'] = t('rl.php returned HTTP @code', ['@code' => $result['code'] ?? 0]);
$requirement['description'] = t('The web server returned HTTP @code for rl.php at @url. Ensure .htaccess is processed (Apache) or rewrite rules pass real files through (Nginx). @hint See the <a href="@readme" target="_blank">README</a>.', [
'@code' => $result['code'] ?? 0,
'@url' => $result['public_url'] ?? '(unknown URL)',
'@hint' => $loopback_hint,
'@readme' => $readme,
]);
break;

case EndpointChecker::STATUS_BODY_MISMATCH:
$requirement['severity'] = REQUIREMENT_ERROR;
$requirement['value'] = t('rl.php returned unexpected content');
$requirement['description'] = t('rl.php is served at @url but returned unexpected content (@detail). Another module or rewrite rule may be intercepting the request, or a redirect led somewhere wrong. @hint See the <a href="@readme" target="_blank">README</a>.', [
'@url' => $result['public_url'] ?? '(unknown URL)',
'@detail' => $result['detail'] ?? '',
'@hint' => $loopback_hint,
'@readme' => $readme,
]);
break;

case EndpointChecker::STATUS_CONNECTION_ERROR:
$requirement['severity'] = REQUIREMENT_ERROR;
$requirement['value'] = t('rl.php connection failed');
$requirement['description'] = t('The HTTP probe to rl.php failed at the network layer: @detail. The web server may not be running, the public hostname may not resolve from inside the container, or TLS may be misconfigured. See the <a href="@readme" target="_blank">README</a>.', [
'@detail' => $result['detail'] ?? '',
'@readme' => $readme,
]);
break;

case EndpointChecker::STATUS_FILE_MISSING:
$requirement['severity'] = REQUIREMENT_ERROR;
$requirement['value'] = t('rl.php is missing on disk');
$requirement['description'] = t('rl.php is not present where it should be: @detail. Reinstall or re-deploy the rl module.', [
'@detail' => $result['detail'] ?? '',
]);
break;

default:
$requirement['severity'] = REQUIREMENT_ERROR;
$requirement['value'] = t('rl.php accessibility unknown');
$requirement['description'] = t('The endpoint check returned an unrecognised status (@status). See the <a href="@readme" target="_blank">README</a>.', [
'@status' => $result['status'] ?? '(none)',
'@readme' => $readme,
]);
}

$requirements['rl_php_accessibility'] = $requirement;
}

// Check if installed AI skill files are outdated.
Expand Down
Loading
Loading