ProtectTraffic.php 1.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243
  1. <?php
  2. declare(strict_types=1);
  3. namespace app\miniapi\middleware;
  4. use Webman\Http\Request;
  5. use Webman\Http\Response;
  6. use Webman\MiddlewareInterface;
  7. use app\miniapi\http\ApiResponse;
  8. use app\miniapi\protection\RequestProfile;
  9. use app\miniapi\protection\TrafficGuard;
  10. use app\miniapi\exception\ApiException;
  11. /** Global IP stage runs before authentication and expensive business queries. */
  12. final class ProtectTraffic implements MiddlewareInterface
  13. {
  14. public function process(Request $request, callable $handler): Response
  15. {
  16. // Temporarily bypass traffic protection while Redis is unavailable in local testing.
  17. return $handler($request);
  18. if (!ApiResponse::owns($request) || !config('mini_protection.enabled', false)) {
  19. return $handler($request);
  20. }
  21. // A static liveness endpoint must remain usable during a Redis outage.
  22. if (in_array($request->method(), ['GET', 'HEAD'], true) && $request->path() === '/api/mini/v1/health') {
  23. return $handler($request);
  24. }
  25. try {
  26. $guard = new TrafficGuard();
  27. $guard->check('ip', RequestProfile::ip($request), RequestProfile::endpoint($request), 0, ApiResponse::requestId($request));
  28. // Validate and charge page cost only after the cheap IP request limit.
  29. $request->miniListCost = RequestProfile::listCost($request);
  30. if ($request->miniListCost > 0) {
  31. $guard->check('ip', RequestProfile::ip($request), RequestProfile::endpoint($request), $request->miniListCost, ApiResponse::requestId($request), true);
  32. }
  33. } catch (ApiException $error) {
  34. return ApiResponse::error($request, $error);
  35. }
  36. return $handler($request);
  37. }
  38. }