-
Notifications
You must be signed in to change notification settings - Fork 0
Limit manager as a service #2
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
base: feature/extract-rate-limit-manager
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,8 +9,6 @@ | |
| use Noxlogic\RateLimitBundle\Exception\RateLimitExceptionInterface; | ||
| use Noxlogic\RateLimitBundle\LimitProcessorInterface; | ||
| use Noxlogic\RateLimitBundle\Service\RateLimitService; | ||
| use Noxlogic\RateLimitBundle\Util\AnnotationLimitProcessor; | ||
| use Noxlogic\RateLimitBundle\Util\PathLimitProcessor; | ||
| use Symfony\Component\EventDispatcher\EventDispatcherInterface; | ||
| use Symfony\Component\HttpFoundation\Request; | ||
| use Symfony\Component\HttpFoundation\Response; | ||
|
|
@@ -31,23 +29,23 @@ class RateLimitAnnotationListener extends BaseListener | |
| protected $rateLimitService; | ||
|
|
||
| /** | ||
| * @var \Noxlogic\RateLimitBundle\Util\PathLimitProcessor | ||
| * @var LimitProcessorInterface | ||
| */ | ||
| protected $pathLimitProcessor; | ||
| protected $limitProcessor; | ||
|
|
||
| /** | ||
| * @param EventDispatcherInterface $eventDispatcher | ||
| * @param RateLimitService $rateLimitService | ||
| * @param PathLimitProcessor $pathLimitProcessor | ||
| * @param LimitProcessorInterface $limitProcessor | ||
| */ | ||
| public function __construct( | ||
| EventDispatcherInterface $eventDispatcher, | ||
| RateLimitService $rateLimitService, | ||
| PathLimitProcessor $pathLimitProcessor | ||
| LimitProcessorInterface $limitProcessor | ||
| ) { | ||
| $this->eventDispatcher = $eventDispatcher; | ||
| $this->rateLimitService = $rateLimitService; | ||
| $this->pathLimitProcessor = $pathLimitProcessor; | ||
| $this->limitProcessor = $limitProcessor; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -65,15 +63,7 @@ public function onKernelController(FilterControllerEvent $event) | |
| return; | ||
| } | ||
|
|
||
| // Find the best match | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this logic has been moved in the |
||
| $annotations = $event->getRequest()->attributes->get('_x-rate-limit', array()); | ||
|
|
||
| $limitProcessor = $this->pathLimitProcessor; | ||
| if ($annotations) { | ||
| $limitProcessor = new AnnotationLimitProcessor($annotations, $event->getController()); | ||
| } | ||
|
|
||
| $rateLimit = $limitProcessor->getRateLimit($event->getRequest()); | ||
| $rateLimit = $this->limitProcessor->getRateLimit($event->getRequest()); | ||
|
|
||
| // Another treatment before applying RateLimit ? | ||
| $checkedRateLimitEvent = new CheckedRateLimitEvent($event->getRequest(), $rateLimit); | ||
|
|
@@ -85,7 +75,7 @@ public function onKernelController(FilterControllerEvent $event) | |
| return; | ||
| } | ||
|
|
||
| $key = $this->getKey($limitProcessor, $rateLimit, $event->getRequest()); | ||
| $key = $this->getKey($rateLimit, $event); | ||
|
|
||
| $rateLimitInfo = $this->rateLimitService->getRateLimitInfo($key, $rateLimit); | ||
| if (!$rateLimitInfo) { | ||
|
|
@@ -139,7 +129,7 @@ protected function findBestMethodMatch(Request $request, array $annotations) | |
|
|
||
| // Empty array, check the path limits | ||
| if (count($annotations) == 0) { | ||
| return $this->pathLimitProcessor->getRateLimit($request); | ||
| return $this->limitProcessor->getRateLimit($request); | ||
| } | ||
|
|
||
| $best_match = null; | ||
|
|
@@ -160,13 +150,14 @@ protected function findBestMethodMatch(Request $request, array $annotations) | |
| return $best_match; | ||
| } | ||
|
|
||
| private function getKey(LimitProcessorInterface $limitProcessor, RateLimit $rateLimit, Request $request) | ||
| private function getKey(RateLimit $rateLimit, FilterControllerEvent $event) | ||
| { | ||
| // Let listeners manipulate the key | ||
| $request = $event->getRequest(); | ||
| $keyEvent = new GenerateKeyEvent($request, '', $rateLimit->getPayload()); | ||
|
|
||
| $keyEvent->addToKey(join('.', $rateLimit->getMethods())); | ||
| $keyEvent->addToKey($limitProcessor->getRateLimitAlias($request)); | ||
| $keyEvent->addToKey($this->limitProcessor->getRateLimitAlias($request, $event->getController())); | ||
|
|
||
| $this->eventDispatcher->dispatch(RateLimitEvents::GENERATE_KEY, $keyEvent); | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,8 @@ public function getRateLimit(Request $request); | |
|
|
||
| /** | ||
| * @param Request $request | ||
| * @param callable $controller | ||
| * @return string | ||
| */ | ||
| public function getRateLimitAlias(Request $request); | ||
| public function getRateLimitAlias(Request $request, callable $controller); | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only thing that I did not manager to solve in an elegant way is the
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will think about that case. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,25 +9,25 @@ | |
| class AnnotationLimitProcessor implements LimitProcessorInterface | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This class is a service now, it is instantiated only once by the symfony DI. If the annotations package is not available, disabling it will not require changes in the code, only changing the
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Main reason to move it into the service - to create object only once via DI? Disabling class in DI good approach. Maybe I wrong, but it doesn't bring big value, cause if annotation package is not available we need to disable Before that changes, So in general, I agree, that it can be service class, cause in any case it right now symfony specific.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having stateless services is always a good idea, in the previous implementation Did not know that the aim of this bundle is to be framework agnostic.Wiring it into laravel might be possible, but not sure is the main goal. |
||
| { | ||
| /** | ||
| * @var array | ||
| * @var LimitProcessorInterface | ||
| */ | ||
| private $annotations; | ||
| private $fallback; | ||
|
|
||
| /** | ||
| * @var callable | ||
| */ | ||
| private $controller; | ||
|
|
||
| public function __construct(array $annotations, callable $controller) | ||
| public function __construct(LimitProcessorInterface $fallback) | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here using the chain of responsibility pattern |
||
| { | ||
| $this->annotations = $annotations; | ||
| $this->controller = $controller; | ||
| $this->fallback = $fallback; | ||
| } | ||
|
|
||
| public function getRateLimit(Request $request) | ||
| { | ||
| $annotations = $request->attributes->get('_x-rate-limit', array()); | ||
|
|
||
| if (!$annotations) { | ||
| return $this->fallback->getRateLimit($request); | ||
| } | ||
|
|
||
| $best_match = null; | ||
| foreach ($this->annotations as $annotation) { | ||
| foreach ($annotations as $annotation) { | ||
| // cast methods to array, even method holds a string | ||
| $methods = is_array($annotation->getMethods()) ? $annotation->getMethods() : array($annotation->getMethods()); | ||
|
|
||
|
|
@@ -44,14 +44,18 @@ public function getRateLimit(Request $request) | |
| return $best_match; | ||
| } | ||
|
|
||
| public function getRateLimitAlias(Request $request) | ||
| public function getRateLimitAlias(Request $request, callable $controller) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we use
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's definitely an option! That would be great |
||
| { | ||
| $annotations = $request->attributes->get('_x-rate-limit', array()); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are repeating this part in each of method. Best option as I see - to load all annotation ahead, so we can really use fallback logic with chain of responsibility. Not sure if its possible in symfony, will try to find out. One worry, that it can be breacking change.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instantiating a new object is more expensive in therms of CPU than calling a method (but I think is too soon to consider this kind of optimizations). Currently symfony does not allow to have dependency-injection containers per url/path, so this kind of But then based on that if condition was changing the underlying services, by swapping the rate limit processor with |
||
|
|
||
| if (!$annotations) { | ||
| return $this->fallback->getRateLimitAlias($request, $controller); | ||
| } | ||
|
|
||
| if (($route = $request->attributes->get('_route'))) { | ||
| return $route; | ||
| } | ||
|
|
||
| $controller = $this->controller; | ||
|
|
||
| if (is_string($controller) && false !== strpos($controller, '::')) { | ||
| $controller = explode('::', $controller); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class now depends only on the interface, not on implementations anymore