nmadzharov
nmadzharov

Reputation: 502

@LoadBalanced RestTemplate to call nested context endpoints

The below guide page is great and works as a base case for ribbon in a spring boot application.

https://spring.io/guides/gs/client-side-load-balancing/

The example stops working as soon as the endpoint mappings become nested - e.g. adding

@RequestMapping(value = "/welcome")

at the class level

@RestController
@SpringBootApplication
@RequestMapping(value = "/welcome") //<------------- ADDED --->
public class SayHelloApplication {

  private static Logger log = LoggerFactory.getLogger(SayHelloApplication.class);

  @RequestMapping(value = "/greeting")
  public String greet() {

And then change the @LoadBalanced RestTemplate call in the client from

String greeting = this.restTemplate.getForObject("http://say-hello/greeting", String.class);

to

String greeting = this.restTemplate.getForObject("http://say-hello/welcome/greeting", String.class);

Calls are failing with attached stacktrace while directly acessing http://localhost:8090/welcome/greeting still works fine. What would be the appropriate way to configure ribbon to load balance requests to long and nested URL endpoints such as domain.com/x/y/z/p/q?

Stacktrace:

java.lang.IllegalStateException: No instances available for say-hello
    at org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute(RibbonLoadBalancerClient.java:79) ~[spring-cloud-netflix-core-1.1.4.RELEASE.jar:1.1.4.RELEASE]
    at org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor.intercept(LoadBalancerInterceptor.java:46) ~[spring-cloud-commons-1.1.1.RELEASE.jar:1.1.1.RELEASE]
    at org.springframework.http.client.InterceptingClientHttpRequest$InterceptingRequestExecution.execute(InterceptingClientHttpRequest.java:85) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.http.client.InterceptingClientHttpRequest.executeInternal(InterceptingClientHttpRequest.java:69) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:53) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:596) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:557) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:264) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at hello.UserApplication.hi(UserApplication.java:31) ~[classes/:na]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_45]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_45]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_45]
    at java.lang.reflect.Method.invoke(Method.java:497) ~[na:1.8.0_45]

Upvotes: 2

Views: 3068

Answers (2)

Bright
Bright

Reputation: 666

I was also using this sample application to get started with Ribbon which is great.

To make it clear, I'd like to spend a bit more words on the design:

  1. The user application, under "/complete/user" folder, is the "client" application and we can access it via "curl http://{host}:8888"; while the say-hello application, under "/complete/say-hello" folder, is the "service provider". As instructed by the example, we should spin up 3 instances via {host}:8090, {host}:9092 and {host}:9999 -- we can check out /complete/user/src/main/resources/application.yml to have a look;
  2. Ribbon, embedded in "client" user application, will maintain a series load balancing service instances (here will be 3 if we spin up the instances as mentioned above) by the default Ping strategy, which will periodically ping the service instances by calling a specific URL. By default is the "/" as we can see the code here (again, which is configurable also by specifying the URI): @Bean public IPing ribbonPing(IClientConfig config) { return new PingUrl(); } Now, let's come back to your issue.

Once you have changed the URI mapping in SayHelloApplication.java by adding explicitly the @RequestMapping(value = "/welcome"), the mapping for "/" in

@RequestMapping(value = "/")
public String home() {
  log.info("Access /");
  return "Hi!";
}

will mean the root path under "/welcome" which is "/welcome/", not the "/" of the say-hello application.

Then we don't have any mapping for the real "/" which is, for example, 'http://{host}:8090/'. In this case, the Ping will fail one by one and eventually Ribbon will mark all the service instances unhealthy so you end up with "No instances available for say-hello".

Upvotes: 0

Shawn Clark
Shawn Clark

Reputation: 3440

The issue is by adding @RequestMapping to the class you also changed the / handler to move from the root to /welcome/. To make it so the load balancer can keep working you have to update the PingUrl being used within the SayHelloConfiguration of the user app. Make it new PingUrl(false, "/welcome/")

Upvotes: 2

Related Questions