Move towards a function-like model for resolving
An improved model of definition of resolvers will consolidate configuration options and enable more use cases.
What is right now referred to as a resolve config, should be renamed as a "resolver". A resolver is like a function definition, taking parameters (depending on its definition) along with context/environment information (request context, server configuration and environment) and evaluating to a value (or multiple values).
A resolver is defined under a chosen name. The exact configuration for the definition will also depend on the resolution method, e.g. is the value resolved by executing a process or by performing a JSON-RPC call. After definition, the resolver can be invoked/called - just like a function is called - at arbitrary places through other directives/code. The new concepts decouple definition of the resolver from the call site and make it simpler to introduce new resolution methods: Nothing has to change at the call site.
# Define a resolver `user-primary-group` executing a process
DefineResolverByProcess user-primary-group cache=default,cache_ttl=10m /opt/user-tools/fetch-user-primary-group %{REMOTE_USER}
# Invoke the resolver user-primary-group during the stage `check_authn:after` and store the result in the reqenv variable PRIMARY_GROUP during
ResolveVariable PRIMARY_GROUP destination=reqenv,stage=check_authn:after user-primary-group
In some situations, the caller will want to pass specific "parameters" to the resolver.
One possibility is to store the parameters temporarily in the reqenv so that resolver can access them via expressions.
However, it may also be possible to add a new function to expressions to access a separately defined set of named parameters, e.g. %{params:group_id}
or %{callparams:group_id}
.
Use cases:
- Store resolve result in a request env variable
- Use resolve result through a single
Require
provider - #13 Resolve as part of an ap_expr
- Offer a "resolve" API to external modules
- In their own configuration, they accept the name of the resolver
- They can call an API function
rslv_result_t rslv_resolve(const char *resolver_name, ...)
- Here, passing params is particularly interesting
Open questions:
- Where in the configuration can resolvers be defined? Generally, they should be global, without the possibility to overwrite a resolver (name) with another implementation. Distinction between vhosts/locations etc. can be done via other directives, e.g variable resolution overloading as it is implemented right now. On the other hand, especially for external modules it may be more convenient if they don't have to implement overloading themselves. Also, it may be nice to have a resolver definition close to its (single) call site, e.g. inside a VirtualHost block.
- The "resolve" call parameter signature is clear: Besides the resolver name, options related to resolution may be passed and also specific parameters (secondary priority). Then, the resolver definition can rely on ap_expr expressions to build the exact specification specific to the resolution method.
- The "resolve" call return value is not clear.
- Returning a single string is the most common case.
- Error information may also be returned. And the impact of the errors is to be expanded in #8.
- #6 should expand resolved values to multiple values. So should there be a separate "resolve" API functions per result type?
- This requires an abstract
rslv_result
type. - The resolution-method-specific result type may be contained inside.
- Checking that the call matches the definition could likely be achieved to some extent but remains a topic of future work.