Unable to forward Request to URI with context-path - java

I am unable to forward my request to a URI with contextpath.
For example I am requesting my Spring cloud gateway server with
http://localhost:1010/mygateway/server/date
And I expect it to forward to
http://myapiserver.com/xyzapi/v1/server/date
But It gives me 404
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("api-server-date", r -> r.host("**")
.and()
.path("/mygateway/server/date")
.filters(f -> f.stripPrefix(1))
.uri("https://myapiserver.com/xyzapi/v1"))
.build();
}
It works when I change my configuration to this
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("api-server-date", r -> r.host("**")
.and()
.path("/mygateway/xyzapi/v1/server/date")
.filters(f -> f.stripPrefix(1))
.uri("https://myapiserver.com"))
.build();
}
I don't know what configuration I am providing wrong. It should work.
All I want is to use
https://myapiserver.com/xyzapi/v1
As my URI. Meaning all Incoming traffic to my spring cloud gateway server should forward to this URL after appending the incoming url
Kindly suggest what is wrong ? Or is it something not possible with Spring Cloud Gateway ?

I think what you want (looking at online samples) is:
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
String requestHandlerUrl = "http://myapiserver.com/xyzapi/v1/server/date";
return builder.routes()
.route("api-server-date", r -> r.host("**")
.and()
.path("/mygateway/server/date")
.filters(f -> f.stripPrefix(1))
.uri(requestHandlerUrl)
.build();
}
... so that requests made to whatever host your app is deployed on are forwarded/passed to requestHandlerUrl as the destination.
It looks as if /mygateway/server/date is the "shortcut" or alias for the URL you actually want to handle the request.
Some examples here: https://cloud.spring.io/spring-cloud-gateway/multi/multi__configuration.html#_fluent_java_routes_api

Related

Apply retry to each and every endpoint in the spring webflux application

I have created below webclient and using it inside of my service to make HTTP third party calls.
#Configuration
public class WebclientConfig {
#Bean
public WebClient webClient() {
// custom client connector with connection pool disabled is being used as by default the connection pooling is done and connection reset happens after some idle time.
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.newConnection()))
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
and in my service, I am calling the third party service like below.
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class) //code below this, do I have to copy for each webclient request to configure the retry, even if the values are same
.retryWhen(Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS))
.doBeforeRetry((value) -> log.info("Retrying request " + value))
.filter(error -> error instanceof WebClientRequestException)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) ->
new RuntimeException(retrySignal.failure().getMessage())));
}
My question is as in comment in above code.
I have multiple webclient calls, but I want to configure the retry backoff configuration at a single place. How can I do that? so that my code should look like below
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class)
.somCommonRetryCodeWrappingTheRetryLogic();
}
You can use transform operator for this purpose:
private Flux<BusinessObject> getBusinessObjects(String serviceURL) {
return this.webClient.get()
.uri(serviceURL)
.retrieve()
.bodyToFlux(BusinessObject.class)
.transform(originalFlux -> wrapWithRetry(originalFlux));
}
private <T> Flux<T> wrapWithRetry(Flux<T> originalFlux) {
return originalFlux
.retryWhen(Retry.backoff(3, Duration.of(2, ChronoUnit.SECONDS))
.doBeforeRetry((value) -> log.info("Retrying request " + value))
.filter(error -> error instanceof WebClientRequestException)
.onRetryExhaustedThrow((retryBackoffSpec, retrySignal) ->
new RuntimeException(retrySignal.failure().getMessage())));
}
Only drawback is that if you have Mono use cases as well then you need to implement it twice.
If that is still too much copy-paste, you can consider defining an ExchangeFilterFunction to apply retry for every WebClient call automatically. Something like this:
WebClient.builder()
// ...
.filter((request, next) -> next.exchange(request).retry()) // add full retry config here
.build();

How to Retrieve OAuth2AuthorizedClient in a request when using WebFlux

