spring-cloud-gateway
spring-cloud-gateway copied to clipboard
Have Routes Support Multiple URIs
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
.
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 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.
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.
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.
I have been working on prototyping this, stay tuned for more updates as I play around with things.
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 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
.
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.
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
What's the status of this? Think it will be a really cool feature.
No change in status
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 predicates are booleans, so two different routes with appropriate predicates (you can use not()
in the javadsl or a custom predicate)
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.
That seems beyond the scope of this issue. Any filter can set the uri. Something custom is your only solution ATM.
Is there any status update on this?
Nothing yet
@ryanjbaxter any updates on when can we expect this feature ?
There are currently no plans right now
Not sure why it got added to a project
Would love to have this feature as well. Anyway we can upvote this issue to help prioritize?
@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.
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 no it is not supported yet
Hello Spring Cloud Gateway Team,
This would be a great feature! Hopefully, we can see this available. Thank you
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 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
.
You could also use ProxyExchange
from spring-cloud-gateway-webflux.
@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.
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.