Feign client manually. Load balancer does not have available server for client - netflix-eureka

I have two services registered with eureka. Service C calls service A. Service C is feign client. I want implement feign client manually. But I catch an exception:
com.netflix.client.ClientException: Load balancer does not have
available server for client: service-test-a
Application class:
#EnableEurekaClient
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Feign interface:
#Component
public interface FeignService {
#RequestLine("GET /")
public String getServiceA();
}
Feign config:
#Configuration
#Import(FeignClientsConfiguration.class)
public class MyConfig {
}
Controller:
#RestController
public class Controller {
private FeignService feignService;
#Autowired
public void Controller() {
feignService = Feign.builder()
.client(RibbonClient.create())
.target(FeignService.class, "http://service-test-a");
}
#RequestMapping(value = "/build", method = RequestMethod.GET)
public String getServiceC() {
return feignService.getServiceA();
}
}
What am I doing wrong?

AFAIK, there is no easy way of using OpenFeign with eureka. There is no guide or example for that. Also I guess that it may require some additional implementations and configuration.
Instead, please try to use Spring Cloud Feign. It provides full integration with eureka and ribbon without any additional implementation. You can use Spring Cloud Feign with just a few changes in your above code.
Please refer to Spring Cloud Feign

Related

spring cloud zuul can not works with a simple example code

I'm newbie to Spring Cloud. Following the example of Start Guide. I'm stuck at Zuul example.
With a request to zuul, my client app not receive any request from zuul and get 405(Method not allowed) to my browser.
But test without zuul, the client app can response success.
Simple code as this:
Zuul Server:
#EnableZuulProxy
#SpringBootApplication
public class MyZuulApp {
public static void main(String[] args) {
SpringApplication.run(MyZuulApp.class, args);
}
#Bean
public SimpleFilter simpleFilter() {
return new SimpleFilter();
}
}
Configuration:
##Zuul routes. Here for /student path, we are routing to localhost:8090 with extra path after that.
zuul.routes.book_app.url=http://localhost:8090
#zuul.routes.book_app=/book_app/**
##Ribbon is auto integrated with Zuul and for this exercise we are not using that.
ribbon.eureka.enabled=false
##Will start the gateway server #8080
server.port=8083
logging.level.org.springframework.web=DEBUG
Client App:
#RestController
#SpringBootApplication
public class BookApp {
#RequestMapping(value = "/available")
public String available() {
System.out.println("get abailable - ");
return "Spring in Action";
}
#RequestMapping(value = "/checked-out")
public String checkedOut() {
return "Spring Boot in Action";
}
public static void main(String[] args) {
SpringApplication.run(BookApp.class, args);
}
}
Configuration:
spring.application.name=book_app
server.port=8090
logging.level.org.springframework.web=DEBUG
Full code has organized at Github.
See my-zuul and book-app for that situation.
Thanks
I downloaded your code and run locally and it worked as expected.
Your Zuul application.properties
zuul.routes.book_app.url=http://localhost:8090
server.port=8083
Since your book application name is book_app.Zuul will proxy request to /book_app.So you have to send requests like this: http://localhost:8083/book_app/checked-out/.

How to know the host with ribbon?

