1 | <?php |
||
16 | class RateLimitAnnotationListener extends BaseListener |
||
17 | { |
||
18 | |||
19 | /** |
||
20 | * @var eventDispatcherInterface |
||
21 | */ |
||
22 | protected $eventDispatcher; |
||
23 | |||
24 | /** |
||
25 | * @var \Noxlogic\RateLimitBundle\Service\RateLimitService |
||
26 | */ |
||
27 | protected $rateLimitService; |
||
28 | |||
29 | /** |
||
30 | * @var \Noxlogic\RateLimitBundle\Util\PathLimitProcessor |
||
31 | */ |
||
32 | protected $pathLimitProcessor; |
||
33 | |||
34 | /** |
||
35 | * @param RateLimitService $rateLimitService |
||
36 | */ |
||
37 | 16 | public function __construct( |
|
46 | |||
47 | /** |
||
48 | * @param FilterControllerEvent $event |
||
49 | */ |
||
50 | 12 | public function onKernelController(FilterControllerEvent $event) |
|
51 | { |
||
52 | // Skip if we aren't the main request |
||
53 | 12 | if ($event->getRequestType() != HttpKernelInterface::MASTER_REQUEST) { |
|
54 | 1 | return; |
|
55 | } |
||
56 | |||
57 | // Skip if we are a closure |
||
58 | 11 | if (! is_array($controller = $event->getController())) { |
|
59 | 2 | return; |
|
60 | } |
||
61 | |||
62 | // Find the best match |
||
63 | 10 | $annotations = $event->getRequest()->attributes->get('_x-rate-limit', array()); |
|
64 | 10 | $rateLimit = $this->findBestMethodMatch($event->getRequest(), $annotations); |
|
65 | |||
66 | // No matching annotation found |
||
67 | 10 | if (! $rateLimit) { |
|
68 | 2 | return; |
|
69 | } |
||
70 | |||
71 | 8 | $key = $this->getKey($event, $rateLimit, $annotations); |
|
72 | |||
73 | // Ratelimit the call |
||
74 | 8 | $rateLimitInfo = $this->rateLimitService->limitRate($key); |
|
75 | 8 | if (! $rateLimitInfo) { |
|
76 | // Create new rate limit entry for this call |
||
77 | 5 | $rateLimitInfo = $this->rateLimitService->createRate($key, $rateLimit->getLimit(), $rateLimit->getPeriod()); |
|
78 | 5 | if (! $rateLimitInfo) { |
|
79 | // @codeCoverageIgnoreStart |
||
80 | return; |
||
81 | // @codeCoverageIgnoreEnd |
||
82 | } |
||
83 | 5 | } |
|
84 | |||
85 | |||
86 | // Store the current rating info in the request attributes |
||
87 | 8 | $request = $event->getRequest(); |
|
88 | 8 | $request->attributes->set('rate_limit_info', $rateLimitInfo); |
|
89 | |||
90 | // Reset the rate limits |
||
91 | 8 | if(time() >= $rateLimitInfo->getResetTimestamp()) { |
|
92 | $this->rateLimitService->resetRate($key); |
||
93 | } |
||
94 | 4 | ||
95 | 1 | // When we exceeded our limit, return a custom error response |
|
96 | 1 | if ($rateLimitInfo->getCalls() > $rateLimitInfo->getLimit()) { |
|
97 | |||
98 | // Throw an exception if configured. |
||
99 | 3 | if ($this->getParameter('rate_response_exception')) { |
|
100 | 3 | $class = $this->getParameter('rate_response_exception'); |
|
101 | 3 | throw new $class($this->getParameter('rate_response_message'), $this->getParameter('rate_response_code')); |
|
102 | } |
||
103 | |||
104 | $message = $this->getParameter('rate_response_message'); |
||
105 | 3 | $code = $this->getParameter('rate_response_code'); |
|
106 | 3 | $event->setController(function () use ($message, $code) { |
|
107 | // @codeCoverageIgnoreStart |
||
108 | 7 | return new Response($message, $code); |
|
109 | // @codeCoverageIgnoreEnd |
||
110 | }); |
||
111 | } |
||
112 | |||
113 | } |
||
114 | 14 | ||
115 | |||
116 | /** |
||
117 | 14 | * @param RateLimit[] $annotations |
|
118 | 4 | */ |
|
119 | protected function findBestMethodMatch(Request $request, array $annotations) |
||
143 | |||
144 | 8 | private function getKey(FilterControllerEvent $event, RateLimit $rateLimit, array $annotations) |
|
163 | } |
||
164 |