spring-cloud-gateway icon indicating copy to clipboard operation
spring-cloud-gateway copied to clipboard

Have Routes Support Multiple URIs

Open ryanjbaxter opened this issue 7 years ago • 36 comments

It would be nice if a route could be configured to make requests to multiple URIs and then return the results to a specified controller to aggregate the responses.

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
      # =====================================
      - id: greeting
        uris: 
        - lb://greeting
        - lb://name
        predicates:
        - Path=/greeting/**
        controller: blah

The controller, blah in this case, would take the result of calling the greeting and name services and return a response for the client.

For example if the greeting service returned Hello and the name service returned Ryan. The controller would return Hello Ryan.

ryanjbaxter avatar Nov 16 '17 17:11 ryanjbaxter

It would have to return something structured. Like

{
  "greeting": "Hello",
  "name": "Ryan"
}

Something automated. Maybe some kind of non-blocking template

{{greeting.response}} {{name.response}}

spencergibb avatar Jan 16 '18 20:01 spencergibb

@spencergibb I have been thinking more about this recently...wondering what you thought of this

spring:
  application:
    name: gateway
  cloud:
    gateway:
      routes:
      - id: greeting
        uri: http://greeting
        predicates:
        .....
        filters:
        ......
      - id: name
        uri: lb://name
        filters:
        ......
      - id: my-composite
         routes:
         - id: greeting
           path: /foo
           headers:
             cookie: foo,bar
             host: www.greeting.com
           method: post
           userequestbody: true
         - id: name
           path: /bar
           headers:
             cookie: foo,bar
             host: www.name.com
           method: get
         predicates: 
         ....
         filters:
         ....

Some import things of note

  • Predicates: I was thinking removing the requirement for a route to have predicates (which I think is a requirement today based on what I see in Route.Builder.build). That way I can define a route that might not be accessible itself but still be part of a composite route.

  • Filters: I am a little unsure of how the filters will work in a composite route. In some cases like hystrix or rate limiter, I could see if applying to the entire composite route. However something like add header doesn't seem to make sense. I could see it making sense if it added that header to each route in the composite but then that seems to have a different behavior from hystrix or rate limiting and I am not sure if that makes sense or not.

ryanjbaxter avatar Apr 03 '18 17:04 ryanjbaxter

I'd love to have this feature for BFFs. We're currently investigating using Gateway for BFF endpoints that can be directly proxied downstream but when composing APIs we still have to create controllers and manually handle requests to have parity with all the things Gateway does.

michael-barker avatar Apr 11 '18 23:04 michael-barker

I think we could get there. A couple of issues to consider: transforming individual responses (which we're working on) and combining the multiple requests. I was thinking of maybe using something like timeleaf(sp?) That supports reactive templates.

spencergibb avatar Apr 11 '18 23:04 spencergibb

I have been working on prototyping this, stay tuned for more updates as I play around with things.

ryanjbaxter avatar Apr 12 '18 15:04 ryanjbaxter

So wouldn't the nested routes under a composite route make binding a little trickier and yaml structure a bit more complicated?

I've been poking around with a different approach that would use the existing YAML structure and do the work in filters: https://github.com/Fitzoh/spring-cloud-gateway-114-spike

There's a ForkingGatewayFilterFactory and a JoiningGatewayFilterFactory.

The forking filter makes a request to Config.uri, and stores the response in an exchange attribute map (Config.name -> Mono<ClientResponse>)

The joining filter grabs the response monos out of the exchange attribute, transforms the responses from Map<String, Mono<ClientResponse>> -> Mono<Map<String, ClientResponse>> and writes it to the response.

Sample java config

r.path("/forkjoin")
        .filters(f -> {
            f.filter(forkingGatewayFilterFactory.apply(c -> c.setName("fork1").setUri("http://httpbin.org/anything/fork1")));
            f.filter(forkingGatewayFilterFactory.apply(c -> c.setName("fork2").setUri("http://httpbin.org/anything/fork2")));
            f.filter(joiningGatewayFilterFactory.apply(c -> c.setName("join")));
            return f;
        })
        .uri("http://google.com"))

super rough output : $ curl http://localhost:8080/forkjoin

{fork2={
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "ReactorNetty/0.7.6.RELEASE"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "67.149.185.133", 
  "url": "http://httpbin.org/anything/fork2"
}
, fork1={
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "ReactorNetty/0.7.6.RELEASE"
  }, 
  "json": null, 
  "method": "GET", 
  "origin": "67.149.185.133", 
  "url": "http://httpbin.org/anything/fork1"
}
}

I'm not sure if the JoiningFilter is the right way to go or not, could also potentially do a forward instead.

fitzoh avatar May 05 '18 02:05 fitzoh

@fitzoh what if in the filters i want to apply things like filters to modify the request/response for each route individually? The is why I liked referencing existing route definitions. I wanted to run each route of the "composite" through the existing RoutePredicateHandlerMapper.

ryanjbaxter avatar Jun 26 '18 19:06 ryanjbaxter

That makes sense @ryanjbaxter, but the predicates feel a little weird to me in that approach.

Do we just ignore the top level predicates for composite routes? Can there be additional predicates within a composite route that do get honored? (ie maybe we never want it to be available as a top level route, and only sometimes get called as a composite route)

On an overall feature level I feel like this has the potential to add a lot of cognitive overhead, and config file support would leave you w/ too much yaml to make sense of (at least for me), especially for figuring out how to reassemble downstream requests in a way that makes sense.

To me this seems like something that should be done in code, potentially w/ some utility functions to make it a bit less awkward.

fitzoh avatar Jul 07 '18 01:07 fitzoh

Dave added the ProxyExchange obejct for webflux in the spring-cloud-gateway-webflux which would make this easy in code. Could use a simple request mapping or forward routes

spencergibb avatar Jul 07 '18 01:07 spencergibb

What's the status of this? Think it will be a really cool feature.

alxsimo avatar Jul 10 '18 06:07 alxsimo

No change in status

spencergibb avatar Jul 10 '18 13:07 spencergibb

I feel like this might be similarly related.

If I want to do a conditional flow e.g.

uri targeta = "uria" uri targetb = "urib" bool a = true; // might not always be true if (a) then goto targeta else goto targetb

What would be the proper way to express something like this?

dave-fl avatar Aug 09 '18 14:08 dave-fl

@dave-fl predicates are booleans, so two different routes with appropriate predicates (you can use not() in the javadsl or a custom predicate)

spencergibb avatar Aug 09 '18 14:08 spencergibb

Thank you @spencergibb

Let me add some more details.

The input to the predicate will only be known at runtime - can I have a custom predicate to determine this?

targeta and targetb must be discovered at runtime (they are not fixed and must be sourced from another service) - how can I set the URI dynamically?

Additionally if I visit targetb and the response is successful, I would like to revisit targeta.

dave-fl avatar Aug 09 '18 14:08 dave-fl

That seems beyond the scope of this issue. Any filter can set the uri. Something custom is your only solution ATM.

spencergibb avatar Aug 09 '18 14:08 spencergibb

Is there any status update on this?

sskurapati avatar Jan 30 '19 07:01 sskurapati

Nothing yet

ryanjbaxter avatar Jan 31 '19 21:01 ryanjbaxter

@ryanjbaxter any updates on when can we expect this feature ?

ninad050 avatar Feb 06 '19 05:02 ninad050

There are currently no plans right now

spencergibb avatar Feb 06 '19 15:02 spencergibb

Not sure why it got added to a project

spencergibb avatar Feb 06 '19 15:02 spencergibb

Would love to have this feature as well. Anyway we can upvote this issue to help prioritize?

natraj09 avatar Feb 25 '19 16:02 natraj09

@natraj09 add your thumbs up here https://github.com/spring-cloud/spring-cloud-gateway/issues/114#issue-274604083

This would be a major feature and we already have a programmatic way to do this with ProxyExchange. I'm not sure how much we can improve on that anyway. Without being able to customize how multiple responses are joined I don't see much value in doing this.

spencergibb avatar Feb 25 '19 16:02 spencergibb

Hi,

Sorry I may missed but is multiple URIs already supported. I am thinking of using this feature for legacy systems.

I can do work around by registering in eureka in behalf of the legacy apps. But it would be nice if gateway supports this.

sincang avatar Mar 29 '19 02:03 sincang

@sincang no it is not supported yet

ryanjbaxter avatar Apr 02 '19 00:04 ryanjbaxter

Hello Spring Cloud Gateway Team,

This would be a great feature! Hopefully, we can see this available. Thank you

patpatpat123 avatar Apr 06 '19 00:04 patpatpat123

Hi SCG Team,

I am building an api gateway using SCG. There are some cases where we need to make 3 sequence of requests to different URIs for a single request. The output of 1st request is the input / path param / headers for subsequent request. And we expect the response are aggregated sometimes, and sometimes we need the 2nd request response.

If SCG supports this feature, it would have been a best feature in it.

Thanks.

sthanaraju avatar Apr 07 '19 04:04 sthanaraju

@sthanaraju What you are describing and what is being asked from here seem to be two different things.

The output of 1st request is the input / path param / headers for subsequent request"

If you need to do this, just use WebClient, if you want to be more low level, you can use HttpClient.

dave-fl avatar Apr 08 '19 14:04 dave-fl

You could also use ProxyExchange from spring-cloud-gateway-webflux.

spencergibb avatar Apr 08 '19 15:04 spencergibb

@spencergibb I noticed that in the documentation @RestController is used.

If it is possible, this should really leverage functional endpoints as they seem to operate at an order of magnitude faster relative to their annotated counterparts.

dave-fl avatar Apr 08 '19 17:04 dave-fl

The webflux version is new https://cloud.spring.io/spring-cloud-gateway/2.1.x/multi/multi__building_a_simple_gateway_using_spring_mvc_or_webflux.html Gateway has no functional endpoint functionality yet.

spencergibb avatar Apr 08 '19 17:04 spencergibb