I have an application with eureka, ribbon and feign. I have a feign RequestInterceptor, but the problem is that I need to know which is the host where I'm making the call. So far, with my current code I just can get the path with the RequestTemplate, but not the host.
Any idea?
I'm new to this as well, but I believe I've just learned something relevant to your question.
In the service you're creating, each instance can be given some unique identifier tied to a property included in its instance profile. Some .properties examples below:
application.properties (shared properties between instances)
spring.application.name=its-a-service
eureka.client.service-url.defaultZone=http://localhost:8761/eureka
application-instance1.properties
server.port=5678
instance.uid=some-unique-property
application-instance2.properties
server.port=8765
instance.uid=other-unique-property
This service, as an extremely contrived example will show, can send out #Value annotated attributes to be consumed by the Ribbon app:
#SpringBootApplication
#EnableDiscoveryClient
#RestController
public class ItsAServiceApplication {
#Value("${server.port}")
private int port;
#Value("${instance.uid}")
private String instanceIdentifier;
public static void main(String[] args) {
SpringApplication.run(ItsAServiceApplication.class, args);
}
#RequestMapping
public String identifyMe() {
return "Instance: " + instanceIdentifier + ". Running on port: " + port + ".";
}
}
And just to complete the example, the Ribbon app that might consume these properties could look like this:
#SpringBootApplication
#EnableDiscoveryClient
#RestController
public class ServiceIdentifierAppApplication {
#Autowired
private RestTemplate restTemplate;
public static void main(String[] args) {
SpringApplication.run(ServiceIdentifierAppApplication.class, args);
}
#GetMapping
public String identifyMe() {
return restTemplate.getForEntity("http://its-a-service", String.class).getBody();
}
#Bean
#LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
Results:
Reloading the rest template created by the services
As I said earlier, I'm pretty new to this and have just been learning myself. Hopefully this has given you an idea for how you might send these properties! I would imagine creating a dynamic property identifier through Spring Cloud Config would be ideal here.

Spring Cloud Netflix Feign not working correcly

Trying to use Feign Client with Eureka and Ribbon-
Have a service registered using Eureka with name-test-service.
Was able to use Ribbon and Eureka successfully as follows-
#Autowired
private LoadBalancerClient loadBalancer;
public void getEmployee() throws RestClientException, IOException {
ServiceInstance serviceInstance=loadBalancer.choose("test-service");
String baseUrl=serviceInstance.getUri().toString();
baseUrl=baseUrl+"/test";
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response=null;
try{
response=restTemplate.exchange(baseUrl,
HttpMethod.GET, getHeaders(),String.class);
}
This works correctly including load balancing.
Now tried to replace RestTemplate with Feign as follows-
#FeignClient(name="test-service")
public interface RemoteCallService {
#RequestMapping(method=RequestMethod.GET, value="/test")
public String resp();
}
And making the call with feign as follows-
#Autowired
private RemoteCallService loadBalancer;
public void getEmployee() throws RestClientException, IOException {
String data=loadBalancer.resp();
}
But this is not working. The Feign URL is not getting formed correctly.
On debugging the FeignClient has the following values-
HardCodedTarget(type=RemoteCallService, name=test-service, url=http://test-service)
Can anyone tell what am i missing.
Thanks

spring cloud zuul static routes with circuit breaker

I have configured Zuul with static routes to other micro services. Is there a way to enable CircuitBreaker when calling out to other services?
As you said Zuul will automatically wrap every routes inside Ribbon and Hystrix. But it's also quite easy to integrate with Ribbon and Hystrix between microservices. You can also use Feign to handle REST calls.
Imagine you have two services serviceA and serviceB, and that you want serviceA to call serviceB using Ribbon and Hystrix. Let's assume you have Eureka server running on the localhost and default port (8761)
serviceA
#SpringBootApplication
#EnableDiscoveryClient
public class ServiceAapplication{
public static void main(String[] args) {
SpringApplication.run(ServiceAapplication.class, args);
}
}
#RestController()
#RequestMapping("/rest")
class DummyRestController{
#RequestMapping(method = RequestMethod.GET, path = "/hello")
String hello(){
return "Hello World!";
}
}
ServiceB
#SpringBootApplication
#EnableDiscoveryClient
#EnableCircuitBreaker
#EnableFeignClients
public class ServiceBapplication{
public static void main(String[] args) {
SpringApplication.run(ServiceBapplication.class, args);
}
}
#FeignClient(value = "serviceA") //must be same name as the service name in Eureka
#RibbonClient(name = "serviceA") //must be same name as the service name in Eureka
interface ServiceAClient {
#RequestMapping(method = RequestMethod.GET, value = "/rest/hello")
String helloFromServiceA();
}
#RestController()
#RequestMapping("/foo")
class DummyRestController{
private final ServiceAclient client;
#Autowired
DummyRestController(ServiceAclient client){
this.client = client;
}
#RequestMapping(method = RequestMethod.GET, path = "/bar")
String hello(){
return client.helloFromServiceA();
}
}
Now if you do a GET on serviceB with foo/bar it will use:
Eureka to find the host and port for serviceA
Ribbon to load balance between multiple instances of serviceA
The whole thing is wrapped into a Hystrix command
Because of the #EnableCircuitBreaker annotation, your serviceB will expose a Hystrix stream. If you run a HystrixDashboard server you can connect to this stream and you will see a helloFromServiceA command on the dashboard.
You can configure Ribbon and Hystrix in your usual configuration file or using a separate configuration class in the #FeignClient and #RibbonClient annotations. You can find more info here
Important: If you want Ribbon to retry on a different instance during a timeout, make sure the Hystrix timeout is higher than the Ribbon time out. See this answer

Spring Security and custom ws authentication

We just went over to using Spring Boot and Spring Security for a new project that we have. The problem is, our company uses CXF with a custom implemented SAML authentication regime. Of course the custom implementation is rather old, so we are locked to CXF 2.7.*.
A while back, all worked fine since we only exposed SOAP web services and did not use Spring Security. The authenticator that we used in the old solution, uses some kind of JBossWebRealm thingy to authenticate through org.apache.catlina.connector.Request.
But now, we are going to expose REST services as well, using LDAP as an authentication provider. This works like a dream using Spring Security, but now, the security on SOAP services fails. It now tries to use Spring Security to authenticate using the SAML token as a password into AD.
Currently we have the default Servlet that Spring Boot creates. This one exposes the REST resources and a simple health check webpage.
Then we have a servlet that exposes SOAP web services and one that exposes metrics (REST).
Servlet setup:
#Configuration
#EnableAutoConfiguration
#Import(ApplicationConfig.class)
public class ApplicationServletInitializer extends SpringBootServletInitializer {
#Bean
public WebMvcConfigurerAdapter dispatcherServletConfigurer(final MDCInterceptor mdcInterceptor) {
return new WebMvcConfigurerAdapter() {
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/internal/*");
}
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(mdcInterceptor);
}
};
}
#Bean(name = "webServiceServlet")
public ServletRegistrationBean webServiceServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
servletRegistrationBean.setServlet(new CXFServlet());
servletRegistrationBean.setName("webServiceServlet");
servletRegistrationBean.addUrlMappings("/ws/*");
servletRegistrationBean.setLoadOnStartup(2);
return servletRegistrationBean;
}
#Bean(name = "metricsServlet")
public ServletRegistrationBean metricsServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
servletRegistrationBean.setServlet(new MetricsServlet());
servletRegistrationBean.setName("metricsServlet");
servletRegistrationBean.addUrlMappings("/internal/metrics/*");
servletRegistrationBean.setLoadOnStartup(3);
return servletRegistrationBean;
}
}
Security setup:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#ComponentScan(basePackageClasses = {
MDCInterceptor.class,
WebSecurityConfigurerAdapterConfig.class
})
public class RestSecurityConfig {
#Value("${ldap.url}")
private String ldapUrl;
#Value("${ldap.domain}")
private String ldapDomain;
#Bean
public ActiveDirectoryLdapAuthenticationProvider authenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(ldapDomain, ldapUrl);
provider.setAuthoritiesMapper(authoritiesMapper());
provider.setUserDetailsContextMapper(userDetailsMapper());
provider.setUseAuthenticationRequestCredentials(true);
provider.setConvertSubErrorCodesToExceptions(true);
return provider;
}
#Bean
public MyAuthoritiesMapper authoritiesMapper() {
return new MyAuthoritiesMapper();
}
#Bean
public MyUserDetailsMapper userDetailsMapper() {
return new MyUserDetailsMapper();
}
}
#Component
public class WebSecurityConfigurerAdapterConfig extends WebSecurityConfigurerAdapter {
#Autowired
private ActiveDirectoryLdapAuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/api/**").permitAll()
.antMatchers("/api/**").hasRole("READ")
.and().httpBasic()
.and().anonymous()
.principal(anonymousPrincipal())
.authorities(anonymousRoles());
}
}
web.xml setup:
<security-constraint>
<web-resource-collection>
<web-resource-name>All pages</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
</security-constraint>
Does anyone know if it's possible to solve this? It's not an option to remove the usage of the old security framework that authenticates SAML for the SOAP web services.
Figured it out.
When configuring Spring Security, in WebSecurityConfigurerAdapter, you can also override:
protected void configure(WebSecurity web).
Within this one, you can specify what to ignore.
E.g.:
web.ignoring().antMatchers("/ws/**");