Im using Laravel's builtin throttle like this:
//File: Kernal
protected $middlewareGroups = [
'api' => ['throttle:10,3']
];
However, I would like to reset the count after certain action in one of my controllers (for example after successful login).
I can see this middleware uses RateLimiter and that has a public method called clear.
The problem is, how to use this? Because it depends upon the key from ThrottleRequests middleware.
To get the object of ThrottleRequests I need instance of RateLimiter
To get the object of RateLimiter, I need instance of Cache.
.
.
all in all, there is no end to how to use it..
Any idea?
Thanks
As your question is tagged with Laravel v5.5, here's what applies there:
For Login attempts specifically:
You can use the Illuminate\Foundation\Auth\AuthenticatesUsers trait in your controller, so you would have access to the clearLoginAttempts method, which calls the clear() method on the RateLimiter instance with the correct key without the need to provide the key.
Actually if you look at how Illuminate\Foundation\Auth\ThrottlesLogins::clearLoginAttempts() is implemented, you can see that the right key can be retrieved by $this->throttleKey($request), once your controller uses the AuthenticatesUsers trait.
In general:
You can always get the Illuminate\Cache\RateLimiter instance by using app(\Illuminate\Cache\RateLimiter::class), which would in turn contain all the configured limiters and the cache as well. The problem is that it is not possible to get the cache keys from this point of view. So you really have to find out where and how the key was set in the first place, so you can use the same key for the reset.
The standard ThrottleRequests middleware sets the key in the handle() method, but the actual key would depend on where and how your throttling is configured (e.g.: is it a named limiter or only set using the numeric parameters, was ->by(...) called on it to set the key explicitly etc.)
If you only need to find the key for one particular limiter, probably you can set a breakpoint in the handle() method and just check.
Your case
In your particular case, as it is not a named limiter, the handle() method will call resolveRequestSignature to get the key. I don't think you can easily access the Middleware instance from a Controller. What you can do is to check how that method generates the key and basically copy that piece of code to replicate the same keys, but I'd not recommend that as it is a dirty and fragile solution.
Ifyou check, you'll see the key can be reproduced as something like:
if ($user = $request->user()) {
$key = sha1($user->getAuthIdentifier());
}
elseif ($route = $request->route()) {
$key = sha1($route->getDomain().'|'.$request->ip());
}
But in more recent Laravel versions you can explicitly set the key which is much cleaner and reliable solution:
In Laravel 8
Now as the question is fairly old, most people would rather use the latest version of Laravel (v8 as of 2021/02/12), so for them the documentation includes the way to "segment" the limiters aka. become able to apply separate limit counters for different requests based on the request (or session data, etc.). In fact the by() method actually sets the key of the limiter. So you can set up one or more named limiters like:
RateLimiter::for('my_per_ip_limiter', function (Request $request) {
return Limit::perMinute(100)->by($request->ip());
});
This means the limiter named my_per_ip_limiter will use the IP as the key, so any time in your controllers you could call:
app(\Illuminate\Cache\RateLimiter::class)->clear($request->ip());
to reset the limiter for a particular IP. Or to get the number of attempts so far:
$attempts_so_far = app(\Illuminate\Cache\RateLimiter::class)->attempts($request->ip());
Indeed instead of IP you could use any variable of the request (or session or whatever).
However there is no way (I think) to differentiate between the named limiters. So if the same key is used for another limiter as well, their hits will be counted together* and clear together. So giving a name like my_per_ip_limiter to the limiter is only useful so you can assign that limiter to particular routes by the name, e.g.:
Route::post( 'login', 'Auth\LoginController#login' )
->middleware('throttle:my_per_ip_limiter');
But if you really need named limiters to reset individually, you have to use a unique key, for example prefixing it with something, e.g.:
RateLimiter::for('my_other_ip_limiter', function (Request $request) {
return Limit::perMinute(100)->by('other_'.$request->ip());
});
This can be cleared independently from the other one:
// reset my_other_ip_limiter, but not my_per_ip_limiter :
app(\Illuminate\Cache\RateLimiter::class)->clear('other_'.$request->ip());
*: By counted together I mean they would add up, so if you apply two of them to the same request, each single request will bump the counter by 2!
Related
I already have a GET route with an URI /projects/{id} which displays Infos of a project with a given id. I also have a GET index route (/projects), which shows all my projects.
My problem is that I currently try to create different indexes (for example one which only displays the projects where I am assigned [e.g. on /projects/mines], or the projects which are pending administrator approval [e.g. on /projects/proposals], and still others displays).
So I want to know if I can have two GET routes /projects/{id}and /projects/{display_mode} which will be calling two differents methods of my ProjectController (respectively show and index).
Thanks for your help! :)
You may have one route /projects which returns all projects as default.
If there is query parameter like
/projects?displayMode=proposals
then you can apply filters.
In your controller it would look something like this
$projects = Project::query();
if ($request->query('displayMode') == 'proposals')
$projects->where('pending', true)
return $projects->get();
You can add multiple filters too in the same way
I'm not sure about specific Laravel options for the route definitions (sorry!), but if the {id} will always be an integer and {display_mode} will always have non-digits in it, you could keep just one route, but do the conditional handling in your controller. Just have the mainAction do something like…
return preg_match('/^\d+$/', $param) ? idHelperAction($param) : displayModeHelperAction($param);
Then create those two helper functions and have them return whatever you want.
$param is supposed to be whatever you get from that route parameter -- /projects/{param}.
That should call the idHelperAction for routes where $param is all digits and nothing else; otherwise, it should call the displayModeHelperAction. Either way, it sends the same $param to the helper function and returns whatever that helper function returns -- effectively splitting one route definition into two possible actions.
Of course, you might have to add some context in the code sample. If the functions are all defined in the same class, you might need to use $this->idHelperAction($param) or self::idHelperAction($param) (and the same with the other helper action), depending on whether it's static or not; or tell it where to find the functions if you put them in another class, etc., etc. -- all the normal contextual requirements.
PSR-7 is going to be standardized soon (I believe). That's got me thinking about middlewares, such as used by Phly, StackPHP, and ConnectJS.
The way ConnectJS works is that it modifies the request object when a middleware needs to add something. For example, cookie-session creates a session property on the req object:
app.use(session({
keys: ['key1', 'key2']
}))
app.use(function (req, res, next) {
var n = req.session.views || 0 // <-- req.session is managed by the session middleware
req.session.views = ++n
res.end(n + ' views')
})
With PSR-7, both our Request and Response objects are (supposed to be) immutable, so how are we supposed to pass along additional data like this? i.e. where would be the best place to store a 'session' object or a 'user' object created by an authentication middleware?
Request and Response objects in PSR-7 are implemented as value objects, hence they are immutable.
Every time you need a different object, you create a new instance from the previous one, like
$newRequest = $oldRequest->withMethod('GET');
and from that point on use the new instance.
In middlewares you would have to pass the new instance to the next() function that calls the next middleware (see here for example).
If you need to store in the request object additional data computed from your current request, in the ServerRequestInterface there are defined the withAttribute and the withAttributes methods that allow you to do exactly that.
A common use case for this is for storing the results of routing, but you can surely use them to store other additional data of the request, like session or user data
Do not store at all. Inject it as parameter into consumer function. For instance:
function doSomething(reqest, response, session, user, foo, bar, ...)
Be explicit.
I am creating an analytics storage process (using elastic search) where I need to add the items to be stored in elastic from my controller. But then I want to wait until after the response has been sent to the user to actually do the processing. I don't want the user to have to wait for this process to complete before getting the server response.
I am planning on running the processing part using:
App::finish(function(){
// DO THE PROCESSING
})
I know that this will run after the response has been sent. However, I am not sure how to get the data which has been compiled in a controller (or any class) to be referenced in the App::finish() closure method.
I have tried using App::singleton() but there are many instances where I need to be able to continually 'set' the data, I can't just set it once. I guess I am essentially looking for a global variable that I can manipulate but I know that doesn't exist in Laravel.
Another option is to use Session::push() and Session::get() but right now I have my Session storage using memcached for this app and I would rather not do additional I/O on memcached when I could just be storing the data needed to be saved in temporary memory.
Seems like I just need a simple container to write to and read from which is saved only in memory but I cannot find that in the Laravel docs.
You might be able to use the __destruct() magic method on whichever controllers you need to do the processing on.
You could also potentially implement it in BaseController as well if it should run for all controllers.
Another option would be to use sqlite in memory. Simply create a new connection
'sqlite_memory' => array(
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
),
Then you can use DB::connection('sqlite_memory') to use that connection and store/save whatever you need using the query builder.
You can pass data to the closure using "use".
In PHP 5.3.0, what is the function "use" identifier?
I ended up using something like this for storing data in the cache after returning the data to the user.
App::finish(function($request, $response) use ($id, $json){
Cache::put('key_'.$id, $json, 1440);
});
I am building an API in CakePHP. I have a function that as part of its execution first destroys the cookies associated with the session. I am using the following code to do this.
public function new_thing () {
// I first call another controller to use functions from that controller
App::import('Controller', 'Person');
$PersonsController = new PersonsController;
// This function call is the problem
// This does not throw any errors but does not destroy the cookie as requested
$PersonsController->_kill_auth_cookie()
}
// This is from the Person controller, these are the functions used in the API
// This is the function that sets the cookies
public function _set_auth_cookie( $email ) {
setcookie(Configure::read('auth_cookie_name'), $email);
}
// this is the function that does not properly destroy the cookie from the API
// interestingly, it does work when called from in this controller
public function _kill_auth_cookie() {
setcookie(Configure::read('auth_cookie_name'), 'xxx', time()-7200);
}
I cannot get the API to properly expire the cookie that is created earlier in the session, I am not sure why. Additionally—what is maddening—is that the logs are empty and no error is being thrown of any kind, so I am not sure what to do next.
There is so much wrong in this code and concept…
DON'T instantiate controllers anywhere. It is plain wrong, broken by design and violates the MVC pattern. Only one controller should be dispatched by the framework itself based on the request; you don’t instantiate them manually.
An API using cookies? Well, not impossible but definitely not nice to work with. It’s possible but I’ve never seen one in the wild. I feel sorry for the person who has to implement it. See this question.
Why are you not using the CookieComponent? It has a built-in destroy() method to remove a cookie.
If you have an “auth” cookie, why are you not using CakePHP’s built-in Auth system? It will deal with all of that.
Use App::uses() not App::import() here
By convention, only protected functions should be prefixed with _
The first point is very likely the reason why cookie and sessions are messed up because the second controller instance initiates components again, and by this cookie and session maybe a second time as well. However, this can lead to “interesting” side effects.
I first call another controller to use functions from that controller
This is the evidence that your architecture is broken by design. The code that needs to be executed somewhere else; should be in a model method in this case. Or at least a component if there are controller-related things to be shared between different controllers.
Rather than using controller/action/key1/value1/key2/value2 as my URL, I'd like to use controller/action/value1/value2. I think I could do this by defining a custom route in my Bootstrap class, but I want my entire application to behave this way, so adding a custom route for each action is out of the question.
Is this possible? If so, how would I then access valueN? I'd like to be able to define the parameters in my action method's signature. e.x.:
// PostsController.php
public function view($postID) {
echo 'post ID: ' . $postID;
}
I'm using Zend Framework 1.9.3
Thanks!
While I don't think it's possible with the current router to allow N values (a fixed number would work) you could write a custom router that would do it for you.
I would question this approach, however, and suggest that actually listing all of your routes won't take long and will be easier in the long run. A route designed as you've suggested would mean that either your named parameters are always in the same order, i.e.
/controller/action/id/title/colour
or that they are almost anonymous
/controller/action/value1/value2/value3
With code like
$this->getRequest()->getParam('value2'); //fairly meaningless
Does it have to be N or can you say some finite value? For instance can you imagine that you'll never need more than say 5 params? If so you can set up a route:
/:controller/:action/:param0/:param1/:param2/:param3/:param4
Which will work even if you don't specify all 5 params for every action. If you ever need 6 somewhere else you can just add another /:paramN onto the route.
Another solution I've worked with before is to write a plugin which parses the REQUEST_URI and puts all the extra params in the request object in the dispatchLoopStartup() method. I like the first method better as it makes it more obvious where the params are coming from.