Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw UnrecoverableMessageHandlingException on 4xx #1235

Merged
merged 2 commits into from
Nov 24, 2024
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
19 changes: 19 additions & 0 deletions src/Exception/InvalidApPostException.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,23 @@

final class InvalidApPostException extends \Exception
{
public function __construct(public ?string $messageStart = '', public ?string $url = null, public ?int $responseCode = null, public ?array $payload = null, int $code = 0, ?\Throwable $previous = null)
{
$message = $this->messageStart;
$additions = [];
if ($url) {
$additions[] = $url;
}
if ($responseCode) {
$additions[] = "status code: $responseCode";
}
if ($payload) {
$jsonPayload = json_encode($this->payload);
$additions[] = $jsonPayload;
}
if (0 < \sizeof($additions)) {
$message .= ': '.implode(', ', $additions);
}
parent::__construct($message, $code, $previous);
}
}
35 changes: 34 additions & 1 deletion src/MessageHandler/ActivityPub/Outbox/DeliverHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
namespace App\MessageHandler\ActivityPub\Outbox;

use App\Entity\User;
use App\Exception\InstanceBannedException;
use App\Exception\InvalidApPostException;
use App\Exception\InvalidWebfingerException;
use App\Message\ActivityPub\Outbox\DeliverMessage;
use App\Message\Contracts\MessageInterface;
use App\MessageHandler\MbinMessageHandler;
Expand All @@ -14,12 +16,18 @@
use App\Service\ActivityPubManager;
use App\Service\SettingsManager;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Cache\InvalidArgumentException;
use Psr\Log\LoggerInterface;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
use Symfony\Component\Messenger\Exception\RecoverableMessageHandlingException;
use Symfony\Component\Messenger\Exception\UnrecoverableMessageHandlingException;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
BentiGorlich marked this conversation as resolved.
Show resolved Hide resolved

#[AsMessageHandler]
class DeliverHandler extends MbinMessageHandler
{
public const HTTP_RESPONSE_CODE_RATE_LIMITED = 429;

public function __construct(
private readonly EntityManagerInterface $entityManager,
private readonly ApHttpClient $client,
Expand Down Expand Up @@ -53,8 +61,26 @@ public function workWrapper(MessageInterface $message): void
$this->doWork($message);
$conn->commit();
} catch (InvalidApPostException $e) {
if (400 <= $e->responseCode && 500 > $e->responseCode && self::HTTP_RESPONSE_CODE_RATE_LIMITED !== $e->responseCode) {
$conn->rollBack();
BentiGorlich marked this conversation as resolved.
Show resolved Hide resolved
$this->logger->debug('{domain} responded with {code} for our request, rolling back the changes and not trying again, request: {body}', [
'domain' => $e->url,
'code' => $e->responseCode,
'body' => $e->payload,
]);
throw new UnrecoverableMessageHandlingException('There is a problem with the request which will stay the same, so discarding', previous: $e);
} elseif (self::HTTP_RESPONSE_CODE_RATE_LIMITED === $e->responseCode) {
$conn->rollBack();
// a rate limit is always recoverable
throw new RecoverableMessageHandlingException(previous: $e);
} else {
// we don't roll back on an InvalidApPostException, so the failed delivery attempt gets written to the DB
$conn->commit();
throw $e;
}
} catch (TransportExceptionInterface $e) {
// we don't roll back on an TransportExceptionInterface, so the failed delivery attempt gets written to the DB
$conn->commit();
// we don't roll back on an InvalidApPostException, so the failed delivery attempt gets written to the DB
throw $e;
} catch (\Exception $e) {
$conn->rollBack();
Expand All @@ -64,6 +90,13 @@ public function workWrapper(MessageInterface $message): void
$conn->close();
}

/**
* @throws InvalidApPostException
* @throws TransportExceptionInterface
* @throws InvalidArgumentException
* @throws InstanceBannedException
* @throws InvalidWebfingerException
*/
public function doWork(MessageInterface $message): void
{
if (!($message instanceof DeliverMessage)) {
Expand Down
9 changes: 5 additions & 4 deletions src/Service/ActivityPub/ApHttpClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ private function getActivityObjectImpl(string $url): ?string
// Accepted status code are 2xx or 410 (used Tombstone types)
if (!str_starts_with((string) $statusCode, '2') && 410 !== $statusCode) {
// Do NOT include the response content in the error message, this will be often a full HTML page
throw new InvalidApPostException("Invalid status code while getting: $url, status code: $statusCode");
throw new InvalidApPostException('Invalid status code while getting', $url, $statusCode);
}

// Read also non-OK responses (like 410) by passing 'false'
Expand Down Expand Up @@ -318,7 +318,7 @@ private function getCollectionObjectImpl(string $apAddress): ?string
// Accepted status code are 2xx or 410 (used Tombstone types)
if (!str_starts_with((string) $statusCode, '2') && 410 !== $statusCode) {
// Do NOT include the response content in the error message, this will be often a full HTML page
throw new InvalidApPostException("Invalid status code while getting: $apAddress, status code: $statusCode");
throw new InvalidApPostException('Invalid status code while getting', $apAddress, $statusCode);
}
} catch (\Exception $e) {
$this->logRequestException($response, $apAddress, 'ApHttpClient:getCollectionObject', $e);
Expand Down Expand Up @@ -374,7 +374,8 @@ private function logRequestException(?ResponseInterface $response, string $reque
* @param User|Magazine $actor The actor initiating the request, either a User or Magazine object
* @param array|null $body (Optional) The body of the POST request. Defaults to null.
*
* @throws InvalidApPostException if the POST request fails with a non-2xx response status code
* @throws InvalidApPostException if the POST request fails with a non-2xx response status code
* @throws TransportExceptionInterface
*/
public function post(string $url, User|Magazine $actor, ?array $body = null): void
{
Expand Down Expand Up @@ -407,7 +408,7 @@ public function post(string $url, User|Magazine $actor, ?array $body = null): vo
$statusCode = $response->getStatusCode();
if (!str_starts_with((string) $statusCode, '2')) {
// Do NOT include the response content in the error message, this will be often a full HTML page
throw new InvalidApPostException("Post failed: $url, status code: $statusCode, request body: $jsonBody");
throw new InvalidApPostException('Post failed', $url, $statusCode, $body);
}
} catch (\Exception $e) {
$this->logRequestException($response, $url, 'ApHttpClient:post', $e);
Expand Down
Loading