Feign with RibbonClient and Consul discovery without Spring Cloud - spring

I was trying to setup Feign to work with RibbonClient, something like MyService api = Feign.builder().client(RibbonClient.create()).target(MyService.class, "https://myAppProd");, where myAppProd is an application which I can see in Consul. Now, if I use Spring annotations for the Feign client (#FeignClient("myAppProd"), #RequestMapping), everything works as Spring Cloud module will take care of everything.
If I want to use Feign.builder() and #RequestLine, I get the error:
com.netflix.client.ClientException: Load balancer does not have available server for client: myAppProd.
My first initial thought was that Feign was built to work with Eureka and only Spring Cloud makes the integration with Consul, but I am unsure about this.
So, is there a way to make Feign work with Consul without Spring Cloud?
Thanks in advance.

In my opinion, it's not feign work with consul, its feign -> ribbon -> consul.
RibbonClient needs to find myAppProd's serverList from its LoadBalancer.
Without ServerList, error: 'does not have available server for client'.
This job has been done by SpringCloudConsul and SpringCloudRibbon project, of course you can write another adaptor, it's just some glue code. IMHO, you can import this spring dependency into your project, but use it in non-spring way . Demo code:
just write a new feign.ribbon.LBClientFactory, that generate LBClient with ConsulServerList(Spring's class).
public class ConsulLBFactory implements LBClientFactory {
private ConsulClient client;
private ConsulDiscoveryProperties properties;
public ConsulLBFactory(ConsulClient client, ConsulDiscoveryProperties consulDiscoveryProperties) {
this.client = client;
this.properties = consulDiscoveryProperties;
}
#Override
public LBClient create(String clientName) {
IClientConfig config =
ClientFactory.getNamedConfig(clientName, DisableAutoRetriesByDefaultClientConfig.class);
ConsulServerList consulServerList = new ConsulServerList(this.client, properties);
consulServerList.initWithNiwsConfig(config);
ZoneAwareLoadBalancer<ConsulServer> lb = new ZoneAwareLoadBalancer<>(config);
lb.setServersList(consulServerList.getInitialListOfServers());
lb.setServerListImpl(consulServerList);
return LBClient.create(lb, config);
}
}
and then use it in feign:
public class Demo {
public static void main(String[] args) {
ConsulLBFactory consulLBFactory = new ConsulLBFactory(
new ConsulClient(),
new ConsulDiscoveryProperties(new InetUtils(new InetUtilsProperties()))
);
RibbonClient ribbonClient = RibbonClient.builder()
.lbClientFactory(consulLBFactory)
.build();
GitHub github = Feign.builder()
.client(ribbonClient)
.decoder(new GsonDecoder())
.target(GitHub.class, "https://api.github.com");
List<Contributor> contributors = github.contributors("OpenFeign", "feign");
for (Contributor contributor : contributors) {
System.out.println(contributor.login + " (" + contributor.contributions + ")");
}
}
interface GitHub {
#RequestLine("GET /repos/{owner}/{repo}/contributors")
List<Contributor> contributors(#Param("owner") String owner, #Param("repo") String repo);
}
public static class Contributor {
String login;
int contributions;
}
}
you can find this demo code here, add api.github.com to your local consul before running this demo.

Related

Testing with okhttp and new Spring 6 http interfaces

What's the best way to write integration tests that uses the new Spring 6 http interfaces with a mock server? Example:
#Bean
Blah configService() {
var client = WebClient.builder().baseUrl(baseUrl)
.defaultStatusHandler(HttpStatusCode::is4xxClientError, resp -> Mono.empty())
.build();
var proxyFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(client)).build();
return proxyFactory.createClient(Blah.class);
}
interface BlahService {
#GetExchange("/ok")
ResponseEntity<Blah> getBlah();
}
basically trying to discover a reasonable way to use the mock server from okhttp (https://github.com/spring-projects/spring-framework/blob/main/spring-webflux/src/test/java/org/springframework/web/reactive/function/client/WebClientIntegrationTests.java)
with the server.url as a property I can inject the value and use it in test env as base url in my WebClient config.
Thanks.
I can't find anything specific on the web on this topic yet.
You will need to use #DynamicPropertySource to inject that property in your test class like so
#DynamicPropertySource
static void properties(DynamicPropertyRegistry registry) {
registry.add("server.url", () -> "http://localhost:" + mockWebServer.getPort());
}

How to add custom Springdoc resources to swagger-ui page

I'm not good at swagger and related libraries, so sorry if the title is confusing.
I have several services which provide their api as swagger documentation in json format and via swagger-ui. Next, I have a springboot service which is a proxy of the previously mentioned services and it provides combined api of all services (one can select exact service in a dropdown). It was implemented like this:
public class PropertyResourceProvider implements SwaggerResourcesProvider {
#Autowired
private SwaggerConfigProperties swaggerConfigProperties; // Just a mapping of config
#Override
public List<SwaggerResource> get() {
// Doc for local (proxy) service
val local = new SwaggerResource();
local.setName(title);
local.setUrl(swaggerLocal);
local.setSwaggerVersion(version);
// Add other services - specified in config
List<SwaggerResource> services = swaggerConfigProperties.getServices().stream().map(service -> {
String contextPath = service.get("contextPath");
String externalPath = SWAGGER_DOC_BASE_PATH + "/" + contextPath;
val resource = new SwaggerResource();
resource.setName(service.get("name"));
resource.setUrl(externalPath);
resource.setSwaggerVersion(service.get("version"));
return resource;
}).collect(Collectors.toList());
services.add(local);
return services; // Return combined API
}
Now, we're moving from springfox to springdoc and I can't use SwaggerResource and SwaggerResourcesProvider. How can I implements the same using springdoc?

Spring Cloud Sleuth: Export data from remote system to local Zipkin

In my Spring Boot application I use spring-cloud-starter-sleuth (Version Hoxton.SR10) for tracing. It is (still) a monolithic application so I widely use the #NewSpan annotation for creating new spans.
In my development environment I also use spring-cloud-starter-zipkin, which works great.
But on the servers of our customers I don't have access to any Zipkin server nor am allowed to install one. Is there any possibility to save the data Spring is sending to Zipkin and import that to my local Zipkin server?
Solution thanks to Marcin's inspiration:
#Configuration
#ConditionalOnProperty(name = "custom.property", havingValue = "true")
public class SleuthConfiguration {
#Bean("zipkinSender")
Sender restTemplateSender() {
return new Sender() {
public Encoding encoding() { return Encoding.JSON; }
public int messageMaxBytes() { return Integer.MAX_VALUE; }
public int messageSizeInBytes(List<byte[]> list) { return Integer.MAX_VALUE; }
#Override
public Call<Void> sendSpans(List<byte[]> list) {
String result = convertByteArrayToList(list);
saveToFile(result);
return new Call.Base<Void>() {...};
}
};
}
}
Implement convertByteArrayToList and saveToFile your own, because my solution depends on custom libraries.
You could create your own SpanHandler bean that takes the FinishedSpan, converts into JSON and stores it somewhere on your drive. Then you could just iterate over jsons and upload them to the Zipkin server

How to consume basic-authentication protected Restful web service via REACTIVE feign client

#ReactiveFeignClient(name = "service.b",configuration = CustomConfiguration.class)
public interface FeingConfiguration {
#PostMapping("/api/students/special")
public Flux<Student> getAllStudents(#RequestBody Flux<SubjectStudent> lista);
}
Help, how can I add a basic authentication to my header that I have in the service: service.b.
I have the CustomConfiguration.class class but it doesn't allow me, I have 401 authorization failed
#Configuration
public class CustomConfiguration {
#Bean
public BasicAuthRequestInterceptor basic() {
return new BasicAuthRequestInterceptor("user","user") ;
}
Looks like you are trying to use feign-reactive (https://github.com/Playtika/feign-reactive) to implement your REST clients. I am also using it for one of my projects and it looks like this library does not have an out-of-the-box way to specify basic auth credentials. At least no way to do this declaratively. So I didn't find a better way to do this than to abandon the auto-configuration via #ReactiveFeignClient and start configuring reactive feign clients manually. This way you can manually add "Authorization" header to all outgoing requests. So, provided this client definition:
public interface FeingClient {
#PostMapping("/api/students/special")
public Flux<Student> getAllStudents(#RequestBody Flux<SubjectStudent> lista);
}
Add the following configuration class to your Spring context, replacing username, password and service-url with your own data:
#Configuration
public class FeignClientConfiguration {
#Bean
FeignClient feignClient() {
WebReactiveFeign
.<FeignClient>builder()
.addRequestInterceptor(request -> {
request.headers().put(
"Authorization",
Collections.singletonList(
"Basic " + Base64.getEncoder().encodeToString(
"username:password".getBytes(StandardCharsets.ISO_8859_1))));
return request;
})
.target(FeignClient.class, "service-url");
}
}
Note, that this API for manual configurftion of reactive feign clients can differ between different versions of the reactive-feign library. Also note that this approach has a major drawback - if you start creating beans for your feign clients manually you lose the main advantage of Feign - ability to write REST-clients declaratively with just a few lines of code. E.g. if you want to use the above client with some sort of client-side load-balancing mechanism, like Ribbon/Eureka or Ribbon/Kubernetes, you will also need to configure that manually.
You can use a direct interceptor:
#Configuration
class FeignClientConfiguration {
#Bean
fun reactiveHttpRequestInterceptor(): ReactiveHttpRequestInterceptor {
return ReactiveHttpRequestInterceptor { request: ReactiveHttpRequest ->
request.headers()["Authorization"] = //insert data from SecurityContextHolder;
Mono.just(request)
}
}
}

how to change the #FeignClient name in runtime

I use Spring Cloud Netflix to build my micro service .
#FeignClient(name = "ms-cloud",configuration = MsCloudClientConfig.class)
public interface TestClient {
/**
* #return
*/
#RequestMapping(value = "/test", method = RequestMethod.GET)
String test();
}
I want to change the name to ms-cloud-pre when some special user.
Anyone can give some advice?
According to the documentation feign supports placeholders in the name and url fields.
#FeignClient(name = "${store.name}")
public interface StoreClient {
//..
}
So you could set store.name=storeProd at runtime using normal spring boot configuration mechanisms.
To create a spring-cloud Feign client at runtime in situations where you don't know the service-id until the point of call:
import org.springframework.cloud.openfeign.FeignClientBuilder;
#Component
public class InfoFeignClient {
interface InfoCallSpec {
#RequestMapping(value = "/actuator/info", method = GET)
String info();
}
FeignClientBuilder feignClientBuilder;
public InfoFeignClient(#Autowired ApplicationContext appContext) {
this.feignClientBuilder = new FeignClientBuilder(appContext);
}
public String getInfo(String serviceId) {
InfoCallSpec spec =
this.feignClientBuilder.forType(InfoCallSpec.class, serviceId).build();
return spec.info();
}
}
That actually is possible. In Spring Cloud Zookeeper we're doing a similar thing since the name of the service in the Feign client is not the one that is there in the in Zookeeper. It can be an alias presented in the yaml file. Here you have the code example https://github.com/spring-cloud/spring-cloud-zookeeper/blob/master/spring-cloud-zookeeper-discovery/src/main/java/org/springframework/cloud/zookeeper/discovery/dependency/DependencyRibbonAutoConfiguration.java#L54 and here you have the description of the dependencies feature - https://github.com/spring-cloud/spring-cloud-zookeeper/blob/master/docs/src/main/asciidoc/spring-cloud-zookeeper.adoc#using-the-zookeeper-dependencies

Resources