apache / apisix

The Cloud-Native API Gateway
https://apisix.apache.org/blog/
Apache License 2.0
14.44k stars 2.51k forks source link

feat: As a user, I want to have a `base_uri` option added to `service`, so that I don't have to add `proxy_rewrite` plugin to every `route` #10238

Open markokocic opened 1 year ago

markokocic commented 1 year ago

Description

It is a very common scenario to have an upstream service and a route that doesn't have the same base uri. In that case, it is necessary to add proxy_rewrite plugin and it's regex_uri parameter to adapt the request to the backend endpoint.

For example, we typically have a route defined under prefix /some_url/smt/v2 that needs to be proxied to an upstream with prefix /backend/api/v4/impl. That can of course be done using proxy_rewrite plugin, as described here but the use case is so typical that it would be better to have it built in into upstream or service object, instead of enabling proxy_rewrite everywhere.

For example, we can add base_uri parameter to upstream or service object and define our route like this:

curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "uri": "/foo/*",
    "upstream": {
        "base_uri": "/bar/baz/",
        "type": "roundrobin",
        "nodes": {
            "httpbin.org:80": 1
        }
    }
}'

This would proxy all requests coming to /foo/something to /bar/baz/something. It would be equivalent to the following configuration:

curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
    "uri": "/foo/*",
    "plugins": {
        "proxy-rewrite": {
            "regex_uri": ["^/foo/(.*)","/bar/baz/$1"]
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "httpbin.org:80": 1
        }
    }
}'

Another benefit of base_uri parameter in upstream would be that we could reuse an upream object withot a need to specify multiple regex_uri rules in different routes.

For example, we can define an upstream like this:

   {
        "base_uri": "/bar/baz/",
        "type": "roundrobin",
        "nodes": {
            "httpbin.org:80": 1
        }

Then we can bind it to different routes (or services) without a need to define proxy_rewrite.

{
    "uri": "/foo/*",
   "upstream_id": 1
 }
{
    "uri": "/fuuu/*",
   "upstream_id": 1
 }

Right now, if we would like to achieve the same, we would need to add proxy_rewrite plugin with different config to all route definitions.

This would make typical endpoint configuration much simpler.

The details still need to be worked out. For example if both base_uri and reger_uri are present, regex_uri should have a precedence.

shreemaan-abhishek commented 1 year ago

SGTM, would you like to submit a PR?

markokocic commented 1 year ago

hi @shreemaan-abhishek, I am still at the PoC / evaluation phase of Apisix and don't yet have ability to participate in development.

kayx23 commented 1 year ago

Could you identify why you can't use a service instead and configure a proxy-rewrite on it? So that it applies to all routes within the service.

markokocic commented 1 year ago

hi @kayx23 the issue with that approach is that each route is bound to a different uri pattern, and regex_uri for that would need to know in advance all route uris to do the correct rewriting. With base_uri on a service or upstream level, that rewriting would be done automatically for all routes.

Btw, many other API management products allow you to specify a full URL for an upstream endpoint, including path,. For example, in WSO2 you can define your upstream to point to https://something.com/api/order/sales/v1/rest and define your route as /order_service/. Then, when you invoke https://gateway/order_service/list/full?id=12 it will proxy your request to https://something.com/api/order/sales/v1/rest/list/full?id=12 without you having to define any rewrite rules.

Of course, my preferred solution would be to have this same concept in Apisix, but I'm not familiar enough with Apisix internals to judge if it would fit conceptually. As I understand it, the concept of upstream is that it is a struct of schema, host, port, ... and not a full URL object. That's why I proposed base_uri which may be less intrusive and fit better to existing upstream, but handy enough to be useful.

madhawa-gunasekara commented 8 months ago

I propose adding a base_uri field at the service level in API Gateway configuration. This base_uri will be an optional field. When present, it will be appended to the route URIs associated with that service. This approach eliminates the need for configuring URI rewriting for each route individually, thus streamlining configuration management.

Please find my below example configration

1.) service configuration

curl http://127.0.0.1:9180/apisix/admin/services/200 \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "base_uri" : "/abc.app" "upstream": { "type": "roundrobin", "nodes": { "127.0.0.1:1980": 1 } } }

2.) route configuration

curl http://127.0.0.1:9180/apisix/admin/routes/100 \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "methods": ["GET"], "uri": "/customer", "service_id": "200" }'

curl http://127.0.0.1:9180/apisix/admin/routes/101 \ -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "methods": ["GET"], "uri": "/product", "service_id": "200" }'

final endpoint calls for route 1 : http://127.0.0.1:1980/abc.app/customer for route 2 : http://127.0.0.1:1980/abc.app/product

This setup ensures that the base_uri configured at the service level is automatically appended to the URIs of all routes associated with that service, simplifying configuration and management tasks.

By adopting this approach, you can effectively utilize the base_uri field to construct the final URIs for your routes without the need for repetitive URI rewriting configurations.

I can check to implement this as feature, what do you think? I think this will be a good additional feature for the services

markokocic commented 1 month ago

This approach proposed by @madhawa-gunasekara looks exactly what would be needed.

It could be implemented both as a new base_uri service plugin, or as a part of service definition. Being part of the service may be cleaner.

@kayx23 @shreemaan-abhishek Would you accept PR for service to accept base_uri parameter?

If not, we may go with a separate plugin.

kayx23 commented 1 month ago

cc @bzp2010 @moonming @juzhiyuan for more evaluation