Skip to content
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

RequestFilter adjustments #3130

Closed
wants to merge 3 commits into from
Closed

Conversation

mateusz
Copy link
Contributor

@mateusz mateusz commented May 13, 2014

Make the API more useful by allowing to override the response in pre-request filter so we can build rate-limiting, caching, 304 responses and so on.

Just return non-true from the pre-request handler, and the framework will try to parse it as a response value. Return SS_HTTPResponse to get that delivered to the user.

Needs some more testing - I've only checked against https://github.com/mateusz/controllerpolicy

$filterResponse = Injector::inst()->get('RequestProcessor')->preRequest($req, $session, $model);
if ($filterResponse && $filterResponse!==true) {
if (!$filterResponse instanceof SS_HTTPResponse) {
throw new SS_HTTPResponse_Exception('RequestFilters have to return null, true or SS_HTTPResponse.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use user_error here. A legitimate programming error shouldn't raise a SS_HTTPResponse_Exception, only user-initiated errors.
You also need to handle the 'false' case, or are we dropping that from the API?

Allow pre-filters to override the framework response. Previously, only
throwing an Exception was possible. This change will allow us to do
pre-request caching, rate-limiting, 304 Not Modified responses etc.

Fix the post-filter order execution to be more intuitive: highest
priority goes last.

Adds ability to add filters during the run-time.

Refactor to allow returning SS_HTTPResponses from post filter.
@mateusz
Copy link
Contributor Author

mateusz commented Aug 1, 2014

Needs some tests.

@mateusz
Copy link
Contributor Author

mateusz commented Aug 1, 2014

And needs fixing Director::test :-)

// No-op
}

public function postRequest(\SS_HTTPRequest $req, \SS_HTTPResponse &$res, \Session $session, \DataModel $model) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not keep the parameters in the same order? (request, response, session, model).

Also, I see value in having well defined names (request over req, earlyResponse over earlyRes). What's the argument behind shortening these?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wish PHP had named parameters like ruby. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm did you misread that? The order is the same everywhere - request, response, session, model. Where is it different?

Shortened res and req for readability - otherwise the line needs to be broken up and the whole function header starts looking huge.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I mean is, in 3.1 the order is "request response model" and there is no session. What I'm suggesting is that you add session to the end, rather than move model to the 4th parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the parameter ordering, if I do as you are suggesting, then the postRequest and preRequest will have mismatched order - one will have session last, second one will have session last-but-one. See the proposed interface:

public function preRequest(SS_HTTPRequest $req, Session $session, DataModel $model);
public function postShorted(SS_HTTPRequest $req, SS_HTTPResponse &$earlyRes, Session $session, DataModel $model);
public function postRequest(SS_HTTPRequest $req, SS_HTTPResponse &$res, Session $session, DataModel $model);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, you have my permission to break everything. It's not like 3.1 -> 3.2 is going to be a smooth transition in any case.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most important consideration when breaking API between minor releases is: will the change be immediately obvious, or will everything look like it's still working, but be introducing subtile errors into your logic.

In this case, because of the typing of the arguments, trying to use the old API will trigger an error, so it's probably OK.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @hafriedlander - that sounds like a reasonable approach.

@tractorcow
Copy link
Contributor

Just a general question, do you think it's worth allowing for the order of request filters to be specified, similar to how routes are? It would make sense if some filters simply setup the state (versioned) and others deal with generating responses.

@mateusz
Copy link
Contributor Author

mateusz commented Aug 4, 2014

Hmm the filters are specified as an array, just as the routes are. Ordering is controlled through the config system's before and afters, so if we have a problem with this not being powerful enough, we should probably fix the config system instead of hacking here :-)

m

@mateusz
Copy link
Contributor Author

mateusz commented Aug 4, 2014

@tractorcow
Copy link
Contributor

The problem is that before and after are only reliable ways to ensure order of evaluation, not order of merge. :) Still, if it's hard, we can just not do it for now.

@mateusz
Copy link
Contributor Author

mateusz commented Aug 4, 2014

Isn't the order of evaluation equate to the order of the final array?

If that's not working - you could always name your module zzz_damian :-)

