gravitee-io / issues

Gravitee.io - API Platform - Issues
64 stars 26 forks source link

[dynamic-routing-policy] Route to default endpoint only replacing host #8362

Open tfabien opened 2 years ago

tfabien commented 2 years ago

:rainbow: Feature

As an Api Designer

I want to dynamically route request to a backend url, using the default endpoint, but replacing host

So that I can dynamicaly route to a specific host following a naming convention without maintaining the whole backend url inside the policy

:sunrise_over_mountains: Additional information

Gravitee dynamic-routing policy states:

A typical use case is defining this kind of routing:
- Requests from http://gateway/apis/store/12/info are redirected to http://backend_store12/info
- Requests from http://gateway/apis/store/45/info are redirected to http://backend_store45/info

The problem I see here, is that you have to explicitely define an endpoint for every "store" and maintain this list This can be a hassle, especially if you have a lot of stores...

If you have an organization where stores backend uris follow a convention (say https://storeXXX/myLocalApiPath/API-NAME for any given XXX store and API-NAME ), you could dynamically route to this store/api without declaring it in the endpoints

The only way you can achieve this at the moment is by defining the whole backend URL (eg: including the /myLocalApiPath/myApi part) inside the dynamic routing Policy

"dynamic-routing": {
    "rules": [
        {
            "pattern": "/v1/stores/(?<store>.*)/(?<restOfUri>.*)",
            "url": "http://store{#groups['store']}/myLocalApiPath/myApi/{#group['restOfUri]}"
        }
    ]
}

But this solution leads us to maintaining the whole backend uri inside the policy, and not juste the host If I change the endpoint uri afterwards, this change would be ignored by the policy, leading to incomprehension and errors

What I would like to do is defining a default endpoint such as http://store[PLACEHOLDER]/myLocalApiPath/myApi:

"dynamic-routing": {
    "rules": [
        {
            "pattern": "/v1/stores/(?<store>.*)/(?<restOfUri>.*)",
            "url": "{#endpoints['default'].replace('[PLACEHOLDER]', #group['store'])}/{#group['restOfUri]}"
        }
    ]
}

This cannot be done at the moment because {#endpoints['default']} only returns the default: placeholder that will be resolved after all policies have executed on to an url by the ProxyEndpointResolver

Hence the target url generated by the policy would still be default:restOfUri, which would still be resolved as http://store[PLACEHOLDER]/myLocalApiPath/myApi/restOfUri later on...

Proposition 1:

The later is a bit cleaner, but it would be a breaking change eg: every {#endpoint['XXX']} expression would need to be changed to {#endpoint['XXX'].ref} whereas the first two solutions are just adding functionality

Proposition 2:

Export the resolved endpoint(s) to an request.resolved.endpoint attribute, much like the request.endpoint attribute that already exist. After the policy execution we would get

{#context.attributes['request.endpoint']} // -> default:/restofUri
{#context.attributes['request.resolved.endpoint']} // -> http://store[PLACEHOLDER]/myLocalApiPath/myApi/restOfUri/restofUri

And we could then add a second dynamic routing policy to use this attribute...

"dynamic-routing": {
    "rules": [
        {
            "pattern": "/v1/stores/(?<store>.*)/.*",
            "url": "{#attributes['request.resolved.endpoint'].replace('[PLACEHOLDER]', #group['store'])}"
        }
    ]

Thus http://store[PLACEHOLDER]/myLocalApiPath/myApi/restOfUri would become http://storeXXX/myLocalApiPath/myApi/restOfUri for any XXXstore

Proposition 3

Add a new Policy 'enforce-endpoint-host' letting us change the host when the endpoint is resolved.

:white_check_mark: Acceptance criteria

For all of thoses propositions, If I were to change the endpoint definition of the API (eg: changing it to http://store[PLACEHOLDER]/my__NEW__LocalApiPath/myApi), I would still get the correct dynamic endpoint uri without changing anything to the policies

Note:

tfabien commented 2 years ago

Another possible way I tried but didn't work

Alas, this configuration leads to an error while deploying the api:

io.gravitee.el.exceptions.ExpressionEvaluationException: The template evaluation returns an error.
 at io.gravitee.el.spel.SpelTemplateEngine.getValue(SpelTemplateEngine.java:62)
 at io.gravitee.el.spel.SpelTemplateEngine.getValue(SpelTemplateEngine.java:68)
 at io.gravitee.el.spel.SpelTemplateEngine.convert(SpelTemplateEngine.java:48)
 at io.gravitee.connector.http.HttpConnectorFactory.convert(HttpConnectorFactory.java:81)
 at io.gravitee.connector.http.HttpConnectorFactory.resolve(HttpConnectorFactory.java:62)
 at io.gravitee.connector.http.HttpConnectorFactory.create(HttpConnectorFactory.java:46)
 at io.gravitee.gateway.core.endpoint.lifecycle.impl.EndpointGroupLifecycleManager.start(EndpointGroupLifecycleManager.java:161