HTTP-клиент использует middleware pipeline для обработки запросов и ответов.
Middleware выполняются в порядке убывания приоритета. Больший приоритет = выполняется раньше.
Request Flow (высокий → низкий приоритет):
┌─────────────────────────────────────────────────────────────────────┐
│ HTTP Client │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ [100] AuthMiddleware │
│ Добавляет Authorization header │
│ Почему первый: credentials должны быть добавлены до retry │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ [90] RetryMiddleware │
│ Повторяет запрос при ошибках (429, 5xx) │
│ Почему здесь: retry должен охватывать logging и circuit │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ [80] LoggingMiddleware │
│ Логирует каждую попытку запроса │
│ Почему здесь: логировать каждый retry, но после auth │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ [60] CircuitBreakerMiddleware │
│ Блокирует запросы к "упавшим" сервисам │
│ Почему здесь: circuit учитывает каждую попытку retry │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ [10] MultiHostMiddleware │
│ Выбирает хост по стратегии (failover, round-robin, parallel) │
│ Почему последний: выполняется непосредственно перед transport │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ GuzzleTransport │
│ Отправка HTTP запроса │
└─────────────────────────────────────────────────────────────────────┘
| Приоритет | Middleware | Назначение | Включен по умолчанию |
|---|---|---|---|
| 100 | AuthMiddleware |
Добавляет заголовки аутентификации | Да |
| 90 | RetryMiddleware |
Повторы с экспоненциальной задержкой | Да (настраиваемый) |
| 80 | LoggingMiddleware |
Логирование запросов/ответов | Да (настраиваемый) |
| 60 | CircuitBreakerMiddleware |
Паттерн Circuit Breaker | Нет (по умолчанию) |
| 10 | MultiHostMiddleware |
Обработка стратегий multi-host | Автоматически |
Аутентификация должна быть добавлена до любой логики retry. Если auth добавлять после retry, то:
Retry должен охватывать все нижележащие middleware:
Находится внутри retry-цикла, поэтому:
Проверяет состояние circuit для каждой попытки:
Выбор хоста происходит непосредственно перед отправкой:
Middleware поддерживает как синхронные, так и асинхронные запросы через Promise-based архитектуру (как в Guzzle).
<?php
declare(strict_types=1);
namespace App\Middleware;
use Deeep\ServiceClient\Context\RequestContext;
use Deeep\ServiceClient\Contract\MiddlewareInterface;
use Deeep\ServiceClient\Contract\RequestInterface;
use Deeep\ServiceClient\Contract\ResponseInterface;
use GuzzleHttp\Promise\PromiseInterface;
use Throwable;
final readonly class TimingMiddleware implements MiddlewareInterface
{
private const int PRIORITY = 85;
public function __construct(
private MetricsCollector $metrics,
) {}
public function process(
RequestInterface $request,
RequestContext $context,
callable $next,
): ResponseInterface|PromiseInterface {
$startTime = microtime(true);
try {
$result = $next($request, $context);
} catch (Throwable $e) {
$this->recordMetrics($request, $startTime, false);
throw $e;
}
if ($result instanceof PromiseInterface) {
return $result->then(
function (ResponseInterface $response) use ($request, $startTime): ResponseInterface {
$this->recordMetrics($request, $startTime, true);
return $response;
},
function (Throwable $e) use ($request, $startTime): void {
$this->recordMetrics($request, $startTime, false);
throw $e;
},
);
}
$this->recordMetrics($request, $startTime, true);
return $result;
}
public function getPriority(): int
{
return self::PRIORITY;
}
private function recordMetrics(RequestInterface $request, float $startTime, bool $success): void
{
$this->metrics->recordTiming(
service: $request->getService(),
duration: microtime(true) - $startTime,
success: $success,
);
}
}
$next() возвращает ResponseInterface для sync или PromiseInterface для async->then(onSuccess, onError)onError callbackРеализуйте MiddlewareInterface — Symfony автоматически зарегистрирует middleware:
// src/Middleware/TimingMiddleware.php
// Просто реализуйте MiddlewareInterface - автоконфигурация через instanceof
<?php
// config/services.php
use App\Middleware\TimingMiddleware;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $container): void {
$services = $container->services();
$services->set(TimingMiddleware::class)
->tag('deeep_service_client.middleware');
};
| Диапазон | Назначение | Примеры |
|---|---|---|
| 95-100 | Модификация запроса | Auth, Request ID, Tenant headers |
| 85-94 | Метрики/timing (вне retry) | Prometheus metrics, APM |
| 75-89 | Внутри retry цикла | Логирование, трейсинг |
| 50-74 | Защитные паттерны | Circuit breaker, rate limiting |
| 1-49 | Транспортный уровень | Multi-host, caching |
'services' => [
'fast_api' => [
'host' => '...',
'retry' => [
'enabled' => false,
],
],
],
По умолчанию отключен. Для включения:
'services' => [
'unreliable_api' => [
'host' => '...',
'circuit_breaker' => [
'enabled' => true,
'failure_threshold' => 5,
'open_timeout' => 30,
],
],
],
'services' => [
'internal_api' => [
'host' => '...',
'logging' => [
'enabled' => false,
],
],
],
How can I help you explore Laravel packages today?