@tractorcow
Copy link
Contributor

Maybe just document that it's not reasonable to rely on execution order just yet.

@mateusz
Copy link
Contributor Author

mateusz commented Aug 4, 2014

Changed the names from req to request and res to response. I will have a look how to do the ordering, because it seems important here.

@mateusz
Copy link
Contributor Author

mateusz commented Aug 4, 2014

@hamish, @tractorcow, @halkyon while I have a captive audience here: do we want to have a framework support for filters before/after controllers and controller actions?

Note that it is possible to achieve the "after" filters using the existing global RequestFilters and existing extension points (see silverstripe-controllerpolicy), but it's a little hackish. To apply these "before", one would need to roll their own solution.

Does it make sense to look into institutionalising these?

@mateusz
Copy link
Contributor Author

mateusz commented Aug 4, 2014

@tractorcow
Copy link
Contributor

I like the below syntax as an option. I tested this with injector and it seems to work, but the request processor would have to do the sorting in setFilters.

---
Name: requestprocessors
---
Injector:
  RequestProcessor:
    properties:
      filters:
# Default to 0 order
        - '%$VersionedRequestFilter'
# Lower order means first, higher means later
        - filter: '%$MyLowPriorityFilter'
          order: 10 

@hafriedlander
Copy link
Contributor

Ideally this would all be done with events. But events don't exist yet, so we've brought in the request pre/post filter. So my feeling is: given we haven't seen a clear and present need for them, leave them out for now, and we'll do events in 3.2. However it's not an argument made from a position of strength, since I've been wanting to do events for a while, and so far....

@tractorcow
Copy link
Contributor

As a note, using 'before: #other' and 'after: #other' have the opposite result to what's expected.

---
Name: myconf
---
Page:
  matrix:
    - Two
    - Three
---
Name: myfirstconf
Before: '#myconf'
---
Page:
  matrix:
    - One
---
Name: mylastconf
After: '#myconf'
---
Page:
  matrix:
    - Four 

Produces:
Four
Two
Three
One

@mateusz
Copy link
Contributor Author

mateusz commented Aug 4, 2014

We have a clear need for them. We wanted action-based throttling and caching :-) @hafriedlander, do you have any kind of write-up on the events, or it's all in your head? :-)

@hafriedlander
Copy link
Contributor

Mostly in my head. A little bit in Damien's too. There's a module someone's done that was close, but needed work to examine how performant it was (need to be able to look up event receivers without loading all classes into memory)

@mateusz
Copy link
Contributor Author

mateusz commented Aug 4, 2014

I'll back off then, since I see you've got it sorted. This fix above was meant to be for 3.2, so sounds like it can be closed. Oh well, good learning experience :-)

@hafriedlander
Copy link
Contributor

As for ordering, I like using the config system. Before and After are perhaps not well named - they should maybe be LowerPriorityThan and HigherPriorityThan - and higher priority items should come first in the list, because they should be called first.

@tractorcow
Copy link
Contributor

Then everyone is happy, and people ordering and prioritising things actually get the code to do what it says it does. :) Instead of doing what it says it does, but using the wrong words.

@stevie-mayhew
Copy link
Contributor

The ordering through the configuration system is confusing, even though I know what each one does and why, I regularly have to check myself when working out which to use. I also do the same with implode/explode though, so it may just be my brain.

Renaming to LowerPriorityThan and HigherPriorityThan would be extremely awesome.

@tractorcow
Copy link
Contributor

Or 'over' and 'under'?

*/
public function postShorted(
SS_HTTPRequest $request,
SS_HTTPResponse &$earlyResponse,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be a reference. Likewise throughout.

@simonwelsh
Copy link
Contributor

Unless someone can actually commit to getting an events system in for 3.2, then I don't feel like it should be used as a reason to not add controller filters.

@hafriedlander
Copy link
Contributor

@camspiers yeah, can be done in a backwards compatible way (just have an included-as-standard listener on the event that then calls all the RequestFilters)

@halkyon
Copy link
Contributor

halkyon commented Sep 25, 2014

Needs more work, and a decision as to whether we do it or not. Closing for now.

@halkyon halkyon closed this Sep 25, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants