Git Product home page Git Product logo

feign-reactive's Introduction

CircleCI codecov Codacy Badge Maven Central

feign-reactive

Use Feign with Spring WebFlux

Overview

Implementation of Feign on Spring WebClient. Brings you the best of two worlds together : concise syntax of Feign to write client side API on fast, asynchronous and non-blocking HTTP client of Spring WebClient.

Modules

feign-reactor-core : base classes and interfaces that should allow to implement alternative reactor Feign

feign-reactor-webclient : Spring WebClient based implementation of reactor Feign

feign-reactor-cloud : Spring Cloud implementation of reactor Feign (Ribbon/Hystrix)

feign-reactor-java11 : Java 11 HttpClient based implementation of reactor Feign (!! Winner of benchmarks !!)

feign-reactor-rx2 : Rx2 compatible implementation of reactor Feign (depends on feign-reactor-webclient)

feign-reactor-jetty : experimental Reactive Jetty client based implementation of reactor Feign (doesn't depend on feign-reactor-webclient). In future will allow to write pure Rx2 version.

  • have greater reactivity level then Spring WebClient. By default don't collect body to list instead starts sending request body as stream.
  • starts receiving reactive response before all reactive request body has been sent
  • process Flux<String> correctly in request and response body

feign-reactor-spring-cloud-starter : Single dependency to have reactive feign client operabable in your spring cloud application. Uses webclient as default client implementation.

feign-reactor-bom : Maven BOM module which simplifies dependency management for all reactive feign client modules.

Usage

Write Feign API as usual, but every method of interface

  • may accept org.reactivestreams.Publisher as body
  • must return reactor.core.publisher.Mono or reactor.core.publisher.Flux.
@Headers({ "Accept: application/json" })
public interface IcecreamServiceApi {

  @RequestLine("GET /icecream/flavors")
  Flux<Flavor> getAvailableFlavors();

  @RequestLine("GET /icecream/mixins")
  Flux<Mixin> getAvailableMixins();

  @RequestLine("POST /icecream/orders")
  @Headers("Content-Type: application/json")
  Mono<Bill> makeOrder(IceCreamOrder order);

  @RequestLine("GET /icecream/orders/{orderId}")
  Mono<IceCreamOrder> findOrder(@Param("orderId") int orderId);

  @RequestLine("POST /icecream/bills/pay")
  @Headers("Content-Type: application/json")
  Mono<Void> payBill(Publisher<Bill> bill);
}

Build the client :

/* Create instance of your API */
IcecreamServiceApi client = 
             WebReactiveFeign  //WebClient based reactive feign  
             //JettyReactiveFeign //Jetty http client based
             //Java11ReactiveFeign //Java 11 http client based
            .<IcecreamServiceApi>builder()
            .target(IcecreamServiceApi.class, "http://www.icecreame.com")

/* Execute nonblocking requests */
Flux<Flavor> flavors = icecreamApi.getAvailableFlavors();
Flux<Mixin> mixins = icecreamApi.getAvailableMixins();

or cloud aware client :

 IcecreamServiceApi client = CloudReactiveFeign.<IcecreamServiceApi>builder(WebReactiveFeign.builder())
            .setLoadBalancerCommandFactory(s -> LoadBalancerCommand.builder()
                    .withLoadBalancer(AbstractLoadBalancer.class.cast(getNamedLoadBalancer(serviceName)))
                    .withRetryHandler(new DefaultLoadBalancerRetryHandler(1, 1, true))
                    .build())
            .fallback(() -> Mono.just(new IcecreamServiceApi() {
                @Override
                public Mono<String> get() {
                    return Mono.just("fallback");
                }
            }))
            .target(IcecreamServiceApi.class,  "http://" + serviceName);

/* Execute nonblocking requests */
Flux<Flavor> flavors = icecreamApi.getAvailableFlavors();
Flux<Mixin> mixins = icecreamApi.getAvailableMixins();

Rx2 Usage

Write Feign API as usual, but every method of interface

  • may accept Flowable, Observable, Single or Maybe as body
  • must return Flowable, Observable, Single or Maybe.
@Headers({"Accept: application/json"})
public interface IcecreamServiceApi {

  @RequestLine("GET /icecream/flavors")
  Flowable<Flavor> getAvailableFlavors();

  @RequestLine("GET /icecream/mixins")
  Observable<Mixin> getAvailableMixins();

  @RequestLine("POST /icecream/orders")
  @Headers("Content-Type: application/json")
  Single<Bill> makeOrder(IceCreamOrder order);

  @RequestLine("GET /icecream/orders/{orderId}")
  Maybe<IceCreamOrder> findOrder(@Param("orderId") int orderId);

  @RequestLine("POST /icecream/bills/pay")
  @Headers("Content-Type: application/json")
  Single<Long> payBill(Bill bill);

Build the client :

/* Create instance of your API */
IcecreamServiceApi client = Rx2ReactiveFeign
    .builder()
    .target(IcecreamServiceApi.class, "http://www.icecreame.com")

/* Execute nonblocking requests */
Flowable<Flavor> flavors = icecreamApi.getAvailableFlavors();
Observable<Mixin> mixins = icecreamApi.getAvailableMixins();

Header to request

There are 2 options:

ReactiveHttpRequestInterceptor

ReactiveFeignBuilder
    .addRequestInterceptor(ReactiveHttpRequestInterceptors.addHeader("Cache-Control", "no-cache"))
    .addRequestInterceptor(request -> Mono
            .subscriberContext()
            .map(ctx -> ctx
                    .<String>getOrEmpty("authToken")
                    .map(authToken -> {
                      MultiValueMapUtils.addOrdered(request.headers(), "Authorization", authToken);
                      return request;
                    })
                    .orElse(request)));

@RequestHeader parameter

You can use @RequestHeader annotation for specific parameter to pass one header or map of headers @RequestHeader example

Spring Auto-Configuration

You can enable auto-configuration of reactive Feign clients as Spring beans just by adding feign-reactor-spring-configuration module to classpath. Spring Auto-Configuration module Sample cloud auto-configuration project with Eureka/WebFlux/ReaciveFeign

License

Library distributed under Apache License Version 2.0.

feign-reactive's People

Contributors

aivinog1 avatar alekseibevzenko avatar amakohon-playtika avatar davidmelia avatar dependabot-preview[bot] avatar dependabot[bot] avatar github-actions[bot] avatar gleb-kom avatar gromspys avatar ijusti avatar jenkins-playtika avatar jorgediego16 avatar kptfh avatar kuliginstepan avatar maccamlcq avatar mukeshj13 avatar nawrok avatar ntkoopman avatar playtikagithub avatar tdanylchuk avatar vstorona-origin avatar wllianwd avatar yevtsy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

feign-reactive's Issues

Providing a direct binding to Reactor-Netty (HttpClient)

I wonder if we could have a direct binding to reactor-netty as we progress towards maturity, more protocol support (h2, unix socket) and performance optimizations. Also I suppose it would provide less indirection than WebClient which is mostly useful when exposed directly.

How to configure webclient keepalive and connection pool ?

modify CustomizableWebClientBuilder but no work.

Function<HttpClient, HttpClient> mapper = client ->
client.tcpConfiguration(c ->
c.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, webOptions.getConnectTimeoutMillis().intValue())
.option(ChannelOption.SO_KEEPALIVE,true)
.option(ChannelOption.TCP_NODELAY, true)
.doOnConnected(conn -> {
conn.addHandlerLast(new ReadTimeoutHandler(
webOptions.getReadTimeoutMillis(), TimeUnit.MILLISECONDS));
conn.addHandlerLast(new WriteTimeoutHandler(
webOptions.getWriteTimeoutMillis(), TimeUnit.MILLISECONDS));
log.info("doOnConnected:" + conn.hashCode());
}).doOnDisconnected(connection -> {
// [Q1 ]after response closed
log.info("doOnDisconnected:" + connection.hashCode());
})).keepAlive(true) // [Q2]http keep alive no work
.chunkedTransfer(false);
return new ReactorClientHttpConnector(reactorResourceFactory, mapper);

public ReactorResourceFactory resourceFactory() {
ReactorResourceFactory factory = new ReactorResourceFactory();
factory.setUseGlobalResources(false);
factory.setConnectionProvider(ConnectionProvider.fixed("cpool", 10));
factory.setLoopResources(LoopResources.create("cpooll", 4, true));
return factory;
}

query parameters are not encoded

Hi, I have a simple reactive feign client with a method:
@GetMapping("/")
Mono<Void> test(@RequestParam String query);
If I use as query something like Kuligin Stepan I get an exception:

java.lang.IllegalArgumentException: Illegal character in query at index 26: http://test/?query=Kuligin Stepan
	at java.net.URI.create(URI.java:852)
	at reactivefeign.methodhandler.PublisherClientMethodHandler.buildUri(PublisherClientMethodHandler.java:123)
	at reactivefeign.methodhandler.PublisherClientMethodHandler.buildRequest(PublisherClientMethodHandler.java:105)
	at reactivefeign.methodhandler.PublisherClientMethodHandler.invoke(PublisherClientMethodHandler.java:96)
	at reactivefeign.methodhandler.PublisherClientMethodHandler.invoke(PublisherClientMethodHandler.java:47)
	at reactivefeign.methodhandler.MonoMethodHandler.invoke(MonoMethodHandler.java:18)
	at reactivefeign.methodhandler.MonoMethodHandler.invoke(MonoMethodHandler.java:6)

Current Webclient implementation is unsupported by versions prior to Spring 5.2

Today, while adding feign-reactive to one of our main microservices, I faced an issue where WebReactiveHttpResponse was calling a method decode(DataBuffer,ResolvableType,MimeType,Map) from ByteArrayDecoder that did not exist.

Digging up a little bit, I discovered that this method is called decodeDataBuffer(DataBuffer,ResolvableType,MimeType,Map) in previous Spring 5 versions, and that not only it is deprecated, it was not public on ByteArrayDecoder anymore.

As it is a very simple method

    byte[] result = new byte[dataBuffer.readableByteCount()];
    dataBuffer.read(result);
    DataBufferUtils.release(dataBuffer);
    return result;

I thought this can be inlined to enchance compatibility.

Query parameters encoding

Hi, thanks for fix #63, but I am faced with an issue when I have a URL like http://host/search?filter=workers=in=("123") it should be encoded to http://host/search?filter=workers%3Din%3D(%22123%22) but now it's encoded to http://host/search?filter=workers=in=(%22123%22)

To fix this you can use UriUtils.encode() instead of UriUtils.queryEncode()

IllegalStateException on Duplicate Method Key Parameter

Hey, i found an issue when i create feign client method and add request header that has equals key with request parameter.

@PostMapping("/delay") Mono<OutboundResponse> delay(@RequestHeader("username") String headerUsername, @RequestParam String username) throws Exception;

And i get this error on runtime,

java.lang.IllegalStateException: Duplicate key Hello World at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133) Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): |_ checkpoint ⇢ Handler org.faza.example.spring.open.feign.OutboundController#test(Integer, String, String, String, String, String) [DispatcherHandler] Stack trace: at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133) at java.util.HashMap.merge(HashMap.java:1254) at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320) at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169) at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418) at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:270) at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175) at java.util.Iterator.forEachRemaining(Iterator.java:116) at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499) at reactivefeign.methodhandler.PublisherClientMethodHandler.buildSubstitutions(PublisherClientMethodHandler.java:130) at reactivefeign.methodhandler.PublisherClientMethodHandler.buildRequest(PublisherClientMethodHandler.java:102) at reactivefeign.methodhandler.PublisherClientMethodHandler.invoke(PublisherClientMethodHandler.java:97) at reactivefeign.methodhandler.PublisherClientMethodHandler.invoke(PublisherClientMethodHandler.java:49) at reactivefeign.methodhandler.MonoMethodHandler.invoke(MonoMethodHandler.java:18) at reactivefeign.methodhandler.MonoMethodHandler.invoke(MonoMethodHandler.java:6) at reactivefeign.ReactiveInvocationHandler.invoke(ReactiveInvocationHandler.java:64) at com.sun.proxy.$Proxy98.delay(Unknown Source) at org.faza.example.spring.open.feign.OutboundService.callDelay(OutboundService.java:14) at org.faza.example.spring.open.feign.OutboundController.test(OutboundController.java:42) at org.faza.example.spring.open.feign.OutboundController$$FastClassBySpringCGLIB$$fe86d3b2.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:769) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:747) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:689) at org.faza.example.spring.open.feign.OutboundController$$EnhancerBySpringCGLIB$$5c35eb60.test(<generated>) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.web.reactive.result.method.InvocableHandlerMethod.lambda$invoke$0(InvocableHandlerMethod.java:147) at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:118) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1637) at reactor.core.publisher.MonoZip$ZipCoordinator.signal(MonoZip.java:247) at reactor.core.publisher.MonoZip$ZipInner.onNext(MonoZip.java:329) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:173) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:92) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1637) at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext(MonoFlatMap.java:241) at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2199) at reactor.core.publisher.MonoFlatMap$FlatMapInner.onSubscribe(MonoFlatMap.java:230) at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55) at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2199) at reactor.core.publisher.MonoFlatMap$FlatMapMain.onSubscribe(MonoFlatMap.java:103) at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) at reactor.core.publisher.Mono.subscribe(Mono.java:4105) at reactor.core.publisher.MonoZip.subscribe(MonoZip.java:128) at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55) at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55) at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:153) at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55) at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:274) at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:851) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:121) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:173) at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2199) at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:132) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.request(ScopePassingSpanSubscriber.java:79) at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:162) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.request(ScopePassingSpanSubscriber.java:79) at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2007) at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:1881) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onSubscribe(ScopePassingSpanSubscriber.java:71) at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:90) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onSubscribe(ScopePassingSpanSubscriber.java:71) at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onSubscribe(MonoPeekTerminal.java:145) at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) at reactor.core.publisher.Mono.subscribe(Mono.java:4105) at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:441) at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onNext(FluxConcatMap.java:243) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:96) at reactor.core.publisher.FluxIterable$IterableSubscription.slowPath(FluxIterable.java:243) at reactor.core.publisher.FluxIterable$IterableSubscription.request(FluxIterable.java:201) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.request(ScopePassingSpanSubscriber.java:79) at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:228) at org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onSubscribe(ScopePassingSpanSubscriber.java:71) at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:139) at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:63) at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55) at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55) at org.springframework.cloud.sleuth.instrument.web.TraceWebFilter$MonoWebFilterTrace.subscribe(TraceWebFilter.java:180) at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55) at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52) at reactor.core.publisher.Mono.subscribe(Mono.java:4105) at reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:172) at reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56) at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:55) at reactor.netty.http.server.HttpServerHandle.onStateChange(HttpServerHandle.java:64) at reactor.netty.tcp.TcpServerBind$ChildObserver.onStateChange(TcpServerBind.java:228) at reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:465) at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:90) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355) at reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:167) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355) at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:321) at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:295) at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355) at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363) at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163) at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714) at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650) at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576) at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989) at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) at java.lang.Thread.run(Thread.java:748)

Please help to check and solve this, thanks

Greenwich.SR3 problem

I'm having issues with spring cloud Greenwich.SR3. Everything works just fine with Greenwich.SR2 but as soon as I switch I get:

java.lang.ClassCastException: class org.springframework.core.io.buffer.NettyDataBuffer cannot be cast to class com.risi.learningreactiveclient.webfluxlearningclient.domain.ItemClient (org.springframework.core.io.buffer.NettyDataBuffer and com.risi.learningreactiveclient.webfluxlearningclient.domain.ItemClient are in unnamed module of loader 'app')

Not sure what is the problem but I hope it helps.

Ribbon Implementation Does not Support "https" when using Eureka / Service Discovery

I had added reactive-feign to one of our most important micro-services in here, and we are using Eureka for Service Discovery.
All of our services register itself as only secure ports being enabled... and feign-reactive picks the 443 ports right away. However, it is still making calls using http scheme when using Ribbon (Cloud module).

We are unable to bump this microservice to Spring Boot 2.2, so we are still using Spring Boot 2.1 and cannot use Cloud 2 implementation that takes schema into account.

The current implementation for Ribbon in the cloud module is

  protected ReactiveHttpRequest loadBalanceRequest(ReactiveHttpRequest request, Server server) {
    URI lbUrl =
        UriComponentsBuilder.fromUri(request.uri())
            .host(server.getHost())
            .port(server.getPort())
            .build(true)
            .toUri();
    return new ReactiveHttpRequest(request, lbUrl);

So it uses the schema received from the ReactiveFeignClientFactoryBean that is hardcoded to use http only.

I can provide a PR that will take Eureka data into consideration and use it as the schema for this code.

Too many metrics generated when using Path Variables

Using requests with PathVariables in reactive feign client
(ex.: @GetMapping("/{cropId}/summary"))
leads to generating metrics for each request separately
(ex: /2339501/summary.snapshot.50thPercentile, /2339502/summary.snapshot.50thPercentile, etc.)
Then it finishes with this Warning:
WARN org.springframework.boot.actuate.autoconfigure.metrics.OnlyOnceLoggingDenyMeterFilter - Reached the maximum number of URI tags for 'http.client.requests'. Are you using 'uriVariables'?

Same requests didn't generate separate metrics in synchronous feign client.

Spring feign-reactor-cloud2 timeout

Hi everyone,

We are using the Spring cloud module through feign-reactor-cloud2 + spring-cloud-starter-circuitbreaker-reactor-resilience4j (Ribbon and Hystrix are disabled) and everything works fine except for the timeout configurations. Could someone provide me with an example with the proper way to override the default 1s read timeout per Feign client?

Thanks

Bean autowire issue while using FallbackFactory

I'm getting an error (Could not autowire. No beans of ServiceFeignClient type found.) while including FallbackFactory to the parameters as:

@ReactiveFeignClient(name = "service_statistics", fallbackFactory = StatisticFallbackFactory.class)
public interface ServiceFeignClient {
    ...
}

I realized that in OpenFeign FallbackFactory comes from feign.hystrix.FallbackFactory package, whereas in Reactive one, it comes from reactivefeign.FallbackFactory. Maybe there is some Hystrix support problem or bean conflict that causes that error. But it it Ok when using only Fallback class.

Can't customize the ObjectMapper used by feign-reactor-webclient

I want to add a custom module to the ObjectMapper used by feign-reactor-webclient.

Things that I have tried and none of then worked:

  • Create a @Bean of the Module
  • Create a @Bean of type WebClientCustomizer

The alternatives above works when I inject the WebClient directly, but not when I use a @ReactiveFeignClient interface.

I think it may be happening because, according to the javadoc in ReactiveFeignContext, "It creates a Spring ApplicationContext per client name", wich, apparently overrides the application context where I am injecting the beans that I mentioned above.

There is any way I can customize the ObjectMapper used by feign-reactor-webclient? Maybe an undocumented one that I could not find? Or this is the intended behaviour?

I'm using feign-reactor-webclient and feign-reactor-spring-configuration with Spring Boot 2.1.3.RELEASE.

What versions do SpringBoot, Springcloud and feign-reactive support?

In my project,2.2.1 Springboot , Hoxton.RC1 SpringCloud, 1.0.41 reactivefeign,But it doesn't start。
error:
Caused by: java.lang.IllegalStateException: Failed to introspect annotated methods on class reactivefeign.spring.config.ReactiveFeignClientsConfiguration
I tried all 2.2.x versions, none of which started. Why?
I tried 2.1.2 springboot, 1.0.22 reactivefeign, Greenwich SpringCloud is available.

Reactive ReactiveHttpRequestInterceptor

Hi,
I would like to have reactive based ReactiveHttpRequestInterceptor. My goal is to add Authorization header to all requests using request itnerceptor. I was thinking to do something like. To get Authorization data from Mono Context:

WebReactiveFeign
                .<Client>builder(webClientBuilder)
                .decode404()
                .addRequestInterceptor(request -> Mono
                            .subscriberContext()
                            .map(ctx -> ctx
                                    .<String>getOrEmpty("authToken")
                                    .map(authToken -> {
                                        Map<String, List<String>> headers = new LinkedHashMap<>(request.headers());
                                        headers.put("Authorization", List.of(authToken));
                                        return new ReactiveHttpRequest(request.method(), request.uri(), headers, request.body());
                                    })
                                    .orElse(request));)
                .target(Client.class, "http://localhost:8080")

Or is there any other recommended approach?

Thanks

Running into connection issue; Connection prematurely closed BEFORE response

I have a UI I've implemented w/ Vaadin Flow that employs reactive-feign libs to interact with a backend. I'm using Spring Boot 2.1.4.RELEASE and Spring Cloud Greenwich.SR1.

Linking to source and physical deployments, here:

When I visit the UI I'd expect to see one metric rendered, the total number of applications as taken from /snapshot/summary endpoint. But instead we get a fallback to a blank screen.

This is what I see in the logs...

Fri Apr 26 2019
18:05:12.018
[APP/0]
1	at reactor.core.publisher.MonoDelayElement$DelayElementSubscriber.lambda$onNext$0(MonoDelayElement.java:123)
Fri Apr 26 2019
18:05:12.018
[APP/0]
1	at io.pivotal.cfapp.ui.CfHooverDashboard.lambda$init$0(CfHooverDashboard.java:44)
Fri Apr 26 2019
18:05:12.018
[APP/0]
1�[30m2019-04-27T01:05:12,017�[m �[1;31mERROR �[m[�[1;34mparallel-1�[m] �[1;33mr.u.Loggers$Slf4JLogger�[m: Scheduler worker in group main failed with an uncaught exception
Fri Apr 26 2019
18:05:12.018
[APP/0]
1	at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:27)
Fri Apr 26 2019
18:05:12.018
[APP/0]
1	at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:50)
Fri Apr 26 2019
18:05:12.018
[APP/0]
1java.lang.NullPointerException
Fri Apr 26 2019
18:05:12.018
[APP/0]
1	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
Fri Apr 26 2019
18:05:12.018
[APP/0]
1	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
Fri Apr 26 2019
18:05:12.018
[APP/0]
1	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
Fri Apr 26 2019
18:05:12.018
[APP/0]
1	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505)
Fri Apr 26 2019
18:05:12.018
[APP/0]
1	at reactor.core.publisher.LambdaMonoSubscriber.onNext(LambdaMonoSubscriber.java:137)
Fri Apr 26 2019
18:05:12.018
[APP/0]
1	at java.base/java.lang.Thread.run(Unknown Source)
Fri Apr 26 2019
18:05:12.018
[APP/0]
1	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
Fri Apr 26 2019
18:05:12.016
[APP/0]
1	at io.pivotal.cfapp.ui.CfHooverDashboard.lambda$init$0(CfHooverDashboard.java:44)
Fri Apr 26 2019
18:05:12.016
[APP/0]
1�[30m2019-04-27T01:05:12,015�[m �[1;31mERROR �[m[�[1;34mparallel-1�[m] �[1;33mr.u.Loggers$Slf4JLogger�[m: Operator called default onErrorDropped
Fri Apr 26 2019
18:05:12.016
[APP/0]
1java.lang.NullPointerException
Fri Apr 26 2019
18:05:12.016
[APP/0]
1	at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1505)
Fri Apr 26 2019
18:05:12.016
[APP/0]
1	at reactor.core.publisher.LambdaMonoSubscriber.onNext(LambdaMonoSubscriber.java:137)
Fri Apr 26 2019
18:05:12.016
[APP/0]
1	at java.base/java.lang.Thread.run(Unknown Source)
Fri Apr 26 2019
18:05:12.016
[APP/0]
1	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
Fri Apr 26 2019
18:05:12.016
[APP/0]
1	at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Unknown Source)
Fri Apr 26 2019
18:05:12.016
[APP/0]
1	at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
Fri Apr 26 2019
18:05:12.016
[APP/0]
1	at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:50)
Fri Apr 26 2019
18:05:12.016
[APP/0]
1	at reactor.core.publisher.MonoDelayElement$DelayElementSubscriber.lambda$onNext$0(MonoDelayElement.java:123)
Fri Apr 26 2019
18:05:12.016
[APP/0]
1	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
Fri Apr 26 2019
18:05:12.016
[APP/0]
1	at reactor.core.scheduler.SchedulerTask.call(SchedulerTask.java:27)
Fri Apr 26 2019
18:05:11.927
[APP/0]
1�[30m2019-04-27T01:05:11,927�[m �[32mINFO �[m[�[1;34mPollingServerListUpdater-0�[m] �[1;33mc.n.c.ChainedDynamicProperty$ChainLink�[m: Flipping property: cf-hoover.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
Fri Apr 26 2019
18:05:11.498
[APP/0]
1�[30m2019-04-27T01:05:11,497�[m �[33mWARN �[m[�[1;34mreactor-http-epoll-4�[m] �[1;33mi.p.c.c.HooverClientFallbackFactory$1�[m: Could not obtain snapshot summary. reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response
Fri Apr 26 2019
18:05:11.486
[APP/0]
1reactor.netty.http.client.PrematureCloseException: Connection prematurely closed BEFORE response
Fri Apr 26 2019
18:05:11.486
[APP/0]
1�[30m2019-04-27T01:05:11,485�[m �[33mWARN �[m[�[1;34mreactor-http-epoll-4�[m] �[1;33mr.u.Loggers$Slf4JLogger�[m: [id: 0x0bcd082a, L:/10.255.38.181:50486 ! R:cf-hoover-hilarious-alligator.apps.pcfone.io/10.193.36.220:443] The connection observed an error
Fri Apr 26 2019
18:05:11.018
[APP/0]
1�[30m2019-04-27T01:05:11,018�[m �[36mDEBUG �[m[�[1;34mhttp-nio-8080-exec-3�[m] �[1;33mr.c.l.DefaultReactiveLogger�[m: [HooverClient#getSummary]--->GET http://cf-hoover-hilarious-alligator.apps.pcfone.io:443/snapshot/summary HTTP/1.1
Fri Apr 26 2019
18:05:10.956
[APP/0]
1�[30m2019-04-27T01:05:10,956�[m �[32mINFO �[m[�[1;34mhttp-nio-8080-exec-3�[m] �[1;33mc.n.l.DynamicServerListLoadBalancer�[m: DynamicServerListLoadBalancer for client cf-hoover initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=cf-hoover,current list of Servers=[cf-hoover-hilarious-alligator.apps.pcfone.io:793032db-2a8b-4e65-5f8c-4f7d],Load balancer stats=Zone stats: {apps.pcfone.io=[Zone:apps.pcfone.io;	Instance count:1;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
Fri Apr 26 2019
18:05:10.956
[APP/0]
1},Server stats: [[Server:cf-hoover-hilarious-alligator.apps.pcfone.io:793032db-2a8b-4e65-5f8c-4f7d; Zone:apps.pcfone.io;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 00:00:00 UTC 1970;	First connection made: Thu Jan 01 00:00:00 UTC 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0; min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
Fri Apr 26 2019
18:05:10.956
[APP/0]
1]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@6304e536

This behavior is surprisingly similar to

Just wondering if you observe anything wrong with my implementation? And if I should perhaps cross-post with the appropriate Spring/Reactor team? Or is this perhaps a defect in the reactive-feign impl?

Can you improve the document with README?

I use to feign-reactive ‘1.0.15‘ this version,when I using the readme example code and it not work.

/* Create instance of your API */
IcecreamServiceApi client = ReactiveFeign
    .builder()
    .target(IcecreamServiceApi.class, "http://www.icecreame.com")
...

ReactiveFeign API have modified, so that compile error.
Please, can you change the new version READEME content or provide another WIKI Website?
Hope to reply me,
Thanks...

wrong URL with ribbon

@kptfh thanks for fix #63. I found what RibbonPublisherClient break correct URL encoding if URL contains Cyrillic symbols. Load balanced URL created with method

    protected ReactiveHttpRequest loadBalanceRequest(ReactiveHttpRequest request, Server server) {
        URI uri = request.uri();
        try {
            URI lbUrl = new URI(uri.getScheme(), uri.getUserInfo(), server.getHost(), server.getPort(),
                    uri.getPath(), uri.getQuery(), uri.getFragment());
            return new ReactiveHttpRequest(request.method(), lbUrl, request.headers(), request.body());
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException(e);
        }
    }

I wrote test for this issue

    @Test
    public void loadBalancedRequestUriTest() throws URISyntaxException {
        UriComponentsBuilder builder = UriComponentsBuilder.newInstance()
            .scheme("http")
            .path("/test/api")
            .query("query=Степан Кулигин").encode(StandardCharsets.UTF_8);
        URI uri = builder.build().toUri();

        URI expectedLbUri = builder.host("localhost").port(80).build().toUri();

        URI lbUrl = new URI(uri.getScheme(), uri.getUserInfo(), "localhost", 80,
            uri.getPath(), uri.getQuery(), uri.getFragment());

        Assert.assertEquals(expectedLbUri, lbUrl);
    }

Expected :http://localhost:80/test/api?query=%D0%A1%D1%82%D0%B5%D0%BF%D0%B0%D0%BD%20%D0%9A%D1%83%D0%BB%D0%B8%D0%B3%D0%B8%D0%BD
Actual :http://localhost:80/test/api?query=Степан%20Кулигин

WebReactiveFeign always overrides the client connector provided

It seems there is no way to customise the client connector when creating a client using WebReactiveFeign as the Builder always creates a new one and set it to the webClient provided.

The code in question:

    public static class Builder<T> extends ReactiveFeign.Builder<T> {

        protected WebClient webClient;

        protected Builder() {
            this(WebClient.create());
        }

        protected Builder(WebClient webClient) {
            setWebClient(webClient);
            options(new WebReactiveOptions.Builder()
                    .setReadTimeoutMillis(DEFAULT_READ_TIMEOUT_MILLIS)
                    .setWriteTimeoutMillis(DEFAULT_WRITE_TIMEOUT_MILLIS)
                    .setConnectTimeoutMillis(DEFAULT_CONNECT_TIMEOUT_MILLIS)
                    .build());
        }

        @Override
        public Builder<T> options(ReactiveOptions options) {
            if (!options.isEmpty()) {
                WebReactiveOptions webOptions = (WebReactiveOptions)options;
                TcpClient tcpClient = TcpClient.create();
                if (options.getConnectTimeoutMillis() != null) {
                    tcpClient = tcpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
                            options.getConnectTimeoutMillis().intValue());
                }
                tcpClient = tcpClient.doOnConnected(connection -> {
                    if(webOptions.getReadTimeoutMillis() != null){
                        connection.addHandlerLast(new ReadTimeoutHandler(
                                webOptions.getReadTimeoutMillis(), TimeUnit.MILLISECONDS));
                    }
                    if(webOptions.getWriteTimeoutMillis() != null){
                        connection.addHandlerLast(new WriteTimeoutHandler(
                                webOptions.getWriteTimeoutMillis(), TimeUnit.MILLISECONDS));
                    }
                });

                HttpClient httpClient = HttpClient.from(tcpClient);
                if (options.isTryUseCompression() != null) {
                    httpClient = httpClient.compress(true);
                }
                ReactorClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);

                setWebClient(webClient.mutate().clientConnector(connector).build());
            }
            return this;
        }

        protected void setWebClient(WebClient webClient){
            this.webClient = webClient;
            clientFactory(methodMetadata -> webClient(methodMetadata, webClient));
        }
    }

The method options will always be called, even if we use the builder constructor that accepts a webClient object, creating a new connector every time and setting it to the underlying webClient.

This is needed for example if the webClient object is configured to trust any SSL certificate:

 TcpClient tcpClient = TcpClient.create().secure(t -> t.sslContext(SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build()))
  .doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(60))
   .addHandlerLast(new WriteTimeoutHandler(60)));

 WebClient webClientToUse = WebClient.builder()
  .clientConnector(new ReactorClientHttpConnector(HttpClient.from(tcpClient)))
  .build();

This issue can be addressed by not calling the options method from the builder constructor that takes already a webClient object (https://github.com/Playtika/feign-reactive/blob/develop/feign-reactor-webclient/src/main/java/reactivefeign/webclient/WebReactiveFeign.java#L59). Happy to create a PR if needed.

ReactiveStatusHandler is overrided in one Configuration but for all clients

1st client configuration

    @Bean
    public ErrorDecoder errorDecoder() {
        return new MyErrorDecoder1();
    }

    @Bean
    public ReactiveStatusHandler reactiveStatusHandler(ErrorDecoder errorDecoder) {
        return ReactiveStatusHandlers.errorDecoder(errorDecoder);
    }

2st client configuration overrides only ErrorDecoder

    @Bean
    public MyErrorDecoder2 errorDecoder(ObjectMapper objectMapper) {
        return new MyErrorDecoder2(objectMapper);
    }

When 1st and 2nd clients are used together 2nd client uses MyErrorDecoder1 but I expect it to use MyErrorDecoder2with default ReactiveStatusHandler.

Both clients contain @ReactiveFeignClient with appropriate 'configuration'.
)

Client Specific RequestInterceptor / OAuth?

Can you provide an example of how to have a PER CLIENT ReactiveHttpRequestInterceptor while still maintaining the ability to connect by service name? I've tried both WebReactiveFeign.builder() and CloudReactiveFeign.builder() and they can connect by URL only. If I use @EnableReactiveFeignClients, then it works by service name, but I can only add a global ReactiveHttpRequestInterceptor and have no way knowing which Oauth details to use.

Console app hangs with Cloud2 / ReactiveLoadBalancer

Hi,

I am trying to use cloud2 and my app is hanging on exit. Not sure if this is here or spring-cloud reactive load balancer yet. This is a console app, so it runs, does its thing and exits. I can't use the starter directly, since I need to customize the WebClient. Here is how I am initializing:

Windows 10 Pro x64
Spring Boot 2.2.6
JDK 11.0.6

Gradle looks like:

implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client:2.2.2.RELEASE'
implementation 'com.playtika.reactivefeign:feign-reactor-webclient:2.0.2'
implementation 'com.playtika.reactivefeign:feign-reactor-cloud2:2.0.2'
implementation 'com.playtika.reactivefeign:feign-reactor-spring-configuration:2.0.2'

Code wise, I am using the CloudReactiveFeign builder:

		ReactiveFeignNamedContext namedContext = new ReactiveFeignNamedContext(this.applicationContext, name);

		ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory
		= namedContext.get(ReactiveLoadBalancer.Factory.class);
		
		ReactiveFeignBuilder<T> builder = CloudReactiveFeign.<T>builder(WebReactiveFeign.builder(
			WebClient.builder()
				.filter(filterFunction(name.toLowerCase())))).enableLoadBalancer(loadBalancerFactory);

		List<AbstractReactiveFeignConfigurator> configurators = new ArrayList<>(
			namedContext.getAll(AbstractReactiveFeignConfigurator.class).values());

		Collections.sort(configurators);

		for (AbstractReactiveFeignConfigurator configurator : configurators)
			configurator.configure(builder, namedContext);

		return builder
			.target(clazz, name, "http://" + name);

Can you provide any insight? P.S. for cloud v1, the app exists, but throws an illegal state exception on the netflix ShutdownTimerEnabled Shutdown In Progress.

Thanks!

Problem with feign-reactor-cloud without having Ribbon

Is it possible (and how if it is) to use a reactive feign client WITH Hystrix but WITHOUT Ribbon (smart load balancer)?

What I'm trying to achive is just calling an address I've provided in (reactive) feign client in 'url' property and provide a fallback or fallback factory to be called when feign fails. I would also like to use Hystrix Circuit Breaker pattern with HystrixCommandKey and HystrixCommandMetrics. Because of that I've decided to use feign-reactor-cloud project as well as feign-reactor-webclient and feign-reactor-core.

I'm able to do that with a simple, synchronous feign client but i can't make a call with a reactive one after I've added feign-reactor-cloud to my project.

Do you know how to exclude Ribbon configuration or provide a something like a NoOp Ribbon?
Now when I'm trying to call an API through the ReactiveFeignClient I'm getting an exception: [com.netflix.client.ClientException: Load balancer does not have available server for client: reactive-feign-client] which is thrown most likely because my application (or more precisely Ribbon client) don't know the address of Ribbon server - but I don't have one and I don't want to have it.
Cheers

Multipart/Formdata

Many thanks for developing this library. We are glad to be able to use it!
However, we are facing some issues with multipart/formdata when we passthrough it with feign-reactive.

Our webflux controller at endpoint 1 has a simple interface

@PutMapping(value = User.UPDATE, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<> method(@RequestPart("files") Flux<Part> files) 

and receives incoming multipart formdata as a stream. If we now store the files directly we have no issues. But we do not know at the moment how to pass it to another internal webservice via feign-reactive.

Our feign interface in endpoint 1 looks like

  @RequestLine("POST " + Document.BASE)
  @Headers({"Accept: application/json", "Content-Type: multipart/form-data"})
  public Flux<DataDTO> createDocument(@RequestBody Flux<Part> files);

Most of the time we receive this error message before sending anything via feign-reactive

"Content type 'multipart/form-data' not supported for bodyType=org.springframework.http.codec.multipart.Part"

The error message sounds quite odd and any help or idea is very appreciated.

@Param not working in @Header annotation

Hi,

i am trying to port a traditional FeignClient to a ReactiveFeignClient in my project. Here is my interface definition:

@ReactiveFeignClient(name = "my-client",
        url = "\${my.url}",
        configuration = [MyConfig::class])
interface MyReactiveClient {

    @RequestLine("POST /the/post/url")
    @Headers("Authorization: Bearer {token}", "Content-Type: application/json")
    fun predict(@Param("token") token: String, myRequest: MyRequest): Mono<MyResponse>
}

While this works for a traditional FeignClient there seems to be a problem with the token parameter in the reactive implementation. The parameter does not get set on the header. Is there anything wrong with this approach?

I use 'Spring Cloud Greenwich.RELEASE' and my api declare @ReactiveFeignClient, but micro-service can not get eureka server list for service

Hi,
I using Spring Cloud the version is Greenwich.RELEASE and feign-reactive version is '1.0.15', my service api declare using @ReactiveFeignClient and use @Autowired inject to api client instance, when my eureka client micro-service invoke the api remote method, it can not got eureka registry services list. when I debug found some problem need to help..

package com.netflix.loadbalancer;
LoadBalancerContext

ILoadBalancer lb = this.getLoadBalancer();
        if (host == null) {
            if (lb != null) {
                Server svc = lb.chooseServer(loadBalancerKey);
                if (svc == null) {
                    throw new ClientException(ErrorType.GENERAL, "Load balancer does not have available server for client: " + this.clientName);
                }

                host = svc.getHost();
                if (host == null) {
                    throw new ClientException(ErrorType.GENERAL, "Invalid Server for :" + svc);
                }

                logger.debug("{} using LB returned Server: {} for request {}", new Object[]{this.clientName, svc, original});
                return svc;
            }

the svc is null!

until to manual configure bootstap.yaml like this:

xxx-service:
  ribbon:
    listOfServers: 'http://127.0.0.1:9002'

that svc can got not null.
but spring cloud micro-services too many and using docker, so that can not manual configure client, i think it is auto fetch eureka registry service.

under is my eureka client config,

eureka:
  instance:
    prefer-ip-address: true
    metadata-map:
      user.name: ${spring.security.user.name}
      user.password: ${spring.security.user.password}
  client:
    registerWithEureka: true
    fetchRegistry: true
    registryFetchIntervalSeconds: 5
    service-url:
      defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@localhost:8761/eureka/

WebClientConfig.java

import com.netflix.client.ClientFactory;
import com.netflix.client.DefaultLoadBalancerRetryHandler;
import com.netflix.client.RequestSpecificRetryHandler;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandKey;
import com.netflix.hystrix.HystrixCommandProperties;
import com.netflix.hystrix.HystrixObservableCommand;
import com.netflix.loadbalancer.reactive.LoadBalancerCommand;
import feign.Response;
import feign.codec.ErrorDecoder;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.web.reactive.function.client.WebClient;
import reactivefeign.ReactiveOptions;
import reactivefeign.cloud.CloudReactiveFeign;
import reactivefeign.cloud.LoadBalancerCommandFactory;
import reactivefeign.retry.BasicReactiveRetryPolicy;
import reactivefeign.retry.ReactiveRetryPolicy;
import reactivefeign.webclient.WebReactiveOptions;

@Configuration
public class WebClientConfig {

    @Bean
    public LoadBalancerCommandFactory balancerCommandFactory(){
        return serviceName ->
                LoadBalancerCommand.builder()
                        .withLoadBalancer(ClientFactory.getNamedLoadBalancer(serviceName))
                        .withRetryHandler(new RequestSpecificRetryHandler(true, true,
                                new DefaultLoadBalancerRetryHandler(1, 0, true), null))
                        .build();
    }

    @Bean
    CloudReactiveFeign.SetterFactory setterFactory() {
        return (target, methodMetadata) -> {
            String groupKey = target.name();
            HystrixCommandKey commandKey = HystrixCommandKey.Factory.asKey(methodMetadata.configKey());
            return HystrixObservableCommand.Setter
                    .withGroupKey(HystrixCommandGroupKey.Factory.asKey(groupKey))
                    .andCommandKey(commandKey)
                    .andCommandPropertiesDefaults(HystrixCommandProperties.Setter()
                            .withExecutionTimeoutEnabled(false)
                    );
        };
    }

    @Bean
    @LoadBalanced
    public WebClient.Builder loadBalancedWebClientBuilder() {
        return WebClient.builder().clientConnector(new ReactorClientHttpConnector());
    }

    @Bean
    public ReactiveOptions reactiveOptions(){
        return new WebReactiveOptions.Builder().setReadTimeoutMillis(500).build();
    }

    @Bean
    public ReactiveRetryPolicy reactiveRetryPolicy(){
        return BasicReactiveRetryPolicy.retryWithBackoff(1, 10);
    }

    @Bean
    public feign.codec.ErrorDecoder reactiveStatusHandler(){
        return new ErrorDecoder() {
            @Override
            public Exception decode(String s, Response response) {
                return null;
            }
        };
    }

}

when I remove

xxx-service:
  ribbon:
    listOfServers: 'http://127.0.0.1:9002'

the problem is throw exception:

Caused by: com.netflix.client.ClientException: Load balancer does not have available server for client: default
	at com.netflix.loadbalancer.LoadBalancerContext.getServerFromLoadBalancer(LoadBalancerContext.java:483) ~[ribbon-loadbalancer-2.3.0.jar:2.3.0]
	at com.netflix.loadbalancer.reactive.LoadBalancerCommand$1.call(LoadBalancerCommand.java:184) [ribbon-loadbalancer-2.3.0.jar:2.3.0]
	... 168 common frames omitted

this config param not work

ribbon:
  eureka:
    enabled: false/true

if loadbalance what should I config,,,,so sad!
Thanks.

Using reactive feign client to read response headers

I am trying to use the Playtika reactive feign client to do a non blocking read from a service and read the response headers. I can get all this to work fine with regular feign clients, but when going to reactive, I don't know how to decode the response to get at the headers. Using a Mono doesn't seem to work. I get a:

Caused by: org.springframework.web.reactive.function.UnsupportedMediaTypeException: Content type 'text/plain;charset=UTF-8' not supported for bodyType=feign.Response

error. Here is my source code. Any ideas?:

@SpringBootApplication
@EnableEurekaClient
@RestController
@EnableReactiveFeignClients
@EnableFeignClients
public class FeignApplication implements CommandLineRunner {

    @Autowired
    private GreetingReactive reactiveFeignClient;

    public static void main(String[] args) {
        SpringApplication.run(FeignApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {

        Mono<Response> a = reactiveFeignClient.greetingWithParam("a");
        a.subscribe(response ->
                System.out.println(response.headers().get("sessionId").iterator().next()),
                    doe->e.printStackTrace());
        while (true);
    }
}


@ReactiveFeignClient(name = "web-flux-app")
public interface GreetingReactive {

    @GetMapping("/greeting")
    Mono<String> greeting();

    @GetMapping(value = "/greetingWithParam")
    Mono<Response> greetingWithParam(@RequestParam(value = "id") String id);
}

@PathVariable is not supported in path in @ReactiveFeignClient

It is not possible to use @PathVariable("userId") as 'path' in @ReactiveFeignClient

@ReactiveFeignClient(
        path = "/endpoint/{userId}/smth"
)
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'c...': Unsatisfied dependency expressed through field 'cropsClient'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '...': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Can't extract service name from url:http://sgh-user-progression-service/sgh-user-progression-service/internal/{userId}/crops

	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1378)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireBeanProperties(AbstractAutowireCapableBeanFactory.java:396)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:119)
	at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83)
	at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:44)
	at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at com.github.tomakehurst.wiremock.junit.WireMockClassRule$1.evaluate(WireMockClassRule.java:70)
	at org.junit.rules.RunRules.evaluate(RunRules.java:20)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'MyClient': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Can't extract service name from url:http://sgh-user-progression-service/sgh-user-progression-service/internal/{userId}/crops
	at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:178)
	at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.getObjectFromFactoryBean(FactoryBeanRegistrySupport.java:101)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getObjectForBeanInstance(AbstractBeanFactory.java:1674)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.getObjectForBeanInstance(AbstractAutowireCapableBeanFactory.java:1216)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:257)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.addCandidateEntry(DefaultListableBeanFactory.java:1467)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1424)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1207)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1164)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593)
	... 30 more
Caused by: java.lang.IllegalArgumentException: Can't extract service name from url:http://sgh-user-progression-service/sgh-user-progression-service/internal/{userId}/crops
	at reactivefeign.cloud.CloudReactiveFeign$Builder.extractServiceName(CloudReactiveFeign.java:205)
	at reactivefeign.cloud.CloudReactiveFeign$Builder.access$100(CloudReactiveFeign.java:50)
	at reactivefeign.cloud.CloudReactiveFeign$Builder$1.create(CloudReactiveFeign.java:194)
	at reactivefeign.methodhandler.ReactiveMethodHandlerFactory.create(ReactiveMethodHandlerFactory.java:34)
	at reactivefeign.cloud.methodhandler.HystrixMethodHandlerFactory.create(HystrixMethodHandlerFactory.java:40)
	at reactivefeign.ReactiveFeign.targetToHandlersByName(ReactiveFeign.java:132)
	at reactivefeign.ReactiveFeign.newInstance(ReactiveFeign.java:85)
	at reactivefeign.ReactiveFeignBuilder.target(ReactiveFeignBuilder.java:95)
	at reactivefeign.ReactiveFeignBuilder.target(ReactiveFeignBuilder.java:85)
	at reactivefeign.spring.config.ReactiveFeignClientFactoryBean.getTarget(ReactiveFeignClientFactoryBean.java:290)
	at reactivefeign.spring.config.ReactiveFeignClientFactoryBean.getObject(ReactiveFeignClientFactoryBean.java:259)
	at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:171)
	... 41 more
Caused by: java.net.URISyntaxException: Illegal character in path at index ...
	at java.net.URI$Parser.fail(URI.java:2848)
	at java.net.URI$Parser.checkChars(URI.java:3021)
	at java.net.URI$Parser.parseHierarchical(URI.java:3105)
	at java.net.URI$Parser.parse(URI.java:3053)
	at java.net.URI.<init>(URI.java:588)
	at reactivefeign.cloud.CloudReactiveFeign$Builder.extractServiceName(CloudReactiveFeign.java:203)
	... 52 more

All requests returns 503

Starting from version 1.0.21, any request with reactive feign client returns response status 503.
with message "Load balancer does not contain an instance for the service 192.168.127.108"
(same requests worked fine with 1.0.20)

Webclient missing upstream context in ExchangeFilterFunction

Feign client's webclient doesn't have the upstream context present when using webclient's filter functions.

A basic filter as follows:

public static class MyFilterFunction implements ExchangeFilterFunction {
        @Override
        public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) {
              return next.exchange(request)
                    .subscriberContext(context -> {
                        System.out.println("WebClient's context: " + context);
                        return context;
                });
        }
}

won't have the context created, for example, in a webfilter, as in:

public class ReactiveSessionIdFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
        return chain.filter(exchange)
                .subscriberContext(context -> {
                    context = context.put("myKey", "myValue"));
                    return context;
                });
    }

}

Using the same filter outside feign-reactive, directly with the WebClient works as expected.

More complete examples of this usage can be found in:

Custom ErrorDecoder doesn't work with feign.version=10.1.0

Please, support feign.version=10.1.0 (it is already used in the latest infra-libraries-bom:3.2.10)

reactivefeign.client.statushandler.ReactiveStatusHandlers.ReactiveStatusHandler.decode

Here feign.Response object is built without setting request-field (request=null). But since feign.version=10.1.0 feign.Response (line 48) constructor requires field request not to be null.

checkState(builder.request != null, "original request is required");

So call method decode will always fail when feign.version=10.1.0.

Caused by: java.lang.IllegalStateException: original request is required
	at feign.Util.checkState(Util.java:127)
	at feign.Response.<init>(Response.java:48)
	at feign.Response.<init>(Response.java:38)
	at feign.Response$Builder.build(Response.java:133)
	at reactivefeign.client.statushandler.ReactiveStatusHandlers$1.lambda$decode$0(ReactiveStatusHandlers.java:54)
	at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:100)

The fuse exhausts all threads

Sending more than 10 Feign requests in one request will cause the fuse to exhaust all threads, resulting in only 10 request results being returned, how can I avoid it

Dynamic URIs with empty targets

Hi,

We are seeing the following error when using an empty target and a URI in the interface.

Exception in thread "main" java.lang.UnsupportedOperationException: Empty targets don't have URLs
	at feign.Target$EmptyTarget.url(Target.java:166)
	at reactivefeign.methodhandler.PublisherClientMethodHandler.<init>(PublisherClientMethodHandler.java:75)
	at reactivefeign.methodhandler.ReactiveMethodHandlerFactory.create(ReactiveMethodHandlerFactory.java:34)
	at reactivefeign.ReactiveFeign.targetToHandlersByName(ReactiveFeign.java:126)
	at reactivefeign.ReactiveFeign.newInstance(ReactiveFeign.java:79)
	at reactivefeign.ReactiveFeignBuilder.target(ReactiveFeignBuilder.java:114)

To reproduce this, use the following code. It creates a standard feign client and a reactive one.
The former works as expected but the reactive one fails with the above exception.

public static void main(String[] args)
	{
		Test feign = Feign.builder()
		     .target(Target.EmptyTarget.create(Test.class));

                 try {
		    feign.send(URI.create("https://google.com"), "").block();
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
		Test feignReactive = WebReactiveFeign
				.<Test>builder()
				.target(Target.EmptyTarget.create(Test.class));

		feignReactive.send(URI.create("https://google.com"), "").block();
	}

interface Test
{
	@RequestLine("PUT")
	Mono<Void> send(URI baseUrl, String data);

}

WebClient request metrics is useless

Hi,
we use the library for a lot of our services and it working quite well.

We have detect problem ugprading to Spring Bott 2.1. Since Spring Boot 2.1, the framework expose WebClient Metrics (see https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-metrics.html#production-ready-metrics-http-clients). To working the metrics correctly it is required to use template uri. Eg. to use webClient.get().uri("/users/{id}", 1). But current implementation construct full URI from method @RequestLine and then call eg. webClient.get().uri("/users/1").

This causing to have metrics for every URL parameters and effectively they are useless.

Do we have a plan to integrate reactive-feign with RSocket

Thanks a lot for this awesome library.

I have been using reactive-feign in production for months. It works fine for almost all of http use cases except that I have some services base on RSocket cannot use flow control feature when useing reactive-feign on client side. So may I ask you if we will integrate reactivefeign with RSocket in feature? or some advise on how to config feign-reactor-webclient/feign-reactor-rx2.

Best regards.

Zipkinn/Sleuth + ReactiveFeign

I am currently facing the problem of getting different TraceIDs when forwarding requests with ReactiveFeign. There is a new TraceID every time the request gets through my gateway, which I use to check if credentials are valid.

Ran into this on research:
https://stackoverflow.com/questions/48211360/how-to-implement-sleuth-tracing-with-feign-builder

So I wondered if this is also possible with ReactiveFeign? I can't get it to work. Maybe you could point me in the right direction.

Thanks in advance for the answer!

ResponseEntity Support

Hello,

I have a client like this that I'm trying to make it work using reactive feign.

@ReactiveFeignClient(name = "mktcloud-service", url = "http://localhost:8092")
public interface JourneyV1Client {
  @RequestMapping(
      value = "/v1/journeys/apikey",
      produces = {"application/json"},
      method = RequestMethod.GET)
  Mono<ResponseEntity<ApiKeySearchResponseDTO>> findApiKey(
      @NotNull
          @ApiParam(value = "Journey Name", required = true)
          @Valid
          @RequestParam(value = "name", required = true)
          String name);
}

However when I run the application I face the following issue:

Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.http.ResponseEntity` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (io.netty.buffer.ByteBufInputStream); line: 1, column: 2]
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67) ~[jackson-databind-2.10.2.jar:2.10.2]
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1589) ~[jackson-databind-2.10.2.jar:2.10.2]

On regular feign there is the Encoder and Decoder concept, and spring implementation of openfeign uses a ResponseEntityDecoder to wrap the other decoders.

I did not see a similar way to perform this on reactive feign, so I think this must be an issue, as Spring support seems to be part of the project.

Spring Boot 2.2.0.RELEASE Beans form a cycle

Hey, I'm facing a problem when upgrading to Spring Boot 2.2.0.RELEASE
Error message:

Description:

The dependencies of some of the beans in the application context form a cycle:
.
.
.
.
      ↓
┌─────┐
|  reactiveFeignCloudBuilder defined in reactivefeign.spring.config.ReactiveFeignClientsConfiguration$ReactiveFeignClientsCloudConfiguration
└─────┘

Cloudless client configuration

Case

I have a project with reactive feign clients. Some clients need to be configured in a cloud mode, so I put feign-reactor-cloud in the classpath. But several clients are external APIs, so these clients need to be configured in cloudless mode.

Expected behavior

In openfeign this case solved by url attribute in @FeignClient. If url is present then load balancer doesn't apply to this client

Actual behavior

In reactivefeign load balancer applied to these clients
if I use reactive.feign.ribbon.enabled and reactive.feign.hystrix.enabled it disables ribbon and hystrix globally.

Filter function breaks WebClient implementation

Hi,

I have noticed that if I use the Spring oauth filter function, it breaks the WebClient implementation. Oddly, I have to manually "prime it" and then it works, but something is breaking it working automatically. Works fine with a normal WebClient. I have also tried bypassing the load balancer with an absolute URL.

Creation code:

		ReactiveFeignBuilder<MyFeignClient> builder = CloudReactiveFeign.<MyFeignClient>builder(WebReactiveFeign.builder(
			WebClient.builder()
			.filter(filterFunction(name.toLowerCase()))
				
					));

	private ServerOAuth2AuthorizedClientExchangeFilterFunction filterFunction(String name) {
		ServerOAuth2AuthorizedClientExchangeFilterFunction filterFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(
			new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(this.clientRegistrations, this.clientService));
		filterFunction.setDefaultClientRegistrationId(name);
		return filterFunction;
	}

POM looks like this:

		<dependency>
		  <groupId>com.playtika.reactivefeign</groupId>
		  <artifactId>feign-reactor-webclient</artifactId>
		  <version>2.0.2</version>
		</dependency>		
		<dependency>
		  <groupId>com.playtika.reactivefeign</groupId>
		  <artifactId>feign-reactor-cloud</artifactId>
		  <version>2.0.2</version>
		</dependency>
		<dependency>
		  <groupId>com.playtika.reactivefeign</groupId>
		  <artifactId>feign-reactor-spring-configuration</artifactId>
		  <version>2.0.2</version>
		</dependency>

If I don't manually prime the webclient, I get an error:

MyFeignClient#getAllCustomers() timed-out and no fallback available.

This is testing on a rest method that doesn't even require Oauth and I still get that. If I comment out the filter function part, then it works fine. Using a regular WebClient with that filter function also works fine.

Another observation is that the load balancer intercept doesn't work on the filter function, if I point the filter function to http://localhost:8080/oauth/token it works (after priming), but if I specify http://MYSVC//oauth/token, it doesn't resolve that through the load balancer.

A problem about feign-reactor-spring-configuration

I have several reactive-feign client (@ReactiveFeignClient) in my project,and they have the same value of the attribute “name”.
When I started my application,it throw exception:

The bean 'xxx-service.ReactiveFeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.

I checked your code:

reactivefeign.spring.config.ReactiveFeignClientsRegistrar#registerReactiveFeignClients

I find you use the value of “name” to regist spring bean.

Hope to fix it soon!

Tks!

IllegalArgumentException on using MicrometerReactiveLogger

i use this micrometerReactiveLogger configuration:
new MicrometerReactiveLogger( Clock.systemUTC(), meterRegistry, CLIENT_CALL_METRIC_NAME, Set.of(MetricsTag.FEIGN_CLIENT_METHOD, MetricsTag.URI_TEMPLATE, MetricsTag.HTTP_METHOD, MetricsTag.STATUS, MetricsTag.EXCEPTION)); }
but because there are exceptions only on some calls, the exception tag is not always present, which results in this exception from prometheus side:

java.lang.IllegalArgumentException: Prometheus requires that all meters with the same name have the same set of tag keys. There is already an existing meter named 'http_client_requests_seconds' containing tag keys [feignMethod, method, status, uri]. The meter you are attempting to register has keys [exception, feignMethod, method, status, uri].

there should maybe be the EXCEPTION tag always present, defaulting to "none" for example

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.