I have a back-end(Springboot) application that is connected to Azure AD and a front-end application that accesses it. In the front-end, I am requiring the user to authenticate using MSAL and passing this authentication to the BE using the On-Behalf-Of flow.
In the front-end, when I am trying to specify the registered client I simple use:
#RegisteredOAuth2AuthorizedClient("back-end") OAuth2AuthorizedClient authorizedClient
I'm trying to create another back-end application that my existing back-end will call and pass the authentication using OBO flow. To check the difference between the initial token from the user and the token the BE will provide to the new BE application, I created a log that fetch the token from these client like authorizedClient.getAccessToken().getTokenValue().
Now that I don't want the explicit approach and want only to add directly in the webclient request the .attributes(clientRegistrationId("new-back-end")), is there any way to check the token? Or at least get the OAuth2AuthorizedClient from the request?
Sample code:
webClient.get()
.uri(new URI(resourceBaseUri + resourceEndpoint))
.attributes(clientRegistrationId("new-be-app"))
.retrieve()
.bodyToMono(String.class)
.block();
• You can do the same as desired by you by using the ‘ServerOAuth2AuthorizedClientExchangeFilterFunction’ to determine the client to use by resolving the ‘OAuth2AuthorizedClient’ from the ‘ClientRequest.attributes()’. The following code shows how to set an ‘OAuth2AuthorizedClient’ as a request attribute: -
#GetMapping("/")
public Mono<String> index(#RegisteredOAuth2AuthorizedClient("okta")
OAuth2AuthorizedClient authorizedClient) {
String resourceUri = ...
return webClient
.get()
.uri(resourceUri)
.attributes(oauth2AuthorizedClient(authorizedClient))
.retrieve()
.bodyToMono(String.class)
...
.thenReturn("index");
}
Note: - ‘oauth2AuthorizedClient()’ is a static method in ‘ServerOAuth2AuthorizedClientExchangeFilterFunction’.
Also, please note that the following code shows how to set the ‘ClientRegistration.getRegistrationId()’ as a request attribute: -
#GetMapping("/")
public Mono<String> index() {
String resourceUri = ...
return webClient
.get()
.uri(resourceUri)
.attributes(clientRegistrationId("okta"))
.retrieve()
.bodyToMono(String.class)
...
.thenReturn("index");
}
You can use the code below also for your purpose: -
#Component
#RequiredArgsConstructor
public class OAuth2Utils {
private final ServerOAuth2AuthorizedClientRepository authorizedClientRepository;
public Mono<OAuth2AuthorizedClient> extractOAuth2AuthorizedClient(ServerRequest request) {
return request.principal()
.filter(principal -> principal instanceof OAuth2AuthenticationToken)
.cast(OAuth2AuthenticationToken.class)
.flatMap(auth -> authorizedClientRepository.loadAuthorizedClient(auth.getAuthorizedClientRegistrationId(), auth, request.exchange()));
}
}
Please find the links below for more information: -
How to access OAuth2AuthorizedClient in Webflux Functional Endpoints?
https://docs.spring.io/spring-security/reference/reactive/oauth2/client/authorized-clients.html#_providing_the_authorized_client

Spring Cloud API Gateway Routing based on data in JWT

I am using Spring Cloud Gateway, I want to route incoming request to ServiceA or ServiceB based on a value in the JWT token. So basically I want to extract the Token and get a userId from it and based on that information route to either service-a and service-b.
So the incoming request is something like "/v1/customers" and I need to prefix it with either /service-a/v1/customers to that it will be route to urlServiceA.
Here is the code of my Routes.
#Bean
public RouteLocator myRoutes(RouteLocatorBuilder builder) {
String urlServiceA = "a";
String urlServiceB = "b";
return builder.routes()
.route(r -> r.path("/service-a/v1/**")
.filters(f -> f.filter(authentication))
.uri(urlServiceA))
.route(r -> r.path("/service-b/v1/**")
.filters(f -> f.filter(authentication))
.uri(urlServiceB))
.build();
}
How can I do it ? Please help.
Thanks

Follow redirection with cookies using WebFlux

I am using WebFlux with netty to make third party calls for my spring boot app. A post request with form parameters is made on client's provided url and client responds with 302 status and a location. The code I have written below is able to follow redirections but doesn't send cookies with it which is causing subsequent redirections to fail.
WebFlux config
#Bean
public WebClient webClient() {
ExchangeStrategies exchangeStrategies = ExchangeStrategies.builder()
.codecs((configurer) -> {
configurer.defaultCodecs().jaxb2Encoder(new Jaxb2XmlEncoder());
configurer.defaultCodecs().jaxb2Decoder(new Jaxb2XmlDecoder());
}).build();
exchangeStrategies.messageWriters().stream()
.filter(LoggingCodecSupport.class::isInstance)
.forEach(writer -> ((LoggingCodecSupport) writer)
.setEnableLoggingRequestDetails(Boolean.TRUE));
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.create()
.followRedirect(true)
.secure()))
.exchangeStrategies(exchangeStrategies)
.build();
}
Post Request
private <T> Mono <String> buildPostRequest(MultiValueMap <String, String> formData, String postUrl) {
return client.post()
.uri(postUrl)
.body(BodyInserters.fromFormData(formData))
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.retrieve()
.bodyToMono(String.class);
}

How to get the response body as a String using Spring Gateway?

I need to get information from the response body of certain responses as a json string.
I have seen the following example of modifying it:
#Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org")
.filters(f -> f.prefixPath("/httpbin")
.modifyResponseBody(String.class, String.class,
(exchange, s) -> Mono.just(s.toUpperCase()))).uri(uri)
.build();
}
GatewayFilter Factories
But how would I just log it as a string?
Can I do it in a custom filter?

Categories

Resources