-
Notifications
You must be signed in to change notification settings - Fork 25.2k
Refactor PathTrie and RestController to use a single trie for all methods #25459
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
Conversation
…hods This changes `PathTrie` and `RestController` to use a single `PathTrie` for all endpoints, it also allows retrieving the endpoints' supported HTTP methods more easily. This is a spin-off and prerequisite of elastic#24437
/** | ||
* Encapsulate multiple handlers for the same path, allowing different handlers for different HTTP verbs. | ||
*/ | ||
public class MethodHandlers { |
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.
Could we instead have RestHandler have optional overridable methods for each verb? That would seem less error prone (and greatly reduce the number of classes we define from what we currently have).
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.
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.
I think, @dakrone correct me if I am wrong, that in #24437 we need to know if a rest handler can handle a certain verb to ensure we send the right error back. That might be the reason why we need to be explicit about it. But maybe there is a way to do this?
That's correct, that's the reason for this, the only way I can think of to do it on the handler itself is collapsing every handler for the same rest endpoint into the same class, and then duplicating methods to determine which verbs are supported. I don't think it would be cleaner at all
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.
left some suggestions and comments
if (node == null) { | ||
node = children.get(wildcard); | ||
if (node == null) { | ||
if (trieMatchingMode == TrieMatchingMode.WILDCARD_NODES_ALLOWED) { |
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.
maybe you wanna use a switch here?
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.
I tried to change this to a switch
, but it's not strictly a switch due to the additional if
conditions in the branches below and the else condition, I think it's much cleaner as an if
tree
// is a child wildcard node, use the child wildcard node | ||
if (index + 1 == path.length && node.value == null && children.get(wildcard) != null) { | ||
if (index + 1 == path.length && node.value == null && children.get(wildcard) != null | ||
&& trieMatchingMode != TrieMatchingMode.WILDCARD_ROOT_NODES_ALLOWED |
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.
I like it when we have an explicitly named EnumSet for conditions like this. It's easier to read and understand the code?
|
||
import static java.util.Collections.emptyMap; | ||
import static java.util.Collections.unmodifiableMap; | ||
|
||
public class PathTrie<T> { | ||
|
||
enum TrieMatchingMode { |
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.
I wonder, if we would always retrieve all matching one (recall optimized) and then sort once we have them by the conditions we have here would that be simpler code wise? Also down the road, not in this PR should we use an FST <String, int> that we could score by some predifined value to get the sorted order by default and safe a boat load of objects? the output of the fst would then just point into an array to get the handler...
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.
I wonder, if we would always retrieve all matching one (recall optimized) and
then sort once we have them by the conditions we have here would that be
simpler code wise?
We currently can't easily do this due to not knowing which handler matched for
the params
. For example, let's say the second TrieMatchingMode
matched, but
from the perspective of the caller (RestController
), it needs both the
MethodHandlers
object and the parameters that were parsed out of the URI for
the RestRequest
. It can't use the parameters from the first handler because
they might be a different map that was parsed out.
This is why I used an Iterator with a param supplier, so that they could be
"reset" between invocations, we essentially try to match the request to a
handler multiple times, mutating the RestRequest
each time and resetting it if
we didn't find an appropriate handler.
I do think it would be worth investigating this, however, I think it should be
combined with making PathTrie
use a builder pattern and FST
since otherwise
we'll change this whole class around only to have to basically rewrite it for
using an FST
.
If it's okay with you, I'd like to tackle this in subsequent work that is more
focused, rather than blocking the improve REST PR for internal cleanup. Is that
alright?
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.
sure thing, I didn't imply to change this now
* </pre> | ||
* allowing the value to be updated if desired. | ||
*/ | ||
public void insertOrUpdate(String path, T value, BiFunction<T, T, T> updater) { |
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.
would this be a perfect example to use a bulider pattern? I mean that would make it immutable which is what we want at some point? we can keep the nodes mutability for simplicity? WDYT?
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.
I think that would be worth trying, but it would be a large undertaking. Since this is blocking a breaking change for 6.0, would you be okay with me looking at that in a follow-up?
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.
yeah ++
@s1monw I pushed a commit addressing one of your comments and left responses for the others, thanks for taking a look! |
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.
I like it! I left some minors
* @param handler The handler to actually execute | ||
*/ | ||
public void registerHandler(RestRequest.Method singleMethod, String path, RestHandler handler) { | ||
RestRequest.Method[] methods = new RestRequest.Method[1]; |
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.
why do you construct this array rather thank just passing it on to the var args? and why do we have this extra method wouldn't it be ok to just have the one with the var args?
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.
Whoops yeah I'll remove the extra one
// Between retrieving the correct path, we need to reset the parameters, | ||
// otherwise parameters are parsed out of the URI that aren't actually handled. | ||
final Map<String, String> originalParams = new HashMap<>(request.params()); | ||
return handlers.retrieveAll(getPath(request), () -> { |
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.
👍
/** | ||
* Encapsulate multiple handlers for the same path, allowing different handlers for different HTTP verbs. | ||
*/ | ||
public class MethodHandlers { |
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.
I havent' looked closely but can this class be package private and final?
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.
Definitely can!
* master: Refactor PathTrie and RestController to use a single trie for all methods (elastic#25459) Switch indices read-only if a node runs out of disk space (elastic#25541)
This changes
PathTrie
andRestController
to use a singlePathTrie
for allendpoints, it also allows retrieving the endpoints' supported HTTP methods more
easily.
This is a spin-off and prerequisite of #24437