├── .gitignore ├── LICENSE ├── README.md ├── feign-reactor-cloud ├── pom.xml └── src │ ├── main │ └── java │ │ └── reactivefeign │ │ └── cloud │ │ ├── CloudReactiveFeign.java │ │ ├── methodhandler │ │ ├── HystrixMethodHandler.java │ │ └── HystrixMethodHandlerFactory.java │ │ └── publisher │ │ └── RibbonPublisherClient.java │ └── test │ └── java │ └── reactivefeign │ └── cloud │ ├── HystrixReactiveHttpClientTest.java │ └── LoadBalancingReactiveHttpClientTest.java ├── feign-reactor-core ├── pom.xml └── src │ ├── main │ └── java │ │ └── reactivefeign │ │ ├── ReactiveContract.java │ │ ├── ReactiveFeign.java │ │ ├── ReactiveInvocationHandler.java │ │ ├── ReactiveOptions.java │ │ ├── ReactiveRetryPolicy.java │ │ ├── ReactiveRetryers.java │ │ ├── client │ │ ├── DelegatingReactiveHttpResponse.java │ │ ├── InterceptorReactiveHttpClient.java │ │ ├── LoggerReactiveHttpClient.java │ │ ├── ReactiveHttpClient.java │ │ ├── ReactiveHttpRequest.java │ │ ├── ReactiveHttpRequestInterceptor.java │ │ ├── ReactiveHttpResponse.java │ │ ├── ReadTimeoutException.java │ │ ├── ResponseMappers.java │ │ ├── StatusHandlerReactiveHttpClient.java │ │ └── statushandler │ │ │ ├── CompositeStatusHandler.java │ │ │ ├── ReactiveStatusHandler.java │ │ │ └── ReactiveStatusHandlers.java │ │ ├── methodhandler │ │ ├── DefaultMethodHandler.java │ │ ├── FluxMethodHandler.java │ │ ├── MethodHandler.java │ │ ├── MethodHandlerFactory.java │ │ ├── MonoMethodHandler.java │ │ ├── PublisherClientMethodHandler.java │ │ └── ReactiveMethodHandlerFactory.java │ │ ├── publisher │ │ ├── FluxPublisherHttpClient.java │ │ ├── FluxRetryPublisherHttpClient.java │ │ ├── MonoPublisherHttpClient.java │ │ ├── MonoRetryPublisherHttpClient.java │ │ ├── PublisherClientFactory.java │ │ ├── PublisherHttpClient.java │ │ └── RetryPublisherHttpClient.java │ │ └── utils │ │ ├── FeignUtils.java │ │ ├── HttpUtils.java │ │ ├── MultiValueMapUtils.java │ │ └── Pair.java │ └── test │ └── java │ └── reactivefeign │ ├── CompressionTest.java │ ├── ConnectionTimeoutTest.java │ ├── ContractTest.java │ ├── DefaultMethodTest.java │ ├── LoggerTest.java │ ├── NotFoundTest.java │ ├── ReactivityTest.java │ ├── ReadTimeoutTest.java │ ├── RequestInterceptorTest.java │ ├── RetryingTest.java │ ├── SmokeTest.java │ ├── StatusHandlerTest.java │ ├── TestUtils.java │ ├── allfeatures │ ├── AllFeaturesApi.java │ ├── AllFeaturesController.java │ └── AllFeaturesTest.java │ ├── resttemplate │ ├── CompressionTest.java │ ├── ConnectionTimeoutTest.java │ ├── ContractTest.java │ ├── DefaultMethodTest.java │ ├── LoggerTest.java │ ├── NotFoundTest.java │ ├── ReactivityTest.java │ ├── ReadTimeoutTest.java │ ├── RequestInterceptorTest.java │ ├── RetryingTest.java │ ├── SmokeTest.java │ ├── StatusHandlerTest.java │ └── client │ │ ├── RestTemplateFakeReactiveFeign.java │ │ └── RestTemplateFakeReactiveHttpClient.java │ └── testcase │ ├── IcecreamServiceApi.java │ ├── IcecreamServiceApiBroken.java │ ├── IcecreamServiceApiBrokenByCopy.java │ └── domain │ ├── Bill.java │ ├── Flavor.java │ ├── IceCreamOrder.java │ ├── Mixin.java │ └── OrderGenerator.java ├── feign-reactor-jetty ├── pom.xml └── src │ ├── main │ └── java │ │ └── reactivefeign │ │ └── jetty │ │ ├── JettyReactiveFeign.java │ │ ├── client │ │ ├── JettyReactiveHttpClient.java │ │ └── JettyReactiveHttpResponse.java │ │ └── utils │ │ └── ProxyPostProcessor.java │ └── test │ ├── java │ └── reactivefeign │ │ └── jetty │ │ ├── CompressionTest.java │ │ ├── ConnectionTimeoutTest.java │ │ ├── ContractTest.java │ │ ├── DefaultMethodTest.java │ │ ├── LoggerTest.java │ │ ├── NotFoundTest.java │ │ ├── ReactivityTest.java │ │ ├── ReadTimeoutTest.java │ │ ├── RequestInterceptorTest.java │ │ ├── RetryingTest.java │ │ ├── SmokeTest.java │ │ ├── StatusHandlerTest.java │ │ └── allfeatures │ │ └── AllFeaturesTest.java │ └── resources │ └── log4j2.xml ├── feign-reactor-rx2 ├── pom.xml └── src │ ├── main │ └── java │ │ └── reactivefeign │ │ └── rx2 │ │ ├── Rx2Contract.java │ │ ├── Rx2ReactiveFeign.java │ │ ├── client │ │ └── statushandler │ │ │ ├── Rx2ReactiveStatusHandler.java │ │ │ ├── Rx2StatusHandler.java │ │ │ └── Rx2StatusHandlers.java │ │ └── methodhandler │ │ ├── Rx2MethodHandler.java │ │ ├── Rx2MethodHandlerFactory.java │ │ └── Rx2PublisherClientMethodHandler.java │ └── test │ └── java │ └── reactivefeign │ └── rx2 │ ├── ContractTest.java │ ├── DefaultMethodTest.java │ ├── LoggerTest.java │ ├── NotFoundTest.java │ ├── ReactivityTest.java │ ├── ReadTimeoutTest.java │ ├── RequestInterceptorTest.java │ ├── SmokeTest.java │ ├── StatusHandlerTest.java │ ├── TestUtils.java │ └── testcase │ ├── IcecreamServiceApi.java │ ├── IcecreamServiceApiBroken.java │ └── domain │ ├── Bill.java │ ├── Flavor.java │ ├── IceCreamOrder.java │ ├── Mixin.java │ └── OrderGenerator.java ├── feign-reactor-webclient ├── pom.xml └── src │ ├── main │ └── java │ │ └── reactivefeign │ │ └── webclient │ │ ├── WebReactiveFeign.java │ │ └── client │ │ ├── WebReactiveHttpClient.java │ │ └── WebReactiveHttpResponse.java │ └── test │ ├── java │ └── reactivefeign │ │ └── webclient │ │ ├── CompressionTest.java │ │ ├── ConnectionTimeoutTest.java │ │ ├── ContractTest.java │ │ ├── DefaultMethodTest.java │ │ ├── LoggerTest.java │ │ ├── NotFoundTest.java │ │ ├── ReactivityTest.java │ │ ├── ReadTimeoutTest.java │ │ ├── RequestInterceptorTest.java │ │ ├── RetryingTest.java │ │ ├── SmokeTest.java │ │ ├── StatusHandlerTest.java │ │ └── allfeatures │ │ ├── AllFeaturesTest.java │ │ ├── WebClientFeaturesApi.java │ │ ├── WebClientFeaturesController.java │ │ └── WebClientFeaturesTest.java │ └── resources │ └── log4j2.xml ├── pom.xml └── settings.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | 12 | ### IntelliJ IDEA ### 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | 18 | ### NetBeans ### 19 | nbproject/private/ 20 | build/ 21 | nbbuild/ 22 | dist/ 23 | nbdist/ 24 | .nb-gradle/ -------------------------------------------------------------------------------- /feign-reactor-cloud/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.github.reactivefeign 7 | feign-reactor 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | feign-reactor-cloud 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | io.github.reactivefeign 20 | feign-reactor-webclient 21 | 22 | 23 | 24 | com.netflix.hystrix 25 | hystrix-core 26 | 27 | 28 | 29 | com.netflix.archaius 30 | archaius-core 31 | 32 | 33 | 34 | com.netflix.ribbon 35 | ribbon-core 36 | 37 | 38 | 39 | com.netflix.ribbon 40 | ribbon-loadbalancer 41 | 42 | 43 | 44 | io.reactivex 45 | rxjava 46 | 47 | 48 | 49 | io.reactivex 50 | rxjava-reactive-streams 51 | 52 | 53 | 54 | io.github.openfeign 55 | feign-core 56 | 57 | 58 | 59 | org.slf4j 60 | slf4j-api 61 | 62 | 63 | 64 | 65 | org.springframework.boot 66 | spring-boot-starter-test 67 | test 68 | 69 | 70 | io.projectreactor 71 | reactor-test 72 | test 73 | 74 | 75 | junit 76 | junit 77 | test 78 | 79 | 80 | 81 | org.assertj 82 | assertj-core 83 | test 84 | 85 | 86 | 87 | com.github.tomakehurst 88 | wiremock 89 | test 90 | 91 | 92 | 93 | com.google.guava 94 | guava 95 | test 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /feign-reactor-cloud/src/main/java/reactivefeign/cloud/methodhandler/HystrixMethodHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.cloud.methodhandler; 2 | 3 | import feign.MethodMetadata; 4 | import feign.Target; 5 | import org.springframework.lang.Nullable; 6 | import reactivefeign.cloud.CloudReactiveFeign; 7 | import reactivefeign.methodhandler.MethodHandler; 8 | import reactivefeign.methodhandler.MethodHandlerFactory; 9 | 10 | import java.lang.reflect.Method; 11 | import java.util.function.Function; 12 | 13 | import static feign.Util.checkNotNull; 14 | 15 | public class HystrixMethodHandlerFactory implements MethodHandlerFactory { 16 | 17 | private final MethodHandlerFactory methodHandlerFactory; 18 | private final CloudReactiveFeign.SetterFactory commandSetterFactory; 19 | private final Function fallbackFactory; 20 | 21 | public HystrixMethodHandlerFactory(MethodHandlerFactory methodHandlerFactory, 22 | CloudReactiveFeign.SetterFactory commandSetterFactory, 23 | @Nullable Function fallbackFactory) { 24 | this.methodHandlerFactory = checkNotNull(methodHandlerFactory, "methodHandlerFactory must not be null"); 25 | this.commandSetterFactory = checkNotNull(commandSetterFactory, "hystrixObservableCommandSetter must not be null"); 26 | this.fallbackFactory = fallbackFactory; 27 | } 28 | 29 | @Override 30 | public MethodHandler create(final Target target, final MethodMetadata metadata) { 31 | return new HystrixMethodHandler( 32 | target, metadata, 33 | methodHandlerFactory.create(target, metadata), 34 | commandSetterFactory, 35 | fallbackFactory); 36 | } 37 | 38 | @Override 39 | public MethodHandler createDefault(Method method) { 40 | return methodHandlerFactory.createDefault(method); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /feign-reactor-cloud/src/main/java/reactivefeign/cloud/publisher/RibbonPublisherClient.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.cloud.publisher; 2 | 3 | import com.netflix.loadbalancer.Server; 4 | import com.netflix.loadbalancer.reactive.LoadBalancerCommand; 5 | import org.reactivestreams.Publisher; 6 | import org.springframework.lang.Nullable; 7 | import reactivefeign.client.ReactiveHttpRequest; 8 | import reactivefeign.publisher.PublisherHttpClient; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | import rx.Observable; 12 | import rx.RxReactiveStreams; 13 | 14 | import java.lang.reflect.Type; 15 | import java.net.URI; 16 | import java.net.URISyntaxException; 17 | 18 | /** 19 | * @author Sergii Karpenko 20 | */ 21 | public class RibbonPublisherClient implements PublisherHttpClient { 22 | 23 | private final LoadBalancerCommand loadBalancerCommand; 24 | private final PublisherHttpClient publisherClient; 25 | private final Type publisherType; 26 | 27 | public RibbonPublisherClient(@Nullable LoadBalancerCommand loadBalancerCommand, 28 | PublisherHttpClient publisherClient, 29 | Type publisherType) { 30 | this.loadBalancerCommand = loadBalancerCommand; 31 | this.publisherClient = publisherClient; 32 | this.publisherType = publisherType; 33 | } 34 | 35 | @Override 36 | public Publisher executeRequest(ReactiveHttpRequest request) { 37 | 38 | if (loadBalancerCommand != null) { 39 | Observable observable = loadBalancerCommand.submit(server -> { 40 | 41 | ReactiveHttpRequest lbRequest = loadBalanceRequest(request, server); 42 | 43 | Publisher publisher = (Publisher)publisherClient.executeRequest(lbRequest); 44 | return RxReactiveStreams.toObservable(publisher); 45 | }); 46 | 47 | Publisher publisher = RxReactiveStreams.toPublisher(observable); 48 | 49 | if(publisherType == Mono.class){ 50 | return Mono.from(publisher); 51 | } else if(publisherType == Flux.class){ 52 | return Flux.from(publisher); 53 | } else { 54 | throw new IllegalArgumentException("Unknown publisherType: " + publisherType); 55 | } 56 | } else { 57 | return publisherClient.executeRequest(request); 58 | } 59 | } 60 | 61 | protected ReactiveHttpRequest loadBalanceRequest(ReactiveHttpRequest request, Server server) { 62 | URI uri = request.uri(); 63 | try { 64 | URI lbUrl = new URI(uri.getScheme(), uri.getUserInfo(), server.getHost(), server.getPort(), 65 | uri.getPath(), uri.getQuery(), uri.getFragment()); 66 | return new ReactiveHttpRequest(request.method(), lbUrl, request.headers(), request.body()); 67 | } catch (URISyntaxException e) { 68 | throw new IllegalArgumentException(e); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/ReactiveContract.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import feign.Contract; 17 | import feign.MethodMetadata; 18 | import reactor.core.publisher.Flux; 19 | import reactor.core.publisher.Mono; 20 | 21 | import java.lang.reflect.ParameterizedType; 22 | import java.lang.reflect.Type; 23 | import java.util.List; 24 | 25 | import static feign.Util.checkNotNull; 26 | import static reactivefeign.utils.FeignUtils.bodyActualType; 27 | import static reactivefeign.utils.FeignUtils.returnActualType; 28 | 29 | /** 30 | * Contract allowing only {@link Mono} and {@link Flux} return type. 31 | * 32 | * @author Sergii Karpenko 33 | */ 34 | public class ReactiveContract implements Contract { 35 | 36 | private final Contract delegate; 37 | 38 | public ReactiveContract(final Contract delegate) { 39 | this.delegate = checkNotNull(delegate, "delegate must not be null"); 40 | } 41 | 42 | @Override 43 | public List parseAndValidatateMetadata(final Class targetType) { 44 | final List methodsMetadata = 45 | this.delegate.parseAndValidatateMetadata(targetType); 46 | 47 | for (final MethodMetadata metadata : methodsMetadata) { 48 | final Type type = metadata.returnType(); 49 | if (!isReactorType(type)) { 50 | throw new IllegalArgumentException(String.format( 51 | "Method %s of contract %s doesn't returns reactor.core.publisher.Mono or reactor.core.publisher.Flux", 52 | metadata.configKey(), targetType.getSimpleName())); 53 | } 54 | 55 | if(returnActualType(metadata) == byte[].class || bodyActualType(metadata) == byte[].class){ 56 | throw new IllegalArgumentException(String.format( 57 | "Method %s of contract %s will cause data to be copied, use ByteBuffer instead", 58 | metadata.configKey(), targetType.getSimpleName())); 59 | } 60 | } 61 | 62 | return methodsMetadata; 63 | } 64 | 65 | private boolean isReactorType(final Type type) { 66 | return (type instanceof ParameterizedType) 67 | && (((ParameterizedType) type).getRawType() == Mono.class 68 | || ((ParameterizedType) type).getRawType() == Flux.class); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/ReactiveInvocationHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import feign.InvocationHandlerFactory; 17 | import feign.InvocationHandlerFactory.MethodHandler; 18 | import feign.Target; 19 | import org.reactivestreams.Publisher; 20 | import reactivefeign.client.ReactiveHttpClient; 21 | import reactor.core.publisher.Flux; 22 | import reactor.core.publisher.Mono; 23 | 24 | import java.lang.reflect.InvocationHandler; 25 | import java.lang.reflect.Method; 26 | import java.lang.reflect.Proxy; 27 | import java.util.Map; 28 | 29 | import static feign.Util.checkNotNull; 30 | 31 | /** 32 | * {@link InvocationHandler} implementation that transforms calls to methods of feign contract into 33 | * asynchronous HTTP requests via spring WebClient. 34 | * 35 | * @author Sergii Karpenko 36 | */ 37 | public final class ReactiveInvocationHandler implements InvocationHandler { 38 | private final Target target; 39 | private final Map dispatch; 40 | 41 | private ReactiveInvocationHandler(final Target target, 42 | final Map dispatch) { 43 | this.target = checkNotNull(target, "target must not be null"); 44 | this.dispatch = checkNotNull(dispatch, "dispatch must not be null"); 45 | defineObjectMethodsHandlers(); 46 | } 47 | 48 | private void defineObjectMethodsHandlers() { 49 | try { 50 | dispatch.put(Object.class.getMethod("equals", Object.class), 51 | args -> { 52 | Object otherHandler = args.length > 0 && args[0] != null 53 | ? Proxy.getInvocationHandler(args[0]) 54 | : null; 55 | return equals(otherHandler); 56 | }); 57 | dispatch.put(Object.class.getMethod("hashCode"), 58 | args -> hashCode()); 59 | dispatch.put(Object.class.getMethod("toString"), 60 | args -> toString()); 61 | } catch (NoSuchMethodException e) { 62 | throw new RuntimeException(e); 63 | } 64 | } 65 | 66 | @Override 67 | public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { 68 | return dispatch.get(method).invoke(args); 69 | } 70 | 71 | @Override 72 | public boolean equals(final Object other) { 73 | if (other instanceof ReactiveInvocationHandler) { 74 | final ReactiveInvocationHandler otherHandler = (ReactiveInvocationHandler) other; 75 | return this.target.equals(otherHandler.target); 76 | } 77 | return false; 78 | } 79 | 80 | @Override 81 | public int hashCode() { 82 | return target.hashCode(); 83 | } 84 | 85 | @Override 86 | public String toString() { 87 | return target.toString(); 88 | } 89 | 90 | /** 91 | * Factory for ReactiveInvocationHandler. 92 | */ 93 | public static final class Factory implements InvocationHandlerFactory { 94 | 95 | @Override 96 | public InvocationHandler create(final Target target, 97 | final Map dispatch) { 98 | return new ReactiveInvocationHandler(target, dispatch); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/ReactiveOptions.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | /** 17 | * @author Sergii Karpenko 18 | */ 19 | public class ReactiveOptions { 20 | 21 | private final Long connectTimeoutMillis; 22 | private final Long readTimeoutMillis; 23 | private final Boolean tryUseCompression; 24 | 25 | private ReactiveOptions(Long connectTimeoutMillis, Long readTimeoutMillis, 26 | Boolean tryUseCompression) { 27 | this.connectTimeoutMillis = connectTimeoutMillis; 28 | this.readTimeoutMillis = readTimeoutMillis; 29 | this.tryUseCompression = tryUseCompression; 30 | } 31 | 32 | public Long getConnectTimeoutMillis() { 33 | return connectTimeoutMillis; 34 | } 35 | 36 | public Long getReadTimeoutMillis() { 37 | return readTimeoutMillis; 38 | } 39 | 40 | public Boolean isTryUseCompression() { 41 | return tryUseCompression; 42 | } 43 | 44 | public boolean isEmpty() { 45 | return connectTimeoutMillis == null && readTimeoutMillis == null 46 | && tryUseCompression == null; 47 | } 48 | 49 | public static class Builder { 50 | private Long connectTimeoutMillis; 51 | private Long readTimeoutMillis; 52 | private Boolean tryUseCompression; 53 | 54 | public Builder() {} 55 | 56 | public Builder setConnectTimeoutMillis(long connectTimeoutMillis) { 57 | this.connectTimeoutMillis = connectTimeoutMillis; 58 | return this; 59 | } 60 | 61 | public Builder setReadTimeoutMillis(long readTimeoutMillis) { 62 | this.readTimeoutMillis = readTimeoutMillis; 63 | return this; 64 | } 65 | 66 | public Builder setTryUseCompression(boolean tryUseCompression) { 67 | this.tryUseCompression = tryUseCompression; 68 | return this; 69 | } 70 | 71 | public ReactiveOptions build() { 72 | return new ReactiveOptions(connectTimeoutMillis, readTimeoutMillis, 73 | tryUseCompression); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/ReactiveRetryPolicy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import reactor.core.Exceptions; 17 | import reactor.core.publisher.Flux; 18 | import reactor.core.publisher.Mono; 19 | import reactor.util.function.Tuples; 20 | 21 | import java.time.Duration; 22 | import java.util.function.Function; 23 | 24 | public interface ReactiveRetryPolicy { 25 | /** 26 | * @param error 27 | * @param attemptNo 28 | * @return -1 if should not be retried, 0 if retry immediately 29 | */ 30 | long retryDelay(Throwable error, int attemptNo); 31 | 32 | default Function, Flux> toRetryFunction() { 33 | return errors -> errors 34 | .zipWith(Flux.range(1, Integer.MAX_VALUE), (error, index) -> { 35 | long delay = retryDelay(error, index); 36 | if (delay >= 0) { 37 | return Tuples.of(delay, error); 38 | } else { 39 | throw Exceptions.propagate(error); 40 | } 41 | }).flatMap( 42 | tuple2 -> tuple2.getT1() > 0 43 | ? Mono.delay(Duration.ofMillis(tuple2.getT1())) 44 | .map(time -> tuple2.getT2()) 45 | : Mono.just(tuple2.getT2())); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/ReactiveRetryers.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import feign.RetryableException; 17 | 18 | import java.util.Date; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class ReactiveRetryers { 24 | 25 | public static ReactiveRetryPolicy retry(int maxRetries) { 26 | return (error, attemptNo) -> attemptNo <= maxRetries ? 0 : -1; 27 | } 28 | 29 | public static ReactiveRetryPolicy retryWithBackoff(int maxRetries, long periodInMs) { 30 | return (error, attemptNo) -> { 31 | if (attemptNo <= maxRetries) { 32 | long delay; 33 | Date retryAfter; 34 | // "Retry-After" header set 35 | if (error instanceof RetryableException 36 | && (retryAfter = ((RetryableException) error) 37 | .retryAfter()) != null) { 38 | delay = retryAfter.getTime() - System.currentTimeMillis(); 39 | delay = Math.min(delay, periodInMs); 40 | delay = Math.max(delay, 0); 41 | } else { 42 | delay = periodInMs; 43 | } 44 | return delay; 45 | } else { 46 | return -1; 47 | } 48 | }; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/DelegatingReactiveHttpResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.client; 15 | 16 | import reactor.core.publisher.Mono; 17 | 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | /** 22 | * @author Sergii Karpenko 23 | */ 24 | abstract public class DelegatingReactiveHttpResponse implements ReactiveHttpResponse { 25 | 26 | private final ReactiveHttpResponse response; 27 | 28 | protected DelegatingReactiveHttpResponse(ReactiveHttpResponse response) { 29 | this.response = response; 30 | } 31 | 32 | protected ReactiveHttpResponse getResponse() { 33 | return response; 34 | } 35 | 36 | @Override 37 | public int status() { 38 | return response.status(); 39 | } 40 | 41 | @Override 42 | public Map> headers() { 43 | return response.headers(); 44 | } 45 | 46 | @Override 47 | public Mono bodyData() { 48 | throw new UnsupportedOperationException(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/InterceptorReactiveHttpClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.client; 15 | 16 | /** 17 | * Used to modify request before call. May be used to set auth headers to all requests. 18 | * 19 | * @author Sergii Karpenko 20 | */ 21 | public class InterceptorReactiveHttpClient { 22 | 23 | public static ReactiveHttpClient intercept(ReactiveHttpClient reactiveHttpClient, 24 | ReactiveHttpRequestInterceptor interceptor) { 25 | return request -> reactiveHttpClient.executeRequest(interceptor.apply(request)); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/ReactiveHttpClient.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.client; 2 | 3 | import reactor.core.publisher.Mono; 4 | 5 | public interface ReactiveHttpClient { 6 | 7 | Mono executeRequest(ReactiveHttpRequest request); 8 | } 9 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/ReactiveHttpRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.client; 15 | 16 | import org.reactivestreams.Publisher; 17 | 18 | import java.net.URI; 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | import static feign.Util.checkNotNull; 23 | 24 | /** 25 | * An immutable reactive request to an http server. 26 | * 27 | * @author Sergii Karpenko 28 | */ 29 | public final class ReactiveHttpRequest { 30 | 31 | private final String method; 32 | private final URI uri; 33 | private final Map> headers; 34 | private final Publisher body; 35 | 36 | /** 37 | * No parameters can be null except {@code body}. All parameters must be effectively immutable, 38 | * via safe copies, not mutating or otherwise. 39 | */ 40 | public ReactiveHttpRequest(String method, URI uri, 41 | Map> headers, Publisher body) { 42 | this.method = checkNotNull(method, "method of %s", uri); 43 | this.uri = checkNotNull(uri, "url"); 44 | this.headers = checkNotNull(headers, "headers of %s %s", method, uri); 45 | this.body = body; // nullable 46 | } 47 | 48 | public ReactiveHttpRequest(ReactiveHttpRequest request, Publisher body){ 49 | this(request.method, request.uri, request.headers, body); 50 | } 51 | 52 | /* Method to invoke on the server. */ 53 | public String method() { 54 | return method; 55 | } 56 | 57 | /* Fully resolved URL including query. */ 58 | public URI uri() { 59 | return uri; 60 | } 61 | 62 | /* Ordered list of headers that will be sent to the server. */ 63 | public Map> headers() { 64 | return headers; 65 | } 66 | 67 | /** 68 | * If present, this is the replayable body to send to the server. 69 | */ 70 | public Publisher body() { 71 | return body; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/ReactiveHttpRequestInterceptor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.client; 15 | 16 | import java.util.function.Function; 17 | 18 | /** 19 | * Used to modify request before call. May be used to set auth headers to all requests. 20 | * 21 | * @author Sergii Karpenko 22 | * 23 | */ 24 | public interface ReactiveHttpRequestInterceptor 25 | extends Function { 26 | } 27 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/ReactiveHttpResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.client; 15 | 16 | import org.reactivestreams.Publisher; 17 | import reactor.core.publisher.Mono; 18 | 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * Reactive response from an http server. 24 | * 25 | * @author Sergii Karpenko 26 | */ 27 | public interface ReactiveHttpResponse { 28 | 29 | int status(); 30 | 31 | Map> headers(); 32 | 33 | Publisher body(); 34 | 35 | /** 36 | * used by error decoders 37 | * 38 | * @return error message data 39 | */ 40 | Mono bodyData(); 41 | } 42 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/ReadTimeoutException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.client; 15 | 16 | public class ReadTimeoutException extends RuntimeException { 17 | 18 | public ReadTimeoutException(Throwable cause) { 19 | super(cause); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/ResponseMappers.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.client; 15 | 16 | import feign.MethodMetadata; 17 | import org.apache.commons.httpclient.HttpStatus; 18 | import org.reactivestreams.Publisher; 19 | import reactor.core.publisher.Mono; 20 | 21 | import java.util.function.BiFunction; 22 | 23 | /** 24 | * Maps 404 error response to successful empty response 25 | * 26 | * @author Sergii Karpenko 27 | */ 28 | public class ResponseMappers { 29 | 30 | public static BiFunction ignore404() { 31 | return (MethodMetadata methodMetadata, ReactiveHttpResponse response) -> { 32 | if (response.status() == HttpStatus.SC_NOT_FOUND) { 33 | return new DelegatingReactiveHttpResponse(response) { 34 | @Override 35 | public int status() { 36 | return HttpStatus.SC_OK; 37 | } 38 | 39 | @Override 40 | public Publisher body() { 41 | return Mono.empty(); 42 | } 43 | }; 44 | } 45 | return response; 46 | }; 47 | } 48 | 49 | public static ReactiveHttpClient mapResponse( 50 | ReactiveHttpClient reactiveHttpClient, 51 | MethodMetadata methodMetadata, 52 | BiFunction responseMapper) { 53 | return request -> reactiveHttpClient.executeRequest(request) 54 | .map(response -> responseMapper.apply(methodMetadata, response)); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/StatusHandlerReactiveHttpClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.client; 15 | 16 | import feign.MethodMetadata; 17 | import org.reactivestreams.Publisher; 18 | import reactivefeign.client.statushandler.ReactiveStatusHandler; 19 | import reactor.core.publisher.Flux; 20 | import reactor.core.publisher.Mono; 21 | 22 | import static reactivefeign.utils.FeignUtils.methodTag; 23 | 24 | /** 25 | * Uses statusHandlers to process status of http response 26 | * 27 | * @author Sergii Karpenko 28 | */ 29 | 30 | public class StatusHandlerReactiveHttpClient implements ReactiveHttpClient { 31 | 32 | private final ReactiveHttpClient reactiveClient; 33 | private final String methodTag; 34 | 35 | private final ReactiveStatusHandler statusHandler; 36 | 37 | public static ReactiveHttpClient handleStatus( 38 | ReactiveHttpClient reactiveClient, 39 | MethodMetadata methodMetadata, 40 | ReactiveStatusHandler statusHandler) { 41 | return new StatusHandlerReactiveHttpClient(reactiveClient, methodMetadata, statusHandler); 42 | } 43 | 44 | private StatusHandlerReactiveHttpClient(ReactiveHttpClient reactiveClient, 45 | MethodMetadata methodMetadata, 46 | ReactiveStatusHandler statusHandler) { 47 | this.reactiveClient = reactiveClient; 48 | this.methodTag = methodTag(methodMetadata); 49 | this.statusHandler = statusHandler; 50 | } 51 | 52 | @Override 53 | public Mono executeRequest(ReactiveHttpRequest request) { 54 | return reactiveClient.executeRequest(request).map(response -> { 55 | if (statusHandler.shouldHandle(response.status())) { 56 | return new ErrorReactiveHttpResponse(response, statusHandler.decode(methodTag, response)); 57 | } else { 58 | return response; 59 | } 60 | }); 61 | } 62 | 63 | private class ErrorReactiveHttpResponse extends DelegatingReactiveHttpResponse { 64 | 65 | private final Mono error; 66 | 67 | protected ErrorReactiveHttpResponse(ReactiveHttpResponse response, Mono error) { 68 | super(response); 69 | this.error = error; 70 | } 71 | 72 | @Override 73 | public Publisher body() { 74 | if (getResponse().body() instanceof Mono) { 75 | return error.flatMap(Mono::error); 76 | } else { 77 | return error.flatMapMany(Flux::error); 78 | } 79 | } 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/statushandler/CompositeStatusHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.client.statushandler; 15 | 16 | import reactivefeign.client.ReactiveHttpResponse; 17 | import reactor.core.publisher.Mono; 18 | 19 | import java.util.List; 20 | 21 | import static java.util.Arrays.asList; 22 | 23 | /** 24 | * @author Sergii Karpenko 25 | */ 26 | public class CompositeStatusHandler implements ReactiveStatusHandler { 27 | 28 | private final List handlers; 29 | 30 | public static CompositeStatusHandler compose(ReactiveStatusHandler... handlers) { 31 | return new CompositeStatusHandler(asList(handlers)); 32 | } 33 | 34 | private CompositeStatusHandler(List handlers) { 35 | this.handlers = handlers; 36 | } 37 | 38 | @Override 39 | public boolean shouldHandle(int status) { 40 | return handlers.stream().anyMatch(handler -> handler.shouldHandle(status)); 41 | } 42 | 43 | @Override 44 | public Mono decode(String methodKey, ReactiveHttpResponse response) { 45 | return handlers.stream() 46 | .filter(statusHandler -> statusHandler 47 | .shouldHandle(response.status())) 48 | .findFirst() 49 | .map(statusHandler -> statusHandler.decode(methodKey, response)) 50 | .orElse(null); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/statushandler/ReactiveStatusHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.client.statushandler; 15 | 16 | import reactivefeign.client.ReactiveHttpResponse; 17 | import reactor.core.publisher.Mono; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public interface ReactiveStatusHandler { 23 | 24 | boolean shouldHandle(int status); 25 | 26 | Mono decode(String methodKey, ReactiveHttpResponse response); 27 | } 28 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/client/statushandler/ReactiveStatusHandlers.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.client.statushandler; 15 | 16 | import feign.Response; 17 | import feign.codec.ErrorDecoder; 18 | import org.apache.commons.httpclient.HttpStatus; 19 | import reactivefeign.client.ReactiveHttpResponse; 20 | import reactor.core.publisher.Mono; 21 | 22 | import java.util.Map; 23 | import java.util.function.BiFunction; 24 | import java.util.function.Predicate; 25 | import java.util.stream.Collectors; 26 | 27 | import static reactivefeign.utils.HttpUtils.familyOf; 28 | 29 | public class ReactiveStatusHandlers { 30 | 31 | public static ReactiveStatusHandler defaultFeign(ErrorDecoder errorDecoder) { 32 | return new ReactiveStatusHandler() { 33 | 34 | @Override 35 | public boolean shouldHandle(int status) { 36 | return familyOf(status).isError(); 37 | } 38 | 39 | @Override 40 | public Mono decode(String methodTag, ReactiveHttpResponse response) { 41 | return response.bodyData() 42 | .defaultIfEmpty(new byte[0]) 43 | .map(bodyData -> errorDecoder.decode(methodTag, 44 | Response.builder().status(response.status()) 45 | .reason(HttpStatus.getStatusText(response.status())) 46 | .headers(response.headers().entrySet() 47 | .stream() 48 | .collect(Collectors.toMap(Map.Entry::getKey, 49 | Map.Entry::getValue))) 50 | .body(bodyData).build())); 51 | } 52 | }; 53 | } 54 | 55 | public static ReactiveStatusHandler throwOnStatus( 56 | Predicate statusPredicate, 57 | BiFunction errorFunction) { 58 | return new ReactiveStatusHandler() { 59 | @Override 60 | public boolean shouldHandle(int status) { 61 | return statusPredicate.test(status); 62 | } 63 | 64 | @Override 65 | public Mono decode(String methodKey, ReactiveHttpResponse response) { 66 | return Mono.just(errorFunction.apply(methodKey, response)); 67 | } 68 | }; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/methodhandler/DefaultMethodHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.methodhandler; 15 | 16 | import java.lang.invoke.MethodHandle; 17 | import java.lang.invoke.MethodHandles.Lookup; 18 | import java.lang.reflect.Field; 19 | import java.lang.reflect.Method; 20 | 21 | /** 22 | * Handles default methods by directly invoking the default method code on the interface. The bindTo 23 | * method must be called on the result before invoke is called. 24 | */ 25 | public final class DefaultMethodHandler implements MethodHandler { 26 | // Uses Java 7 MethodHandle based reflection. As default methods will only exist when 27 | // run on a Java 8 JVM this will not affect use on legacy JVMs. 28 | private final MethodHandle unboundHandle; 29 | 30 | // handle is effectively final after bindTo has been called. 31 | private MethodHandle handle; 32 | 33 | public DefaultMethodHandler(Method defaultMethod) { 34 | try { 35 | Class declaringClass = defaultMethod.getDeclaringClass(); 36 | Field field = Lookup.class.getDeclaredField("IMPL_LOOKUP"); 37 | field.setAccessible(true); 38 | Lookup lookup = (Lookup) field.get(null); 39 | 40 | this.unboundHandle = lookup.unreflectSpecial(defaultMethod, declaringClass); 41 | } catch (NoSuchFieldException | IllegalAccessException ex) { 42 | throw new IllegalStateException(ex); 43 | } 44 | } 45 | 46 | /** 47 | * Bind this handler to a proxy object. After bound, DefaultMethodHandler#invoke will act as if it 48 | * was called on the proxy object. Must be called once and only once for a given instance of 49 | * DefaultMethodHandler 50 | */ 51 | public void bindTo(Object proxy) { 52 | if (handle != null) { 53 | throw new IllegalStateException( 54 | "Attempted to rebind a default method handler that was already bound"); 55 | } 56 | handle = unboundHandle.bindTo(proxy); 57 | } 58 | 59 | /** 60 | * Invoke this method. DefaultMethodHandler#bindTo must be called before the first time invoke is 61 | * called. 62 | */ 63 | @Override 64 | public Object invoke(Object[] argv) throws Throwable { 65 | if (handle == null) { 66 | throw new IllegalStateException( 67 | "Default method handler invoked before proxy has been bound."); 68 | } 69 | return handle.invokeWithArguments(argv); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/methodhandler/FluxMethodHandler.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.methodhandler; 2 | 3 | import reactor.core.publisher.Flux; 4 | 5 | public class FluxMethodHandler implements MethodHandler { 6 | 7 | private final MethodHandler methodHandler; 8 | 9 | public FluxMethodHandler(MethodHandler methodHandler) { 10 | this.methodHandler = methodHandler; 11 | } 12 | 13 | @Override 14 | @SuppressWarnings("unchecked") 15 | public Flux invoke(final Object[] argv) { 16 | try { 17 | return (Flux)methodHandler.invoke(argv); 18 | } catch (Throwable throwable) { 19 | return Flux.error(throwable); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/methodhandler/MethodHandler.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.methodhandler; 2 | 3 | import feign.InvocationHandlerFactory; 4 | 5 | public interface MethodHandler extends InvocationHandlerFactory.MethodHandler{ } 6 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/methodhandler/MethodHandlerFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.methodhandler; 15 | 16 | import feign.MethodMetadata; 17 | import feign.Target; 18 | import reactivefeign.methodhandler.MethodHandler; 19 | 20 | import java.lang.reflect.Method; 21 | 22 | public interface MethodHandlerFactory { 23 | 24 | MethodHandler create(final Target target, final MethodMetadata metadata); 25 | 26 | MethodHandler createDefault(Method method); 27 | } 28 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/methodhandler/MonoMethodHandler.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.methodhandler; 2 | 3 | import reactor.core.publisher.Flux; 4 | import reactor.core.publisher.Mono; 5 | 6 | public class MonoMethodHandler implements MethodHandler { 7 | 8 | private final MethodHandler methodHandler; 9 | 10 | public MonoMethodHandler(MethodHandler methodHandler) { 11 | this.methodHandler = methodHandler; 12 | } 13 | 14 | @Override 15 | @SuppressWarnings("unchecked") 16 | public Mono invoke(final Object[] argv) { 17 | try { 18 | return (Mono)methodHandler.invoke(argv); 19 | } catch (Throwable throwable) { 20 | return Mono.error(throwable); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/methodhandler/ReactiveMethodHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.methodhandler; 2 | 3 | import feign.MethodMetadata; 4 | import feign.Target; 5 | import reactivefeign.publisher.*; 6 | import reactor.core.publisher.Flux; 7 | import reactor.core.publisher.Mono; 8 | 9 | import java.lang.reflect.Method; 10 | import java.lang.reflect.Type; 11 | 12 | import static feign.Util.checkNotNull; 13 | import static reactivefeign.utils.FeignUtils.returnPublisherType; 14 | 15 | public class ReactiveMethodHandlerFactory implements MethodHandlerFactory { 16 | 17 | private final PublisherClientFactory publisherClientFactory; 18 | 19 | public ReactiveMethodHandlerFactory(final PublisherClientFactory publisherClientFactory) { 20 | this.publisherClientFactory = checkNotNull(publisherClientFactory, "client must not be null"); 21 | } 22 | 23 | @Override 24 | public MethodHandler create(Target target, MethodMetadata metadata) { 25 | 26 | MethodHandler methodHandler = new PublisherClientMethodHandler( 27 | target, metadata, publisherClientFactory.apply(metadata)); 28 | 29 | Type returnPublisherType = returnPublisherType(metadata); 30 | if(returnPublisherType == Mono.class){ 31 | return new MonoMethodHandler(methodHandler); 32 | } else if(returnPublisherType == Flux.class) { 33 | return new FluxMethodHandler(methodHandler); 34 | } else { 35 | throw new IllegalArgumentException("Unknown returnPublisherType: " + returnPublisherType); 36 | } 37 | } 38 | 39 | @Override 40 | public MethodHandler createDefault(Method method) { 41 | MethodHandler defaultMethodHandler = new DefaultMethodHandler(method); 42 | 43 | if(method.getReturnType() == Mono.class){ 44 | return new MonoMethodHandler(defaultMethodHandler); 45 | } else if(method.getReturnType() == Flux.class) { 46 | return new FluxMethodHandler(defaultMethodHandler); 47 | } else { 48 | throw new IllegalArgumentException("Unknown returnPublisherType: " + method.getReturnType()); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/publisher/FluxPublisherHttpClient.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.publisher; 2 | 3 | 4 | import reactivefeign.client.ReactiveHttpClient; 5 | import reactivefeign.client.ReactiveHttpRequest; 6 | import reactivefeign.client.ReactiveHttpResponse; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | /** 11 | * Wraps {@link PublisherHttpClient} 12 | * 13 | * @author Sergii Karpenko 14 | */ 15 | public class FluxPublisherHttpClient implements PublisherHttpClient { 16 | 17 | private final ReactiveHttpClient reactiveHttpClient; 18 | 19 | public FluxPublisherHttpClient(ReactiveHttpClient reactiveHttpClient) { 20 | this.reactiveHttpClient = reactiveHttpClient; 21 | } 22 | 23 | @Override 24 | public Flux executeRequest(ReactiveHttpRequest request) { 25 | Mono response = reactiveHttpClient.executeRequest(request); 26 | return response.flatMapMany(ReactiveHttpResponse::body); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/publisher/FluxRetryPublisherHttpClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.publisher; 15 | 16 | import feign.MethodMetadata; 17 | import org.reactivestreams.Publisher; 18 | import reactivefeign.client.ReactiveHttpRequest; 19 | import reactor.core.publisher.Flux; 20 | import reactor.core.publisher.Mono; 21 | 22 | import java.util.function.Function; 23 | 24 | /** 25 | * Wraps {@link PublisherHttpClient} with retry logic provided by retryFunction 26 | * 27 | * @author Sergii Karpenko 28 | */ 29 | public class FluxRetryPublisherHttpClient extends RetryPublisherHttpClient { 30 | 31 | public FluxRetryPublisherHttpClient( 32 | FluxPublisherHttpClient publisherClient, MethodMetadata methodMetadata, 33 | Function, Flux> retryFunction) { 34 | super(publisherClient, methodMetadata, retryFunction); 35 | } 36 | 37 | @Override 38 | public Publisher executeRequest(ReactiveHttpRequest request) { 39 | Flux response = publisherClient.executeRequest(request); 40 | return response.retryWhen(retryFunction).onErrorMap(outOfRetries()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/publisher/MonoPublisherHttpClient.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.publisher; 2 | 3 | 4 | import org.reactivestreams.Publisher; 5 | import reactivefeign.client.ReactiveHttpClient; 6 | import reactivefeign.client.ReactiveHttpRequest; 7 | import reactivefeign.client.ReactiveHttpResponse; 8 | import reactor.core.publisher.Mono; 9 | 10 | /** 11 | * Wraps {@link PublisherHttpClient} 12 | * 13 | * @author Sergii Karpenko 14 | */ 15 | public class MonoPublisherHttpClient implements PublisherHttpClient { 16 | 17 | private final ReactiveHttpClient reactiveHttpClient; 18 | 19 | public MonoPublisherHttpClient(ReactiveHttpClient reactiveHttpClient) { 20 | this.reactiveHttpClient = reactiveHttpClient; 21 | } 22 | 23 | @Override 24 | public Mono executeRequest(ReactiveHttpRequest request) { 25 | Mono response = reactiveHttpClient.executeRequest(request); 26 | return response.flatMap(resp -> Mono.from(resp.body())); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/publisher/MonoRetryPublisherHttpClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.publisher; 15 | 16 | import feign.MethodMetadata; 17 | import org.reactivestreams.Publisher; 18 | import reactivefeign.client.ReactiveHttpRequest; 19 | import reactor.core.publisher.Flux; 20 | import reactor.core.publisher.Mono; 21 | 22 | import java.util.function.Function; 23 | 24 | /** 25 | * Wraps {@link PublisherHttpClient} with retry logic provided by retryFunction 26 | * 27 | * @author Sergii Karpenko 28 | */ 29 | public class MonoRetryPublisherHttpClient extends RetryPublisherHttpClient { 30 | 31 | public MonoRetryPublisherHttpClient( 32 | MonoPublisherHttpClient publisherClient, MethodMetadata methodMetadata, 33 | Function, Flux> retryFunction) { 34 | super(publisherClient, methodMetadata, retryFunction); 35 | } 36 | 37 | @Override 38 | public Publisher executeRequest(ReactiveHttpRequest request) { 39 | Mono response = publisherClient.executeRequest(request); 40 | return response.retryWhen(retryFunction).onErrorMap(outOfRetries()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/publisher/PublisherClientFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.publisher; 15 | 16 | import feign.MethodMetadata; 17 | 18 | import java.util.function.Function; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | 24 | public interface PublisherClientFactory extends Function { 25 | } 26 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/publisher/PublisherHttpClient.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.publisher; 2 | 3 | import org.reactivestreams.Publisher; 4 | import reactivefeign.client.ReactiveHttpRequest; 5 | 6 | import java.lang.reflect.Type; 7 | 8 | /** 9 | * @author Sergii Karpenko 10 | */ 11 | public interface PublisherHttpClient { 12 | 13 | Publisher executeRequest(ReactiveHttpRequest request); 14 | } 15 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/publisher/RetryPublisherHttpClient.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.publisher; 15 | 16 | import feign.MethodMetadata; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | import reactor.core.publisher.Flux; 20 | 21 | import java.util.function.Function; 22 | 23 | import static reactivefeign.utils.FeignUtils.methodTag; 24 | 25 | /** 26 | * Wraps {@link PublisherHttpClient} with retry logic provided by retryFunction 27 | * 28 | * @author Sergii Karpenko 29 | */ 30 | abstract public class RetryPublisherHttpClient

implements PublisherHttpClient { 31 | 32 | private static final Logger logger = LoggerFactory.getLogger(RetryPublisherHttpClient.class); 33 | 34 | private final String feignMethodTag; 35 | protected final P publisherClient; 36 | protected final Function, Flux> retryFunction; 37 | 38 | protected RetryPublisherHttpClient(P publisherClient, 39 | MethodMetadata methodMetadata, 40 | Function, Flux> retryFunction) { 41 | this.publisherClient = publisherClient; 42 | this.feignMethodTag = methodTag(methodMetadata); 43 | this.retryFunction = wrapWithLog(retryFunction, feignMethodTag); 44 | } 45 | 46 | protected Function outOfRetries() { 47 | return throwable -> { 48 | logger.debug("[{}]---> USED ALL RETRIES", feignMethodTag, throwable); 49 | return new OutOfRetriesException(throwable, feignMethodTag); 50 | }; 51 | } 52 | 53 | protected static Function, Flux> wrapWithLog( 54 | Function, Flux> retryFunction, 55 | String feignMethodTag) { 56 | return throwableFlux -> retryFunction.apply(throwableFlux) 57 | .doOnNext(throwable -> { 58 | if (logger.isDebugEnabled()) { 59 | logger.debug("[{}]---> RETRYING on error", feignMethodTag, throwable); 60 | } 61 | }); 62 | } 63 | 64 | public static class OutOfRetriesException extends Exception { 65 | OutOfRetriesException(Throwable cause, String feignMethodTag) { 66 | super("All retries used for: " + feignMethodTag, cause); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/utils/FeignUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.utils; 15 | 16 | import feign.MethodMetadata; 17 | import org.reactivestreams.Publisher; 18 | 19 | import java.lang.reflect.ParameterizedType; 20 | import java.lang.reflect.Type; 21 | 22 | import static feign.Util.resolveLastTypeParameter; 23 | import static java.util.Optional.ofNullable; 24 | 25 | public class FeignUtils { 26 | 27 | public static String methodTag(MethodMetadata methodMetadata) { 28 | return methodMetadata.configKey().substring(0, 29 | methodMetadata.configKey().indexOf('(')); 30 | } 31 | 32 | public static Class returnPublisherType(MethodMetadata methodMetadata) { 33 | final Type returnType = methodMetadata.returnType(); 34 | return (Class)((ParameterizedType) returnType).getRawType(); 35 | } 36 | 37 | public static Type returnActualType(MethodMetadata methodMetadata) { 38 | return resolveLastTypeParameter(methodMetadata.returnType(), returnPublisherType(methodMetadata)); 39 | } 40 | 41 | public static Type bodyActualType(MethodMetadata methodMetadata) { 42 | return getBodyActualType(methodMetadata.bodyType()); 43 | } 44 | 45 | public static Type getBodyActualType(Type bodyType) { 46 | return ofNullable(bodyType).map(type -> { 47 | if (type instanceof ParameterizedType) { 48 | Class bodyClass = (Class) ((ParameterizedType) type).getRawType(); 49 | if (Publisher.class.isAssignableFrom(bodyClass)) { 50 | return resolveLastTypeParameter(bodyType, bodyClass); 51 | } 52 | else { 53 | return type; 54 | } 55 | } 56 | else { 57 | return type; 58 | } 59 | }).orElse(null); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/utils/HttpUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.utils; 15 | 16 | import static reactivefeign.utils.HttpUtils.StatusCodeFamily.*; 17 | 18 | public class HttpUtils { 19 | 20 | public static StatusCodeFamily familyOf(final int statusCode) { 21 | switch (statusCode / 100) { 22 | case 1: 23 | return INFORMATIONAL; 24 | case 2: 25 | return SUCCESSFUL; 26 | case 3: 27 | return REDIRECTION; 28 | case 4: 29 | return CLIENT_ERROR; 30 | case 5: 31 | return SERVER_ERROR; 32 | default: 33 | return OTHER; 34 | } 35 | } 36 | 37 | public enum StatusCodeFamily { 38 | INFORMATIONAL(false), SUCCESSFUL(false), REDIRECTION(false), CLIENT_ERROR(true), SERVER_ERROR( 39 | true), OTHER(false); 40 | 41 | private final boolean error; 42 | 43 | StatusCodeFamily(boolean error) { 44 | this.error = error; 45 | } 46 | 47 | public boolean isError() { 48 | return error; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/utils/MultiValueMapUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.utils; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Collection; 18 | import java.util.List; 19 | import java.util.Map; 20 | 21 | public class MultiValueMapUtils { 22 | 23 | public static void addAllOrdered(Map> multiMap, K key, List values) { 24 | multiMap.compute(key, (key_, values_) -> { 25 | List valuesMerged = values_ != null ? values_ : new ArrayList<>(values.size()); 26 | valuesMerged.addAll(values); 27 | return valuesMerged; 28 | }); 29 | } 30 | 31 | public static void addOrdered(Map> multiMap, K key, V value) { 32 | multiMap.compute(key, (key_, values_) -> { 33 | List valuesMerged = values_ != null ? values_ : new ArrayList<>(1); 34 | valuesMerged.add(value); 35 | return valuesMerged; 36 | }); 37 | } 38 | 39 | public static void addAll(Map> multiMap, K key, Collection values) { 40 | multiMap.compute(key, (key_, values_) -> { 41 | Collection valuesMerged = values_ != null ? values_ : new ArrayList<>(values.size()); 42 | valuesMerged.addAll(values); 43 | return valuesMerged; 44 | }); 45 | } 46 | 47 | public static void add(Map> multiMap, K key, V value) { 48 | multiMap.compute(key, (key_, values_) -> { 49 | Collection valuesMerged = values_ != null ? values_ : new ArrayList<>(1); 50 | valuesMerged.add(value); 51 | return valuesMerged; 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /feign-reactor-core/src/main/java/reactivefeign/utils/Pair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.utils; 15 | 16 | public class Pair { 17 | public final L left; 18 | public final R right; 19 | 20 | public Pair(L left, R right) { 21 | this.left = left; 22 | this.right = right; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/CompressionTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import com.github.tomakehurst.wiremock.common.Gzip; 18 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule; 19 | import org.junit.ClassRule; 20 | import org.junit.Test; 21 | import reactivefeign.testcase.IcecreamServiceApi; 22 | import reactivefeign.testcase.domain.Bill; 23 | import reactivefeign.testcase.domain.IceCreamOrder; 24 | import reactivefeign.testcase.domain.OrderGenerator; 25 | import reactor.core.publisher.Mono; 26 | import reactor.test.StepVerifier; 27 | 28 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 29 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 30 | import static reactivefeign.TestUtils.equalsComparingFieldByFieldRecursively; 31 | 32 | /** 33 | * Test the new capability of Reactive Feign client to support both Feign Request.Options 34 | * (regression) and the new ReactiveOptions configuration. 35 | * 36 | * @author Sergii Karpenko 37 | */ 38 | 39 | abstract public class CompressionTest { 40 | 41 | @ClassRule 42 | public static WireMockClassRule wireMockRule = new WireMockClassRule( 43 | wireMockConfig().dynamicPort()); 44 | 45 | abstract protected ReactiveFeign.Builder builder(ReactiveOptions options); 46 | 47 | @Test 48 | public void testCompression() throws JsonProcessingException { 49 | 50 | IceCreamOrder order = new OrderGenerator().generate(20); 51 | Bill billExpected = Bill.makeBill(order); 52 | 53 | wireMockRule.stubFor(post(urlEqualTo("/icecream/orders")) 54 | .withHeader("Accept-Encoding", containing("gzip")) 55 | .withRequestBody(equalTo(TestUtils.MAPPER.writeValueAsString(order))) 56 | .willReturn(aResponse().withStatus(200) 57 | .withHeader("Content-Type", "application/json") 58 | .withHeader("Content-Encoding", "gzip") 59 | .withBody(Gzip.gzip(TestUtils.MAPPER.writeValueAsString(billExpected))))); 60 | 61 | IcecreamServiceApi client = builder( 62 | new ReactiveOptions.Builder() 63 | .setTryUseCompression(true) 64 | .build()) 65 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port()); 66 | 67 | Mono bill = client.makeOrder(order); 68 | StepVerifier.create(bill) 69 | .expectNextMatches(equalsComparingFieldByFieldRecursively(billExpected)) 70 | .verifyComplete(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/ConnectionTimeoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import org.hamcrest.Matchers; 17 | import org.junit.*; 18 | import org.junit.rules.ExpectedException; 19 | import reactivefeign.testcase.IcecreamServiceApi; 20 | 21 | import java.io.IOException; 22 | import java.net.ConnectException; 23 | import java.net.ServerSocket; 24 | import java.net.Socket; 25 | 26 | /** 27 | * @author Sergii Karpenko 28 | */ 29 | abstract public class ConnectionTimeoutTest { 30 | 31 | @Rule 32 | public ExpectedException expectedException = ExpectedException.none(); 33 | 34 | private ServerSocket serverSocket; 35 | private Socket socket; 36 | private int port; 37 | 38 | abstract protected ReactiveFeign.Builder builder(ReactiveOptions options); 39 | 40 | @Before 41 | public void before() throws IOException { 42 | // server socket with single element backlog queue (1) and dynamicaly allocated 43 | // port (0) 44 | serverSocket = new ServerSocket(0, 1); 45 | // just get the allocated port 46 | port = serverSocket.getLocalPort(); 47 | // fill backlog queue by this request so consequent requests will be blocked 48 | socket = new Socket(); 49 | socket.connect(serverSocket.getLocalSocketAddress()); 50 | } 51 | 52 | @After 53 | public void after() throws IOException { 54 | // some cleanup 55 | if (serverSocket != null && !serverSocket.isClosed()) { 56 | serverSocket.close(); 57 | } 58 | } 59 | 60 | // TODO investigate why doesn't work on codecov.io but works locally 61 | @Ignore 62 | @Test 63 | public void shouldFailOnConnectionTimeout() { 64 | 65 | expectedException.expectCause( 66 | 67 | Matchers.any(ConnectException.class)); 68 | 69 | IcecreamServiceApi client = builder( 70 | new ReactiveOptions.Builder() 71 | .setConnectTimeoutMillis(300) 72 | .setReadTimeoutMillis(100) 73 | .build()) 74 | .target(IcecreamServiceApi.class, "http://localhost:" + port); 75 | 76 | client.findOrder(1).block(); 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/ContractTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import org.junit.Rule; 17 | import org.junit.Test; 18 | import org.junit.rules.ExpectedException; 19 | import reactivefeign.testcase.IcecreamServiceApi; 20 | import reactivefeign.testcase.IcecreamServiceApiBroken; 21 | import reactivefeign.testcase.IcecreamServiceApiBrokenByCopy; 22 | 23 | import static org.hamcrest.Matchers.containsString; 24 | 25 | /** 26 | * @author Sergii Karpenko 27 | */ 28 | 29 | abstract public class ContractTest { 30 | 31 | @Rule 32 | public ExpectedException expectedException = ExpectedException.none(); 33 | 34 | abstract protected ReactiveFeign.Builder builder(); 35 | 36 | @Test 37 | public void shouldFailOnBrokenContract() { 38 | 39 | expectedException.expect(IllegalArgumentException.class); 40 | expectedException.expectMessage(containsString("Broken Contract")); 41 | 42 | this.builder() 43 | .contract(targetType -> { 44 | throw new IllegalArgumentException("Broken Contract"); 45 | }) 46 | .target(IcecreamServiceApi.class, "http://localhost:8888"); 47 | } 48 | 49 | @Test 50 | public void shouldFailIfNotReactiveContract() { 51 | 52 | expectedException.expect(IllegalArgumentException.class); 53 | expectedException.expectMessage(containsString("IcecreamServiceApiBroken#findOrderBlocking(int)")); 54 | 55 | this.builder() 56 | .target(IcecreamServiceApiBroken.class, "http://localhost:8888"); 57 | } 58 | 59 | @Test 60 | public void shouldFailIfMethodOperatesWithByteArray() { 61 | 62 | expectedException.expect(IllegalArgumentException.class); 63 | expectedException.expectMessage(containsString("IcecreamServiceApiBrokenByCopy#findOrderCopy(int)")); 64 | 65 | this.builder() 66 | .target(IcecreamServiceApiBrokenByCopy.class, "http://localhost:8888"); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/NotFoundTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule; 17 | import org.apache.http.HttpStatus; 18 | import org.junit.ClassRule; 19 | import org.junit.Test; 20 | import reactivefeign.testcase.IcecreamServiceApi; 21 | import reactor.test.StepVerifier; 22 | 23 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 24 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 25 | 26 | /** 27 | * @author Sergii Karpenko 28 | */ 29 | public abstract class NotFoundTest { 30 | 31 | @ClassRule 32 | public static WireMockClassRule wireMockRule = new WireMockClassRule( 33 | wireMockConfig().dynamicPort()); 34 | 35 | abstract protected ReactiveFeign.Builder builder(); 36 | 37 | @Test 38 | public void shouldReturnEmptyMono() { 39 | 40 | String orderUrl = "/icecream/orders/2"; 41 | wireMockRule.stubFor(get(urlEqualTo(orderUrl)) 42 | .withHeader("Accept", equalTo("application/json")) 43 | .willReturn(aResponse().withStatus(HttpStatus.SC_NOT_FOUND))); 44 | 45 | IcecreamServiceApi client = builder() 46 | .decode404() 47 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port()); 48 | 49 | StepVerifier.create(client.findOrder(2)) 50 | .expectNextCount(0) 51 | .verifyComplete(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/ReactivityTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule; 18 | import org.awaitility.Duration; 19 | import org.junit.ClassRule; 20 | import org.junit.Test; 21 | import reactivefeign.testcase.IcecreamServiceApi; 22 | import reactivefeign.testcase.domain.IceCreamOrder; 23 | import reactivefeign.testcase.domain.OrderGenerator; 24 | 25 | import java.util.concurrent.TimeUnit; 26 | import java.util.concurrent.atomic.AtomicInteger; 27 | 28 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 29 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 30 | import static org.awaitility.Awaitility.waitAtMost; 31 | 32 | /** 33 | * @author Sergii Karpenko 34 | */ 35 | abstract public class ReactivityTest { 36 | 37 | public static final int DELAY_IN_MILLIS = 500; 38 | public static final int CALLS_NUMBER = 500; 39 | public static final int REACTIVE_GAIN_RATIO = 10; 40 | @ClassRule 41 | public static WireMockClassRule wireMockRule = new WireMockClassRule( 42 | wireMockConfig() 43 | .asynchronousResponseEnabled(true) 44 | .dynamicPort()); 45 | 46 | abstract protected ReactiveFeign.Builder builder(); 47 | 48 | @Test 49 | public void shouldRunReactively() throws JsonProcessingException { 50 | 51 | IceCreamOrder orderGenerated = new OrderGenerator().generate(1); 52 | String orderStr = TestUtils.MAPPER.writeValueAsString(orderGenerated); 53 | 54 | wireMockRule.stubFor(get(urlEqualTo("/icecream/orders/1")) 55 | .withHeader("Accept", equalTo("application/json")) 56 | .willReturn(aResponse().withStatus(200) 57 | .withHeader("Content-Type", "application/json") 58 | .withBody(orderStr) 59 | .withFixedDelay(DELAY_IN_MILLIS))); 60 | 61 | IcecreamServiceApi client = builder() 62 | .target(IcecreamServiceApi.class, 63 | "http://localhost:" + wireMockRule.port()); 64 | 65 | AtomicInteger counter = new AtomicInteger(); 66 | 67 | new Thread(() -> { 68 | for (int i = 0; i < CALLS_NUMBER; i++) { 69 | client.findFirstOrder() 70 | .doOnNext(order -> counter.incrementAndGet()) 71 | .subscribe(); 72 | } 73 | }).start(); 74 | 75 | waitAtMost(new Duration(timeToCompleteReactively(), TimeUnit.MILLISECONDS)) 76 | .until(() -> counter.get() == CALLS_NUMBER); 77 | } 78 | 79 | public static int timeToCompleteReactively() { 80 | return CALLS_NUMBER * DELAY_IN_MILLIS / REACTIVE_GAIN_RATIO; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/ReadTimeoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule; 17 | import org.junit.ClassRule; 18 | import org.junit.Test; 19 | import reactivefeign.client.ReadTimeoutException; 20 | import reactivefeign.testcase.IcecreamServiceApi; 21 | import reactor.test.StepVerifier; 22 | 23 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 24 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 25 | 26 | /** 27 | * @author Sergii Karpenko 28 | */ 29 | abstract public class ReadTimeoutTest { 30 | 31 | @ClassRule 32 | public static WireMockClassRule wireMockRule = new WireMockClassRule( 33 | wireMockConfig().dynamicPort()); 34 | 35 | abstract protected ReactiveFeign.Builder builder(ReactiveOptions options); 36 | 37 | @Test 38 | public void shouldFailOnReadTimeout() { 39 | 40 | String orderUrl = "/icecream/orders/1"; 41 | 42 | wireMockRule.stubFor(get(urlEqualTo(orderUrl)) 43 | .withHeader("Accept", equalTo("application/json")) 44 | .willReturn(aResponse().withFixedDelay(200))); 45 | 46 | IcecreamServiceApi client = builder( 47 | new ReactiveOptions.Builder() 48 | .setConnectTimeoutMillis(300) 49 | .setReadTimeoutMillis(100) 50 | .build()) 51 | .target(IcecreamServiceApi.class, 52 | "http://localhost:" + wireMockRule.port()); 53 | 54 | StepVerifier.create(client.findOrder(1)) 55 | .expectError(ReadTimeoutException.class) 56 | .verify(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/RequestInterceptorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule; 18 | import feign.FeignException; 19 | import org.apache.http.HttpStatus; 20 | import org.junit.ClassRule; 21 | import org.junit.Test; 22 | import reactivefeign.testcase.IcecreamServiceApi; 23 | import reactivefeign.testcase.domain.IceCreamOrder; 24 | import reactivefeign.testcase.domain.OrderGenerator; 25 | import reactivefeign.utils.Pair; 26 | import reactor.test.StepVerifier; 27 | 28 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 29 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 30 | import static java.util.Collections.singletonList; 31 | import static reactivefeign.TestUtils.equalsComparingFieldByFieldRecursively; 32 | 33 | /** 34 | * @author Sergii Karpenko 35 | */ 36 | abstract public class RequestInterceptorTest { 37 | 38 | @ClassRule 39 | public static WireMockClassRule wireMockRule = new WireMockClassRule( 40 | wireMockConfig().dynamicPort()); 41 | 42 | abstract protected ReactiveFeign.Builder builder(); 43 | 44 | @Test 45 | public void shouldInterceptRequestAndSetAuthHeader() throws JsonProcessingException { 46 | 47 | String orderUrl = "/icecream/orders/1"; 48 | 49 | IceCreamOrder orderGenerated = new OrderGenerator().generate(1); 50 | String orderStr = TestUtils.MAPPER.writeValueAsString(orderGenerated); 51 | 52 | wireMockRule.stubFor(get(urlEqualTo(orderUrl)) 53 | .withHeader("Accept", equalTo("application/json")) 54 | .willReturn(aResponse().withStatus(HttpStatus.SC_UNAUTHORIZED))) 55 | .setPriority(100); 56 | 57 | wireMockRule.stubFor(get(urlEqualTo(orderUrl)) 58 | .withHeader("Accept", equalTo("application/json")) 59 | .withHeader("Authorization", equalTo("Bearer mytoken123")) 60 | .willReturn(aResponse().withStatus(200) 61 | .withHeader("Content-Type", "application/json") 62 | .withBody(orderStr))) 63 | .setPriority(1); 64 | 65 | IcecreamServiceApi clientWithoutAuth = builder() 66 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port()); 67 | 68 | StepVerifier.create(clientWithoutAuth.findFirstOrder()) 69 | .expectError(notAuthorizedException()) 70 | .verify(); 71 | 72 | IcecreamServiceApi clientWithAuth = builder() 73 | .addHeaders(singletonList(new Pair<>("Authorization", "Bearer mytoken123"))) 74 | .target(IcecreamServiceApi.class, 75 | "http://localhost:" + wireMockRule.port()); 76 | 77 | StepVerifier.create(clientWithAuth.findFirstOrder()) 78 | .expectNextMatches(equalsComparingFieldByFieldRecursively(orderGenerated)) 79 | .expectComplete(); 80 | } 81 | 82 | protected Class notAuthorizedException() { 83 | return FeignException.class; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/TestUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign; 15 | 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import com.fasterxml.jackson.databind.ObjectMapper; 18 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 19 | 20 | import java.util.function.Predicate; 21 | 22 | /** 23 | * Helper methods for tests. 24 | */ 25 | class TestUtils { 26 | static final ObjectMapper MAPPER; 27 | 28 | static { 29 | MAPPER = new ObjectMapper(); 30 | MAPPER.registerModule(new JavaTimeModule()); 31 | } 32 | 33 | public static Predicate equalsComparingFieldByFieldRecursively(T rhs) { 34 | return lhs -> { 35 | try { 36 | return MAPPER.writeValueAsString(lhs).equals(MAPPER.writeValueAsString(rhs)); 37 | } catch (JsonProcessingException e) { 38 | throw new RuntimeException(e); 39 | } 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/CompressionTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 19 | import reactivefeign.testcase.IcecreamServiceApi; 20 | 21 | /** 22 | * @author Sergii Karpenko 23 | */ 24 | public class CompressionTest extends reactivefeign.CompressionTest { 25 | 26 | @Override 27 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 28 | return RestTemplateFakeReactiveFeign.builder().options(options); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/ConnectionTimeoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 19 | import reactivefeign.testcase.IcecreamServiceApi; 20 | 21 | /** 22 | * @author Sergii Karpenko 23 | */ 24 | public class ConnectionTimeoutTest extends reactivefeign.ConnectionTimeoutTest { 25 | 26 | @Override 27 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 28 | return RestTemplateFakeReactiveFeign.builder().options(options); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/ContractTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public class ContractTest extends reactivefeign.ContractTest { 23 | 24 | @Override 25 | protected ReactiveFeign.Builder builder() { 26 | return RestTemplateFakeReactiveFeign.builder(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/DefaultMethodTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 19 | import reactivefeign.testcase.IcecreamServiceApi; 20 | 21 | /** 22 | * @author Sergii Karpenko 23 | */ 24 | public class DefaultMethodTest extends reactivefeign.DefaultMethodTest { 25 | 26 | @Override 27 | protected ReactiveFeign.Builder builder() { 28 | return RestTemplateFakeReactiveFeign.builder(); 29 | } 30 | 31 | @Override 32 | protected ReactiveFeign.Builder builder(Class apiClass) { 33 | return RestTemplateFakeReactiveFeign.builder(); 34 | } 35 | 36 | @Override 37 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 38 | return RestTemplateFakeReactiveFeign.builder().options(options); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/LoggerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class LoggerTest extends reactivefeign.LoggerTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder() { 27 | return RestTemplateFakeReactiveFeign.builder(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/NotFoundTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class NotFoundTest extends reactivefeign.NotFoundTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder() { 27 | return RestTemplateFakeReactiveFeign.builder(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/ReactivityTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import org.awaitility.core.ConditionTimeoutException; 18 | import org.junit.Before; 19 | import org.junit.Test; 20 | import reactivefeign.ReactiveFeign; 21 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 22 | import reactivefeign.testcase.IcecreamServiceApi; 23 | 24 | public class ReactivityTest extends reactivefeign.ReactivityTest { 25 | 26 | @Override 27 | protected ReactiveFeign.Builder builder() { 28 | return RestTemplateFakeReactiveFeign.builder(); 29 | } 30 | 31 | @Test(expected = ConditionTimeoutException.class) 32 | @Override 33 | public void shouldRunReactively() throws JsonProcessingException { 34 | super.shouldRunReactively(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/ReadTimeoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 19 | import reactivefeign.testcase.IcecreamServiceApi; 20 | 21 | /** 22 | * @author Sergii Karpenko 23 | */ 24 | public class ReadTimeoutTest extends reactivefeign.ReadTimeoutTest { 25 | 26 | @Override 27 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 28 | return RestTemplateFakeReactiveFeign.builder().options(options); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/RequestInterceptorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class RequestInterceptorTest extends reactivefeign.RequestInterceptorTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder() { 27 | return RestTemplateFakeReactiveFeign.builder(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/RetryingTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class RetryingTest extends reactivefeign.RetryingTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder() { 27 | return RestTemplateFakeReactiveFeign.builder(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/SmokeTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class SmokeTest extends reactivefeign.SmokeTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder() { 27 | return RestTemplateFakeReactiveFeign.builder(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/StatusHandlerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.resttemplate.client.RestTemplateFakeReactiveFeign; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class StatusHandlerTest extends reactivefeign.StatusHandlerTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder() { 27 | return RestTemplateFakeReactiveFeign.builder(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/resttemplate/client/RestTemplateFakeReactiveFeign.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.resttemplate.client; 15 | 16 | import org.apache.http.impl.client.HttpClientBuilder; 17 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; 18 | import org.springframework.web.client.RestTemplate; 19 | import reactivefeign.ReactiveFeign; 20 | import reactivefeign.ReactiveOptions; 21 | 22 | import static java.util.Optional.ofNullable; 23 | 24 | /** 25 | * {@link RestTemplate} based implementation 26 | * 27 | * @author Sergii Karpenko 28 | */ 29 | public class RestTemplateFakeReactiveFeign { 30 | 31 | public static ReactiveFeign.Builder builder() { 32 | return new ReactiveFeign.Builder(){ 33 | 34 | { 35 | clientFactory(methodMetadata -> new RestTemplateFakeReactiveHttpClient( 36 | methodMetadata, new RestTemplate(), false)); 37 | } 38 | 39 | @Override 40 | public ReactiveFeign.Builder options(ReactiveOptions options) { 41 | HttpComponentsClientHttpRequestFactory requestFactory = 42 | new HttpComponentsClientHttpRequestFactory( 43 | HttpClientBuilder.create().build()); 44 | if (options.getConnectTimeoutMillis() != null) { 45 | requestFactory.setConnectTimeout(options.getConnectTimeoutMillis().intValue()); 46 | } 47 | if (options.getReadTimeoutMillis() != null) { 48 | requestFactory.setReadTimeout(options.getReadTimeoutMillis().intValue()); 49 | } 50 | 51 | this.clientFactory(methodMetadata -> { 52 | boolean acceptGzip = ofNullable(options.isTryUseCompression()).orElse(false); 53 | return new RestTemplateFakeReactiveHttpClient( 54 | methodMetadata, new RestTemplate(requestFactory), acceptGzip); 55 | }); 56 | 57 | return this; 58 | } 59 | }; 60 | } 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/testcase/IcecreamServiceApi.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.testcase; 15 | 16 | import feign.Headers; 17 | import feign.Param; 18 | import feign.RequestLine; 19 | import reactivefeign.testcase.domain.Bill; 20 | import reactivefeign.testcase.domain.Flavor; 21 | import reactivefeign.testcase.domain.IceCreamOrder; 22 | import reactivefeign.testcase.domain.Mixin; 23 | import reactor.core.publisher.Flux; 24 | import reactor.core.publisher.Mono; 25 | 26 | /** 27 | * API of an iceream web service. 28 | * 29 | * @author Sergii Karpenko 30 | */ 31 | @Headers({"Accept: application/json"}) 32 | public interface IcecreamServiceApi { 33 | 34 | RuntimeException RUNTIME_EXCEPTION = new RuntimeException("tests exception"); 35 | 36 | @RequestLine("GET /icecream/flavors") 37 | Flux getAvailableFlavors(); 38 | 39 | @RequestLine("GET /icecream/mixins") 40 | Flux getAvailableMixins(); 41 | 42 | @RequestLine("POST /icecream/orders") 43 | @Headers("Content-Type: application/json") 44 | Mono makeOrder(IceCreamOrder order); 45 | 46 | @RequestLine("GET /icecream/orders/{orderId}") 47 | Mono findOrder(@Param("orderId") int orderId); 48 | 49 | @RequestLine("POST /icecream/bills/pay") 50 | @Headers("Content-Type: application/json") 51 | Mono payBill(Bill bill); 52 | 53 | default Mono findFirstOrder() { 54 | return findOrder(1); 55 | } 56 | 57 | default Mono throwsException() { 58 | throw RUNTIME_EXCEPTION; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/testcase/IcecreamServiceApiBroken.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.testcase; 15 | 16 | import feign.Headers; 17 | import feign.Param; 18 | import feign.RequestLine; 19 | import reactivefeign.ReactiveContract; 20 | import reactivefeign.testcase.domain.Bill; 21 | import reactivefeign.testcase.domain.Flavor; 22 | import reactivefeign.testcase.domain.IceCreamOrder; 23 | import reactivefeign.testcase.domain.Mixin; 24 | import reactor.core.publisher.Flux; 25 | import reactor.core.publisher.Mono; 26 | 27 | import java.util.Collection; 28 | 29 | /** 30 | * API of an iceream web service with one method that doesn't returns {@link Mono} or {@link Flux} 31 | * and violates {@link ReactiveContract}s rules. 32 | * 33 | * @author Sergii Karpenko 34 | */ 35 | public interface IcecreamServiceApiBroken { 36 | 37 | @RequestLine("GET /icecream/orders/{orderId}") 38 | Mono findOrder(@Param("orderId") int orderId); 39 | 40 | /** 41 | * Method that doesn't respects contract. 42 | */ 43 | @RequestLine("GET /icecream/orders/{orderId}") 44 | IceCreamOrder findOrderBlocking(@Param("orderId") int orderId); 45 | } 46 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/testcase/IcecreamServiceApiBrokenByCopy.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.testcase; 15 | 16 | import feign.Headers; 17 | import feign.Param; 18 | import feign.RequestLine; 19 | import reactivefeign.ReactiveContract; 20 | import reactivefeign.testcase.domain.Bill; 21 | import reactivefeign.testcase.domain.Flavor; 22 | import reactivefeign.testcase.domain.IceCreamOrder; 23 | import reactivefeign.testcase.domain.Mixin; 24 | import reactor.core.publisher.Flux; 25 | import reactor.core.publisher.Mono; 26 | 27 | import java.nio.ByteBuffer; 28 | import java.util.Collection; 29 | 30 | /** 31 | * API of an iceream web service with one method that returns {@link Mono} or {@link Flux} of byte array 32 | * and violates {@link ReactiveContract}s rules. 33 | * 34 | * @author Sergii Karpenko 35 | */ 36 | public interface IcecreamServiceApiBrokenByCopy { 37 | 38 | @RequestLine("GET /icecream/orders/{orderId}") 39 | Mono findOrder(@Param("orderId") int orderId); 40 | 41 | /** 42 | * Method that doesn't respects contract. 43 | */ 44 | @RequestLine("GET /icecream/orders/{orderId}") 45 | Mono findOrderCopy(@Param("orderId") int orderId); 46 | } 47 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/testcase/domain/Bill.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.testcase.domain; 15 | 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | /** 20 | * Bill for consumed ice cream. 21 | */ 22 | public class Bill { 23 | private static final Map PRICES = new HashMap<>(); 24 | 25 | static { 26 | PRICES.put(1, (float) 2.00); // two euros for one ball (expensive!) 27 | PRICES.put(3, (float) 2.85); // 2.85€ for 3 balls 28 | PRICES.put(5, (float) 4.30); // 4.30€ for 5 balls 29 | PRICES.put(7, (float) 5); // only five euros for seven balls! Wow 30 | } 31 | 32 | private static final float MIXIN_PRICE = (float) 0.6; // price per mixin 33 | 34 | private Float price; 35 | 36 | public Bill() {} 37 | 38 | public Bill(final Float price) { 39 | this.price = price; 40 | } 41 | 42 | public Float getPrice() { 43 | return price; 44 | } 45 | 46 | public void setPrice(final Float price) { 47 | this.price = price; 48 | } 49 | 50 | /** 51 | * Makes a bill from an order. 52 | * 53 | * @param order ice cream order 54 | * @return bill 55 | */ 56 | public static Bill makeBill(final IceCreamOrder order) { 57 | int nbBalls = order.getBalls().values().stream().mapToInt(Integer::intValue) 58 | .sum(); 59 | Float price = PRICES.get(nbBalls) + order.getMixins().size() * MIXIN_PRICE; 60 | return new Bill(price); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/testcase/domain/Flavor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.testcase.domain; 15 | 16 | /** 17 | * Ice cream flavors. 18 | */ 19 | public enum Flavor { 20 | STRAWBERRY, CHOCOLATE, BANANA, PISTACHIO, MELON, VANILLA 21 | } 22 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/testcase/domain/IceCreamOrder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.testcase.domain; 15 | 16 | import java.time.Instant; 17 | import java.util.*; 18 | 19 | /** 20 | * Give me some ice-cream! :p 21 | */ 22 | public class IceCreamOrder { 23 | private static Random random = new Random(); 24 | 25 | private int id; // order id 26 | private Map balls; // how much balls of flavor 27 | private Set mixins; // and some mixins ... 28 | private Instant orderTimestamp; // and give it to me right now ! 29 | 30 | IceCreamOrder() {} 31 | 32 | IceCreamOrder(int id) { 33 | this(id, Instant.now()); 34 | } 35 | 36 | IceCreamOrder(int id, final Instant orderTimestamp) { 37 | this.id = id; 38 | this.balls = new HashMap<>(); 39 | this.mixins = new HashSet<>(); 40 | this.orderTimestamp = orderTimestamp; 41 | } 42 | 43 | IceCreamOrder addBall(final Flavor ballFlavor) { 44 | final Integer ballCount = balls.containsKey(ballFlavor) 45 | ? balls.get(ballFlavor) + 1 46 | : 1; 47 | balls.put(ballFlavor, ballCount); 48 | return this; 49 | } 50 | 51 | IceCreamOrder addMixin(final Mixin mixin) { 52 | mixins.add(mixin); 53 | return this; 54 | } 55 | 56 | IceCreamOrder withOrderTimestamp(final Instant orderTimestamp) { 57 | this.orderTimestamp = orderTimestamp; 58 | return this; 59 | } 60 | 61 | public int getId() { 62 | return id; 63 | } 64 | 65 | public Map getBalls() { 66 | return balls; 67 | } 68 | 69 | public Set getMixins() { 70 | return mixins; 71 | } 72 | 73 | public Instant getOrderTimestamp() { 74 | return orderTimestamp; 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "IceCreamOrder{" + " id=" + id + ", balls=" + balls + ", mixins=" + mixins 80 | + ", orderTimestamp=" + orderTimestamp + '}'; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/testcase/domain/Mixin.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.testcase.domain; 15 | 16 | /** 17 | * Ice cream mix-ins. 18 | */ 19 | public enum Mixin { 20 | COOKIES, MNMS, CHOCOLATE_SIROP, STRAWBERRY_SIROP, NUTS, RAINBOW 21 | } 22 | -------------------------------------------------------------------------------- /feign-reactor-core/src/test/java/reactivefeign/testcase/domain/OrderGenerator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.testcase.domain; 15 | 16 | import java.time.Instant; 17 | import java.time.temporal.ChronoUnit; 18 | import java.util.Collection; 19 | import java.util.List; 20 | import java.util.Random; 21 | import java.util.stream.Collectors; 22 | import java.util.stream.IntStream; 23 | 24 | /** 25 | * Generator of random ice cream orders. 26 | */ 27 | public class OrderGenerator { 28 | private static final int[] BALLS_NUMBER = {1, 3, 5, 7}; 29 | private static final int[] MIXIN_NUMBER = {1, 2, 3}; 30 | 31 | private static final Random random = new Random(); 32 | 33 | public IceCreamOrder generate(int id) { 34 | final IceCreamOrder order = new IceCreamOrder(id); 35 | final int nbBalls = peekBallsNumber(); 36 | final int nbMixins = peekMixinNumber(); 37 | 38 | IntStream.rangeClosed(1, nbBalls).mapToObj(i -> this.peekFlavor()) 39 | .forEach(order::addBall); 40 | 41 | IntStream.rangeClosed(1, nbMixins).mapToObj(i -> this.peekMixin()) 42 | .forEach(order::addMixin); 43 | 44 | return order; 45 | } 46 | 47 | public Collection generateRange(int n) { 48 | Instant now = Instant.now(); 49 | 50 | List orderTimestamps = IntStream.range(0, n) 51 | .mapToObj(minutes -> now.minus(minutes, ChronoUnit.MINUTES)) 52 | .collect(Collectors.toList()); 53 | 54 | return IntStream.range(0, n) 55 | .mapToObj( 56 | i -> this.generate(i).withOrderTimestamp(orderTimestamps.get(i))) 57 | .collect(Collectors.toList()); 58 | } 59 | 60 | private int peekBallsNumber() { 61 | return BALLS_NUMBER[random.nextInt(BALLS_NUMBER.length)]; 62 | } 63 | 64 | private int peekMixinNumber() { 65 | return MIXIN_NUMBER[random.nextInt(MIXIN_NUMBER.length)]; 66 | } 67 | 68 | private Flavor peekFlavor() { 69 | return Flavor.values()[random.nextInt(Flavor.values().length)]; 70 | } 71 | 72 | private Mixin peekMixin() { 73 | return Mixin.values()[random.nextInt(Mixin.values().length)]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveFeign.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import com.fasterxml.jackson.core.async_.JsonFactory; 17 | import com.fasterxml.jackson.databind.ObjectMapper; 18 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 19 | import org.eclipse.jetty.client.HttpClient; 20 | import reactivefeign.ReactiveFeign; 21 | import reactivefeign.ReactiveOptions; 22 | import reactivefeign.jetty.client.JettyReactiveHttpClient; 23 | 24 | /** 25 | * Reactive Jetty client based implementation of reactive Feign 26 | * 27 | * @author Sergii Karpenko 28 | */ 29 | public class JettyReactiveFeign { 30 | 31 | public static Builder builder() { 32 | try { 33 | HttpClient httpClient = new HttpClient(); 34 | httpClient.start(); 35 | ObjectMapper objectMapper = new ObjectMapper(); 36 | objectMapper.registerModule(new JavaTimeModule()); 37 | return new Builder<>(httpClient, new JsonFactory(), objectMapper); 38 | } catch (Exception e) { 39 | throw new RuntimeException(e); 40 | } 41 | 42 | } 43 | 44 | public static Builder builder(HttpClient httpClient, JsonFactory jsonFactory, ObjectMapper objectMapper) { 45 | return new Builder<>(httpClient, jsonFactory, objectMapper); 46 | } 47 | 48 | public static class Builder extends ReactiveFeign.Builder { 49 | 50 | protected HttpClient httpClient; 51 | protected JsonFactory jsonFactory; 52 | private ObjectMapper objectMapper; 53 | protected ReactiveOptions options; 54 | 55 | protected Builder(HttpClient httpClient, JsonFactory jsonFactory, ObjectMapper objectMapper) { 56 | setHttpClient(httpClient, jsonFactory, objectMapper); 57 | this.jsonFactory = jsonFactory; 58 | this.objectMapper = objectMapper; 59 | } 60 | 61 | @Override 62 | public Builder options(ReactiveOptions options) { 63 | if (options.getConnectTimeoutMillis() != null) { 64 | httpClient.setConnectTimeout(options.getConnectTimeoutMillis()); 65 | } 66 | if (options.getReadTimeoutMillis() != null) { 67 | setHttpClient(httpClient, jsonFactory, objectMapper); 68 | } 69 | this.options = options; 70 | return this; 71 | } 72 | 73 | protected void setHttpClient(HttpClient httpClient, JsonFactory jsonFactory, ObjectMapper objectMapper){ 74 | this.httpClient = httpClient; 75 | clientFactory(methodMetadata -> { 76 | JettyReactiveHttpClient jettyClient = JettyReactiveHttpClient.jettyClient(methodMetadata, httpClient, jsonFactory, objectMapper); 77 | if (options != null && options.getReadTimeoutMillis() != null) { 78 | jettyClient.setRequestTimeout(options.getReadTimeoutMillis()); 79 | } 80 | return jettyClient; 81 | }); 82 | } 83 | } 84 | } 85 | 86 | 87 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/main/java/reactivefeign/jetty/utils/ProxyPostProcessor.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.jetty.utils; 2 | 3 | import org.eclipse.jetty.reactive.client.internal.AbstractSingleProcessor; 4 | import org.reactivestreams.Publisher; 5 | import org.reactivestreams.Subscriber; 6 | 7 | import java.util.function.BiConsumer; 8 | 9 | public class ProxyPostProcessor extends AbstractSingleProcessor{ 10 | 11 | private final Publisher publisher; 12 | private final BiConsumer postProcessor; 13 | 14 | private ProxyPostProcessor(Publisher publisher, BiConsumer postProcessor) { 15 | this.publisher = publisher; 16 | this.postProcessor = postProcessor; 17 | } 18 | 19 | @Override 20 | public void onNext(I i) { 21 | try { 22 | downStreamOnNext(i); 23 | postProcessor.accept(i, null); 24 | } catch (Throwable err) { 25 | postProcessor.accept(i, err); 26 | } 27 | } 28 | 29 | @Override 30 | public void subscribe(Subscriber s) { 31 | publisher.subscribe(this); 32 | super.subscribe(s); 33 | } 34 | 35 | public static Publisher postProcess(Publisher publisher, BiConsumer postProcessor){ 36 | return new ProxyPostProcessor<>(publisher, postProcessor); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/CompressionTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class CompressionTest extends reactivefeign.CompressionTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 27 | return JettyReactiveFeign.builder().options(options); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/ConnectionTimeoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class ConnectionTimeoutTest extends reactivefeign.ConnectionTimeoutTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 27 | return JettyReactiveFeign.builder().options(options); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/ContractTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | 18 | /** 19 | * @author Sergii Karpenko 20 | */ 21 | public class ContractTest extends reactivefeign.ContractTest { 22 | 23 | @Override 24 | protected ReactiveFeign.Builder builder() { 25 | return JettyReactiveFeign.builder(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/DefaultMethodTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class DefaultMethodTest extends reactivefeign.DefaultMethodTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder() { 27 | return JettyReactiveFeign.builder(); 28 | } 29 | 30 | @Override 31 | protected ReactiveFeign.Builder builder(Class apiClass) { 32 | return JettyReactiveFeign.builder(); 33 | } 34 | 35 | @Override 36 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 37 | return JettyReactiveFeign.builder().options(options); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/LoggerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.testcase.IcecreamServiceApi; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public class LoggerTest extends reactivefeign.LoggerTest { 23 | 24 | @Override 25 | protected ReactiveFeign.Builder builder() { 26 | return JettyReactiveFeign.builder(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/NotFoundTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import com.fasterxml.jackson.databind.ObjectMapper; 18 | import org.junit.Test; 19 | import reactivefeign.ReactiveFeign; 20 | import reactivefeign.testcase.IcecreamServiceApi; 21 | 22 | /** 23 | * @author Sergii Karpenko 24 | */ 25 | public class NotFoundTest extends reactivefeign.NotFoundTest { 26 | 27 | @Override 28 | protected ReactiveFeign.Builder builder() { 29 | return JettyReactiveFeign.builder(); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/ReactivityTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import reactivefeign.ReactiveFeign; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | public class ReactivityTest extends reactivefeign.ReactivityTest { 21 | 22 | @Override 23 | protected ReactiveFeign.Builder builder() { 24 | return JettyReactiveFeign.builder(); 25 | } 26 | 27 | @Override 28 | public void shouldRunReactively() throws JsonProcessingException { 29 | super.shouldRunReactively(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/ReadTimeoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class ReadTimeoutTest extends reactivefeign.ReadTimeoutTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 27 | return JettyReactiveFeign.builder().options(options); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/RequestInterceptorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.testcase.IcecreamServiceApi; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public class RequestInterceptorTest extends reactivefeign.RequestInterceptorTest { 23 | 24 | @Override 25 | protected ReactiveFeign.Builder builder() { 26 | return JettyReactiveFeign.builder(); 27 | } 28 | 29 | @Override 30 | protected Class notAuthorizedException() { 31 | return org.eclipse.jetty.client.HttpResponseException.class; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/RetryingTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import org.junit.Ignore; 17 | import org.junit.Test; 18 | import reactivefeign.ReactiveFeign; 19 | import reactivefeign.testcase.IcecreamServiceApi; 20 | 21 | /** 22 | * @author Sergii Karpenko 23 | */ 24 | public class RetryingTest extends reactivefeign.RetryingTest { 25 | 26 | @Override 27 | protected ReactiveFeign.Builder builder() { 28 | return JettyReactiveFeign.builder(); 29 | } 30 | 31 | @Test 32 | public void shouldFailAsNoMoreRetriesWithBackoff() { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/SmokeTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.testcase.IcecreamServiceApi; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public class SmokeTest extends reactivefeign.SmokeTest { 23 | 24 | @Override 25 | protected ReactiveFeign.Builder builder() { 26 | return JettyReactiveFeign.builder(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/StatusHandlerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.jetty; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.testcase.IcecreamServiceApi; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public class StatusHandlerTest extends reactivefeign.StatusHandlerTest { 23 | 24 | @Override 25 | protected ReactiveFeign.Builder builder() { 26 | return JettyReactiveFeign.builder(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/java/reactivefeign/jetty/allfeatures/AllFeaturesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package reactivefeign.jetty.allfeatures; 18 | 19 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 20 | import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; 21 | import reactivefeign.ReactiveFeign; 22 | import reactivefeign.jetty.JettyReactiveFeign; 23 | 24 | /** 25 | * @author Sergii Karpenko 26 | * 27 | * Tests ReactiveFeign in conjunction with WebFlux rest controller. 28 | */ 29 | @EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class}) 30 | public class AllFeaturesTest extends reactivefeign.allfeatures.AllFeaturesTest { 31 | 32 | @Override 33 | protected ReactiveFeign.Builder builder() { 34 | return JettyReactiveFeign.builder(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /feign-reactor-jetty/src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /feign-reactor-rx2/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.github.reactivefeign 7 | feign-reactor 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | feign-reactor-rx2 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | io.projectreactor.addons 20 | reactor-adapter 21 | 22 | 23 | 24 | io.reactivex.rxjava2 25 | rxjava 26 | 27 | 28 | 29 | io.github.reactivefeign 30 | feign-reactor-webclient 31 | 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-webflux 36 | 37 | 38 | spring-boot-starter-logging 39 | org.springframework.boot 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-test 48 | test 49 | 50 | 51 | io.projectreactor 52 | reactor-test 53 | test 54 | 55 | 56 | junit 57 | junit 58 | test 59 | 60 | 61 | 62 | org.assertj 63 | assertj-core 64 | test 65 | 66 | 67 | 68 | com.github.tomakehurst 69 | wiremock 70 | test 71 | 72 | 73 | 74 | org.awaitility 75 | awaitility 76 | test 77 | 78 | 79 | 80 | com.google.guava 81 | guava 82 | test 83 | 84 | 85 | 86 | org.apache.logging.log4j 87 | log4j-slf4j-impl 88 | test 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/main/java/reactivefeign/rx2/Rx2Contract.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2; 15 | 16 | import feign.Contract; 17 | import feign.MethodMetadata; 18 | import io.reactivex.Flowable; 19 | import io.reactivex.Maybe; 20 | import io.reactivex.Observable; 21 | import io.reactivex.Single; 22 | import reactor.core.publisher.Flux; 23 | import reactor.core.publisher.Mono; 24 | 25 | import java.lang.reflect.ParameterizedType; 26 | import java.lang.reflect.Type; 27 | import java.util.HashSet; 28 | import java.util.List; 29 | import java.util.Set; 30 | 31 | import static feign.Util.checkNotNull; 32 | import static java.util.Arrays.asList; 33 | 34 | /** 35 | * Contract allowing only {@link Mono} and {@link Flux} return type. 36 | * 37 | * @author Sergii Karpenko 38 | */ 39 | public class Rx2Contract implements Contract { 40 | 41 | public static final Set RX2_TYPES = new HashSet<>(asList( 42 | Flowable.class, Observable.class, Single.class, Maybe.class)); 43 | 44 | private final Contract delegate; 45 | 46 | public Rx2Contract(final Contract delegate) { 47 | this.delegate = checkNotNull(delegate, "delegate must not be null"); 48 | } 49 | 50 | @Override 51 | public List parseAndValidatateMetadata(final Class targetType) { 52 | final List methodsMetadata = 53 | this.delegate.parseAndValidatateMetadata(targetType); 54 | 55 | for (final MethodMetadata metadata : methodsMetadata) { 56 | final Type type = metadata.returnType(); 57 | if (!isRx2Type(type)) { 58 | throw new IllegalArgumentException(String.format( 59 | "Method %s of contract %s doesn't returns rx2 types", 60 | metadata.configKey(), targetType.getSimpleName())); 61 | } 62 | } 63 | 64 | return methodsMetadata; 65 | } 66 | 67 | private boolean isRx2Type(final Type type) { 68 | return (type instanceof ParameterizedType) 69 | && RX2_TYPES.contains(((ParameterizedType) type).getRawType()); 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/main/java/reactivefeign/rx2/client/statushandler/Rx2ReactiveStatusHandler.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.rx2.client.statushandler; 2 | 3 | import reactivefeign.client.ReactiveHttpResponse; 4 | import reactivefeign.client.statushandler.ReactiveStatusHandler; 5 | import reactor.core.publisher.Mono; 6 | 7 | import static reactor.adapter.rxjava.RxJava2Adapter.singleToMono; 8 | 9 | public class Rx2ReactiveStatusHandler implements ReactiveStatusHandler { 10 | 11 | private final Rx2StatusHandler statusHandler; 12 | 13 | public Rx2ReactiveStatusHandler(Rx2StatusHandler statusHandler) { 14 | this.statusHandler = statusHandler; 15 | } 16 | 17 | @Override 18 | public boolean shouldHandle(int status) { 19 | return statusHandler.shouldHandle(status); 20 | } 21 | 22 | @Override 23 | public Mono decode(String methodKey, ReactiveHttpResponse response) { 24 | return singleToMono(statusHandler.decode(methodKey, response)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/main/java/reactivefeign/rx2/client/statushandler/Rx2StatusHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2.client.statushandler; 15 | 16 | import io.reactivex.Single; 17 | import reactivefeign.client.ReactiveHttpResponse; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public interface Rx2StatusHandler { 23 | 24 | boolean shouldHandle(int status); 25 | 26 | Single decode(String methodKey, ReactiveHttpResponse response); 27 | } 28 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/main/java/reactivefeign/rx2/client/statushandler/Rx2StatusHandlers.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2.client.statushandler; 15 | 16 | import io.reactivex.Single; 17 | import reactivefeign.client.ReactiveHttpResponse; 18 | 19 | import java.util.function.BiFunction; 20 | import java.util.function.Predicate; 21 | 22 | public class Rx2StatusHandlers { 23 | 24 | 25 | public static Rx2StatusHandler throwOnStatus( 26 | Predicate statusPredicate, 27 | BiFunction errorFunction) { 28 | return new Rx2StatusHandler() { 29 | @Override 30 | public boolean shouldHandle(int status) { 31 | return statusPredicate.test(status); 32 | } 33 | 34 | @Override 35 | public Single decode(String methodKey, ReactiveHttpResponse response) { 36 | return Single.just(errorFunction.apply(methodKey, response)); 37 | } 38 | }; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/main/java/reactivefeign/rx2/methodhandler/Rx2MethodHandler.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.rx2.methodhandler; 2 | 3 | import io.reactivex.Flowable; 4 | import io.reactivex.Maybe; 5 | import io.reactivex.Observable; 6 | import io.reactivex.Single; 7 | import org.reactivestreams.Publisher; 8 | import reactivefeign.methodhandler.MethodHandler; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | import java.lang.reflect.Type; 13 | import static reactor.adapter.rxjava.RxJava2Adapter.*; 14 | 15 | public class Rx2MethodHandler implements MethodHandler { 16 | 17 | private final MethodHandler methodHandler; 18 | private final Type returnPublisherType; 19 | 20 | public Rx2MethodHandler(MethodHandler methodHandler, Type returnPublisherType) { 21 | this.methodHandler = methodHandler; 22 | this.returnPublisherType = returnPublisherType; 23 | } 24 | 25 | @Override 26 | @SuppressWarnings("unchecked") 27 | public Object invoke(final Object[] argv) { 28 | try { 29 | Publisher publisher = (Publisher)methodHandler.invoke(argv); 30 | if(returnPublisherType == Flowable.class){ 31 | return fluxToFlowable((Flux) publisher); 32 | } else if(returnPublisherType == Observable.class){ 33 | return fluxToObservable((Flux) publisher); 34 | } else if(returnPublisherType == Single.class){ 35 | return monoToSingle((Mono) publisher); 36 | } else if(returnPublisherType == Maybe.class){ 37 | return monoToMaybe((Mono) publisher); 38 | } else { 39 | throw new IllegalArgumentException("Unexpected returnPublisherType="+returnPublisherType.getClass()); 40 | } 41 | } catch (Throwable throwable) { 42 | if(returnPublisherType == Flowable.class){ 43 | return Flowable.error(throwable); 44 | } else if(returnPublisherType == Observable.class){ 45 | return Observable.error(throwable); 46 | } else if(returnPublisherType == Single.class){ 47 | return Single.error(throwable); 48 | } else if(returnPublisherType == Maybe.class){ 49 | return Maybe.error(throwable); 50 | } else { 51 | throw new IllegalArgumentException("Unexpected returnPublisherType="+returnPublisherType.getClass()); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/main/java/reactivefeign/rx2/methodhandler/Rx2MethodHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.rx2.methodhandler; 2 | 3 | import feign.MethodMetadata; 4 | import feign.Target; 5 | import io.reactivex.BackpressureStrategy; 6 | import reactivefeign.methodhandler.DefaultMethodHandler; 7 | import reactivefeign.methodhandler.MethodHandler; 8 | import reactivefeign.methodhandler.MethodHandlerFactory; 9 | import reactivefeign.publisher.PublisherClientFactory; 10 | 11 | import java.lang.reflect.Method; 12 | 13 | import static reactivefeign.utils.FeignUtils.returnPublisherType; 14 | 15 | public class Rx2MethodHandlerFactory implements MethodHandlerFactory { 16 | 17 | private final PublisherClientFactory publisherClientFactory; 18 | private final BackpressureStrategy backpressureStrategy; 19 | 20 | public Rx2MethodHandlerFactory(PublisherClientFactory publisherClientFactory, 21 | BackpressureStrategy backpressureStrategy) { 22 | this.publisherClientFactory = publisherClientFactory; 23 | this.backpressureStrategy = backpressureStrategy; 24 | } 25 | 26 | @Override 27 | public MethodHandler create(final Target target, final MethodMetadata metadata) { 28 | MethodHandler methodHandler = new Rx2PublisherClientMethodHandler( 29 | target, metadata, publisherClientFactory.apply(metadata), 30 | backpressureStrategy); 31 | 32 | return new Rx2MethodHandler(methodHandler, returnPublisherType(metadata)); 33 | } 34 | 35 | @Override 36 | public MethodHandler createDefault(Method method) { 37 | return new DefaultMethodHandler(method); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/main/java/reactivefeign/rx2/methodhandler/Rx2PublisherClientMethodHandler.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.rx2.methodhandler; 2 | 3 | import feign.MethodMetadata; 4 | import feign.Target; 5 | import io.reactivex.*; 6 | import org.reactivestreams.Publisher; 7 | import reactivefeign.methodhandler.PublisherClientMethodHandler; 8 | import reactivefeign.publisher.PublisherHttpClient; 9 | import reactor.core.publisher.Mono; 10 | 11 | import static reactor.adapter.rxjava.RxJava2Adapter.*; 12 | 13 | public class Rx2PublisherClientMethodHandler extends PublisherClientMethodHandler { 14 | 15 | private final BackpressureStrategy backpressureStrategy; 16 | 17 | public Rx2PublisherClientMethodHandler( 18 | Target target, MethodMetadata methodMetadata, 19 | PublisherHttpClient publisherClient, BackpressureStrategy backpressureStrategy) { 20 | super(target, methodMetadata, publisherClient); 21 | this.backpressureStrategy = backpressureStrategy; 22 | } 23 | 24 | @Override 25 | protected Publisher body(Object body) { 26 | if (body instanceof Flowable) { 27 | return flowableToFlux((Flowable) body); 28 | } else if (body instanceof Observable) { 29 | return observableToFlux((Observable) body, backpressureStrategy); 30 | } else if (body instanceof Single) { 31 | return singleToMono((Single) body); 32 | } else if (body instanceof Maybe) { 33 | return maybeToMono((Maybe) body); 34 | } else { 35 | return Mono.just(body); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/ContractTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2; 15 | 16 | import org.junit.Rule; 17 | import org.junit.Test; 18 | import org.junit.rules.ExpectedException; 19 | import reactivefeign.ReactiveFeign; 20 | import reactivefeign.rx2.testcase.IcecreamServiceApi; 21 | import reactivefeign.rx2.testcase.IcecreamServiceApiBroken; 22 | 23 | import static org.hamcrest.Matchers.containsString; 24 | 25 | /** 26 | * @author Sergii Karpenko 27 | */ 28 | 29 | public class ContractTest { 30 | 31 | @Rule 32 | public ExpectedException expectedException = ExpectedException.none(); 33 | 34 | protected ReactiveFeign.Builder builder(){ 35 | return Rx2ReactiveFeign.builder(); 36 | } 37 | 38 | @Test 39 | public void shouldFailOnBrokenContract() { 40 | 41 | expectedException.expect(IllegalArgumentException.class); 42 | expectedException.expectMessage(containsString("Broken Contract")); 43 | 44 | this.builder() 45 | .contract(targetType -> { 46 | throw new IllegalArgumentException("Broken Contract"); 47 | }) 48 | .target(IcecreamServiceApi.class, "http://localhost:8888"); 49 | } 50 | 51 | @Test 52 | public void shouldFailIfNotReactiveContract() { 53 | 54 | expectedException.expect(IllegalArgumentException.class); 55 | expectedException.expectMessage(containsString("IcecreamServiceApiBroken#findOrder(int)")); 56 | 57 | this.builder() 58 | .target(IcecreamServiceApiBroken.class, "http://localhost:8888"); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/NotFoundTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2; 15 | 16 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule; 17 | import org.apache.http.HttpStatus; 18 | import org.junit.ClassRule; 19 | import org.junit.Test; 20 | import reactivefeign.ReactiveFeign; 21 | import reactivefeign.rx2.testcase.IcecreamServiceApi; 22 | 23 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 24 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 25 | 26 | /** 27 | * @author Sergii Karpenko 28 | */ 29 | public class NotFoundTest { 30 | 31 | @ClassRule 32 | public static WireMockClassRule wireMockRule = new WireMockClassRule( 33 | wireMockConfig().dynamicPort()); 34 | 35 | protected ReactiveFeign.Builder builder(){ 36 | return Rx2ReactiveFeign.builder(); 37 | } 38 | 39 | @Test 40 | public void shouldReturnEmptyMono() throws InterruptedException { 41 | 42 | String orderUrl = "/icecream/orders/2"; 43 | wireMockRule.stubFor(get(urlEqualTo(orderUrl)) 44 | .withHeader("Accept", equalTo("application/json")) 45 | .willReturn(aResponse().withStatus(HttpStatus.SC_NOT_FOUND))); 46 | 47 | IcecreamServiceApi client = builder() 48 | .decode404() 49 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port()); 50 | 51 | client.findOrder(2).test() 52 | .await() 53 | .assertSubscribed() 54 | .assertNoValues() 55 | .assertNoErrors() 56 | .assertComplete(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/ReactivityTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2; 15 | 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule; 18 | import org.awaitility.Duration; 19 | import org.junit.ClassRule; 20 | import org.junit.Test; 21 | import reactivefeign.ReactiveFeign; 22 | import reactivefeign.rx2.testcase.IcecreamServiceApi; 23 | import reactivefeign.rx2.testcase.domain.IceCreamOrder; 24 | import reactivefeign.rx2.testcase.domain.OrderGenerator; 25 | 26 | import java.util.concurrent.TimeUnit; 27 | import java.util.concurrent.atomic.AtomicInteger; 28 | 29 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 30 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 31 | import static org.awaitility.Awaitility.waitAtMost; 32 | 33 | /** 34 | * @author Sergii Karpenko 35 | */ 36 | public class ReactivityTest { 37 | 38 | public static final int DELAY_IN_MILLIS = 500; 39 | public static final int CALLS_NUMBER = 100; 40 | public static final int REACTIVE_GAIN_RATIO = 10; 41 | @ClassRule 42 | public static WireMockClassRule wireMockRule = new WireMockClassRule( 43 | wireMockConfig() 44 | .asynchronousResponseEnabled(true) 45 | .dynamicPort()); 46 | 47 | protected ReactiveFeign.Builder builder(){ 48 | return Rx2ReactiveFeign.builder(); 49 | } 50 | 51 | @Test 52 | public void shouldRunReactively() throws JsonProcessingException { 53 | 54 | IceCreamOrder orderGenerated = new OrderGenerator().generate(1); 55 | String orderStr = TestUtils.MAPPER.writeValueAsString(orderGenerated); 56 | 57 | wireMockRule.stubFor(get(urlEqualTo("/icecream/orders/1")) 58 | .withHeader("Accept", equalTo("application/json")) 59 | .willReturn(aResponse().withStatus(200) 60 | .withHeader("Content-Type", "application/json") 61 | .withBody(orderStr) 62 | .withFixedDelay(DELAY_IN_MILLIS))); 63 | 64 | IcecreamServiceApi client = builder() 65 | .target(IcecreamServiceApi.class, 66 | "http://localhost:" + wireMockRule.port()); 67 | 68 | AtomicInteger counter = new AtomicInteger(); 69 | 70 | new Thread(() -> { 71 | for (int i = 0; i < CALLS_NUMBER; i++) { 72 | client.findFirstOrder() 73 | .doOnSuccess(order -> counter.incrementAndGet()) 74 | .subscribe(); 75 | } 76 | }).start(); 77 | 78 | int timeToCompleteReactively = CALLS_NUMBER * DELAY_IN_MILLIS / REACTIVE_GAIN_RATIO; 79 | waitAtMost(new Duration(timeToCompleteReactively, TimeUnit.MILLISECONDS)) 80 | .until(() -> counter.get() == CALLS_NUMBER); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/ReadTimeoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2; 15 | 16 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule; 17 | import org.junit.ClassRule; 18 | import org.junit.Test; 19 | import reactivefeign.ReactiveFeign; 20 | import reactivefeign.ReactiveOptions; 21 | import reactivefeign.client.ReadTimeoutException; 22 | import reactivefeign.rx2.testcase.IcecreamServiceApi; 23 | 24 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 25 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 26 | 27 | /** 28 | * @author Sergii Karpenko 29 | */ 30 | public class ReadTimeoutTest { 31 | 32 | @ClassRule 33 | public static WireMockClassRule wireMockRule = new WireMockClassRule( 34 | wireMockConfig().dynamicPort()); 35 | 36 | protected ReactiveFeign.Builder builder(ReactiveOptions options){ 37 | return Rx2ReactiveFeign.builder().options(options); 38 | } 39 | 40 | @Test 41 | public void shouldFailOnReadTimeout() throws InterruptedException { 42 | 43 | String orderUrl = "/icecream/orders/1"; 44 | 45 | wireMockRule.stubFor(get(urlEqualTo(orderUrl)) 46 | .withHeader("Accept", equalTo("application/json")) 47 | .willReturn(aResponse().withFixedDelay(200))); 48 | 49 | IcecreamServiceApi client = builder( 50 | new ReactiveOptions.Builder() 51 | .setConnectTimeoutMillis(300) 52 | .setReadTimeoutMillis(100) 53 | .build()) 54 | .target(IcecreamServiceApi.class, 55 | "http://localhost:" + wireMockRule.port()); 56 | 57 | client.findOrder(1).test() 58 | .await() 59 | .assertSubscribed() 60 | .assertError(ReadTimeoutException.class); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/RequestInterceptorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2; 15 | 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule; 18 | import feign.FeignException; 19 | import org.apache.http.HttpStatus; 20 | import org.junit.ClassRule; 21 | import org.junit.Test; 22 | import reactivefeign.ReactiveFeign; 23 | import reactivefeign.rx2.testcase.IcecreamServiceApi; 24 | import reactivefeign.rx2.testcase.domain.IceCreamOrder; 25 | import reactivefeign.rx2.testcase.domain.OrderGenerator; 26 | import reactivefeign.utils.Pair; 27 | 28 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 29 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 30 | import static java.util.Collections.singletonList; 31 | import static reactivefeign.rx2.TestUtils.equalsComparingFieldByFieldRecursivelyRx; 32 | 33 | /** 34 | * @author Sergii Karpenko 35 | */ 36 | public class RequestInterceptorTest { 37 | 38 | @ClassRule 39 | public static WireMockClassRule wireMockRule = new WireMockClassRule( 40 | wireMockConfig().dynamicPort()); 41 | 42 | protected ReactiveFeign.Builder builder(){ 43 | return Rx2ReactiveFeign.builder(); 44 | } 45 | 46 | @Test 47 | public void shouldInterceptRequestAndSetAuthHeader() throws JsonProcessingException, InterruptedException { 48 | 49 | String orderUrl = "/icecream/orders/1"; 50 | 51 | IceCreamOrder orderGenerated = new OrderGenerator().generate(1); 52 | String orderStr = TestUtils.MAPPER.writeValueAsString(orderGenerated); 53 | 54 | wireMockRule.stubFor(get(urlEqualTo(orderUrl)) 55 | .withHeader("Accept", equalTo("application/json")) 56 | .willReturn(aResponse().withStatus(HttpStatus.SC_UNAUTHORIZED))) 57 | .setPriority(100); 58 | 59 | wireMockRule.stubFor(get(urlEqualTo(orderUrl)) 60 | .withHeader("Accept", equalTo("application/json")) 61 | .withHeader("Authorization", equalTo("Bearer mytoken123")) 62 | .willReturn(aResponse().withStatus(200) 63 | .withHeader("Content-Type", "application/json") 64 | .withBody(orderStr))) 65 | .setPriority(1); 66 | 67 | IcecreamServiceApi clientWithoutAuth = builder() 68 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port()); 69 | 70 | clientWithoutAuth.findFirstOrder().test() 71 | .await() 72 | .assertSubscribed() 73 | .assertError(FeignException.class); 74 | 75 | IcecreamServiceApi clientWithAuth = builder() 76 | .addHeaders(singletonList(new Pair<>("Authorization", "Bearer mytoken123"))) 77 | .target(IcecreamServiceApi.class, 78 | "http://localhost:" + wireMockRule.port()); 79 | 80 | clientWithAuth.findFirstOrder().test() 81 | .await() 82 | .assertSubscribed() 83 | .assertValue(equalsComparingFieldByFieldRecursivelyRx(orderGenerated)) 84 | .assertNoErrors() 85 | .assertComplete(); 86 | 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/StatusHandlerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2; 15 | 16 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule; 17 | import feign.RetryableException; 18 | import org.apache.http.HttpStatus; 19 | import org.junit.Before; 20 | import org.junit.ClassRule; 21 | import org.junit.Test; 22 | import reactivefeign.rx2.testcase.IcecreamServiceApi; 23 | 24 | import static com.github.tomakehurst.wiremock.client.WireMock.*; 25 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; 26 | import static reactivefeign.rx2.client.statushandler.Rx2StatusHandlers.throwOnStatus; 27 | 28 | /** 29 | * @author Sergii Karpenko 30 | */ 31 | public class StatusHandlerTest { 32 | 33 | @ClassRule 34 | public static WireMockClassRule wireMockRule = new WireMockClassRule( 35 | wireMockConfig().dynamicPort()); 36 | 37 | protected Rx2ReactiveFeign.Builder builder(){ 38 | return Rx2ReactiveFeign.builder(); 39 | } 40 | 41 | @Before 42 | public void resetServers() { 43 | wireMockRule.resetAll(); 44 | } 45 | 46 | @Test 47 | public void shouldThrowRetryException() throws InterruptedException { 48 | 49 | wireMockRule.stubFor(get(urlEqualTo("/icecream/orders/1")) 50 | .withHeader("Accept", equalTo("application/json")) 51 | .willReturn(aResponse().withStatus(HttpStatus.SC_SERVICE_UNAVAILABLE))); 52 | IcecreamServiceApi client = builder() 53 | .statusHandler(throwOnStatus( 54 | status -> status == HttpStatus.SC_SERVICE_UNAVAILABLE, 55 | (methodTag, response) -> new RetryableException("Should retry on next node", null))) 56 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port()); 57 | 58 | client.findFirstOrder().test() 59 | .await() 60 | .assertError(RetryableException.class); 61 | } 62 | 63 | @Test 64 | public void shouldThrowOnStatusCode() throws InterruptedException { 65 | 66 | wireMockRule.stubFor(get(urlEqualTo("/icecream/orders/2")) 67 | .withHeader("Accept", equalTo("application/json")) 68 | .willReturn(aResponse().withStatus(HttpStatus.SC_UNAUTHORIZED))); 69 | 70 | 71 | IcecreamServiceApi client = builder() 72 | .statusHandler( 73 | throwOnStatus( 74 | status -> status == HttpStatus.SC_UNAUTHORIZED, 75 | (methodTag, response) -> new RuntimeException("Should login", null))) 76 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port()); 77 | 78 | client.findOrder(2).test() 79 | .await() 80 | .assertError(RuntimeException.class); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/TestUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2; 15 | 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import com.fasterxml.jackson.databind.ObjectMapper; 18 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 19 | 20 | import java.util.function.Predicate; 21 | 22 | 23 | /** 24 | * Helper methods for tests. 25 | */ 26 | class TestUtils { 27 | static final ObjectMapper MAPPER; 28 | 29 | static { 30 | MAPPER = new ObjectMapper(); 31 | MAPPER.registerModule(new JavaTimeModule()); 32 | } 33 | 34 | public static Predicate equalsComparingFieldByFieldRecursively(T rhs) { 35 | return lhs -> { 36 | try { 37 | return MAPPER.writeValueAsString(lhs).equals(MAPPER.writeValueAsString(rhs)); 38 | } catch (JsonProcessingException e) { 39 | throw new RuntimeException(e); 40 | } 41 | }; 42 | } 43 | 44 | public static io.reactivex.functions.Predicate equalsComparingFieldByFieldRecursivelyRx(T rhs) { 45 | return lhs -> { 46 | try { 47 | return MAPPER.writeValueAsString(lhs).equals(MAPPER.writeValueAsString(rhs)); 48 | } catch (JsonProcessingException e) { 49 | throw new RuntimeException(e); 50 | } 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/testcase/IcecreamServiceApi.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2.testcase; 15 | 16 | import feign.Headers; 17 | import feign.Param; 18 | import feign.RequestLine; 19 | import io.reactivex.Flowable; 20 | import io.reactivex.Maybe; 21 | import io.reactivex.Observable; 22 | import io.reactivex.Single; 23 | import reactivefeign.rx2.testcase.domain.Bill; 24 | import reactivefeign.rx2.testcase.domain.Flavor; 25 | import reactivefeign.rx2.testcase.domain.IceCreamOrder; 26 | import reactivefeign.rx2.testcase.domain.Mixin; 27 | 28 | /** 29 | * API of an iceream web service. 30 | * 31 | * @author Sergii Karpenko 32 | */ 33 | @Headers({"Accept: application/json"}) 34 | public interface IcecreamServiceApi { 35 | 36 | RuntimeException RUNTIME_EXCEPTION = new RuntimeException("tests exception"); 37 | 38 | @RequestLine("GET /icecream/flavors") 39 | Flowable getAvailableFlavors(); 40 | 41 | @RequestLine("GET /icecream/mixins") 42 | Observable getAvailableMixins(); 43 | 44 | @RequestLine("POST /icecream/orders") 45 | @Headers("Content-Type: application/json") 46 | Single makeOrder(IceCreamOrder order); 47 | 48 | @RequestLine("GET /icecream/orders/{orderId}") 49 | Maybe findOrder(@Param("orderId") int orderId); 50 | 51 | @RequestLine("POST /icecream/bills/pay") 52 | @Headers("Content-Type: application/json") 53 | Single payBill(Bill bill); 54 | 55 | default Maybe findFirstOrder() { 56 | return findOrder(1); 57 | } 58 | 59 | default Single throwsException() { 60 | throw RUNTIME_EXCEPTION; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/testcase/IcecreamServiceApiBroken.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2.testcase; 15 | 16 | import feign.Headers; 17 | import feign.Param; 18 | import feign.RequestLine; 19 | import io.reactivex.Flowable; 20 | import io.reactivex.Single; 21 | import reactivefeign.ReactiveContract; 22 | import reactivefeign.rx2.testcase.domain.Bill; 23 | import reactivefeign.rx2.testcase.domain.Flavor; 24 | import reactivefeign.rx2.testcase.domain.IceCreamOrder; 25 | import reactivefeign.rx2.testcase.domain.Mixin; 26 | import reactor.core.publisher.Flux; 27 | import reactor.core.publisher.Mono; 28 | 29 | import java.util.Collection; 30 | 31 | /** 32 | * API of an iceream web service with one method that doesn't returns {@link Mono} or {@link Flux} 33 | * and violates {@link ReactiveContract}s rules. 34 | * 35 | * @author Sergii Karpenko 36 | */ 37 | public interface IcecreamServiceApiBroken { 38 | 39 | @RequestLine("GET /icecream/flavors") 40 | Single> getAvailableFlavors(); 41 | 42 | @RequestLine("GET /icecream/mixins") 43 | Flowable getAvailableMixins(); 44 | 45 | /** 46 | * Method that doesn't respects contract. 47 | */ 48 | @RequestLine("GET /icecream/orders/{orderId}") 49 | IceCreamOrder findOrder(@Param("orderId") int orderId); 50 | } 51 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/testcase/domain/Bill.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2.testcase.domain; 15 | 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | /** 20 | * Bill for consumed ice cream. 21 | */ 22 | public class Bill { 23 | private static final Map PRICES = new HashMap<>(); 24 | 25 | static { 26 | PRICES.put(1, (float) 2.00); // two euros for one ball (expensive!) 27 | PRICES.put(3, (float) 2.85); // 2.85€ for 3 balls 28 | PRICES.put(5, (float) 4.30); // 4.30€ for 5 balls 29 | PRICES.put(7, (float) 5); // only five euros for seven balls! Wow 30 | } 31 | 32 | private static final float MIXIN_PRICE = (float) 0.6; // price per mixin 33 | 34 | private Float price; 35 | 36 | public Bill() {} 37 | 38 | public Bill(final Float price) { 39 | this.price = price; 40 | } 41 | 42 | public Float getPrice() { 43 | return price; 44 | } 45 | 46 | public void setPrice(final Float price) { 47 | this.price = price; 48 | } 49 | 50 | /** 51 | * Makes a bill from an order. 52 | * 53 | * @param order ice cream order 54 | * @return bill 55 | */ 56 | public static Bill makeBill(final IceCreamOrder order) { 57 | int nbBalls = order.getBalls().values().stream().mapToInt(Integer::intValue) 58 | .sum(); 59 | Float price = PRICES.get(nbBalls) + order.getMixins().size() * MIXIN_PRICE; 60 | return new Bill(price); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/testcase/domain/Flavor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2.testcase.domain; 15 | 16 | /** 17 | * Ice cream flavors. 18 | */ 19 | public enum Flavor { 20 | STRAWBERRY, CHOCOLATE, BANANA, PISTACHIO, MELON, VANILLA 21 | } 22 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/testcase/domain/IceCreamOrder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2.testcase.domain; 15 | 16 | import java.time.Instant; 17 | import java.util.*; 18 | 19 | /** 20 | * Give me some ice-cream! :p 21 | */ 22 | public class IceCreamOrder { 23 | private static Random random = new Random(); 24 | 25 | private int id; // order id 26 | private Map balls; // how much balls of flavor 27 | private Set mixins; // and some mixins ... 28 | private Instant orderTimestamp; // and give it to me right now ! 29 | 30 | IceCreamOrder() {} 31 | 32 | IceCreamOrder(int id) { 33 | this(id, Instant.now()); 34 | } 35 | 36 | IceCreamOrder(int id, final Instant orderTimestamp) { 37 | this.id = id; 38 | this.balls = new HashMap<>(); 39 | this.mixins = new HashSet<>(); 40 | this.orderTimestamp = orderTimestamp; 41 | } 42 | 43 | IceCreamOrder addBall(final Flavor ballFlavor) { 44 | final Integer ballCount = balls.containsKey(ballFlavor) 45 | ? balls.get(ballFlavor) + 1 46 | : 1; 47 | balls.put(ballFlavor, ballCount); 48 | return this; 49 | } 50 | 51 | IceCreamOrder addMixin(final Mixin mixin) { 52 | mixins.add(mixin); 53 | return this; 54 | } 55 | 56 | IceCreamOrder withOrderTimestamp(final Instant orderTimestamp) { 57 | this.orderTimestamp = orderTimestamp; 58 | return this; 59 | } 60 | 61 | public int getId() { 62 | return id; 63 | } 64 | 65 | public Map getBalls() { 66 | return balls; 67 | } 68 | 69 | public Set getMixins() { 70 | return mixins; 71 | } 72 | 73 | public Instant getOrderTimestamp() { 74 | return orderTimestamp; 75 | } 76 | 77 | @Override 78 | public String toString() { 79 | return "IceCreamOrder{" + " id=" + id + ", balls=" + balls + ", mixins=" + mixins 80 | + ", orderTimestamp=" + orderTimestamp + '}'; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/testcase/domain/Mixin.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2.testcase.domain; 15 | 16 | /** 17 | * Ice cream mix-ins. 18 | */ 19 | public enum Mixin { 20 | COOKIES, MNMS, CHOCOLATE_SIROP, STRAWBERRY_SIROP, NUTS, RAINBOW 21 | } 22 | -------------------------------------------------------------------------------- /feign-reactor-rx2/src/test/java/reactivefeign/rx2/testcase/domain/OrderGenerator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.rx2.testcase.domain; 15 | 16 | import java.time.Instant; 17 | import java.time.temporal.ChronoUnit; 18 | import java.util.Collection; 19 | import java.util.List; 20 | import java.util.Random; 21 | import java.util.stream.Collectors; 22 | import java.util.stream.IntStream; 23 | 24 | /** 25 | * Generator of random ice cream orders. 26 | */ 27 | public class OrderGenerator { 28 | private static final int[] BALLS_NUMBER = {1, 3, 5, 7}; 29 | private static final int[] MIXIN_NUMBER = {1, 2, 3}; 30 | 31 | private static final Random random = new Random(); 32 | 33 | public IceCreamOrder generate(int id) { 34 | final IceCreamOrder order = new IceCreamOrder(id); 35 | final int nbBalls = peekBallsNumber(); 36 | final int nbMixins = peekMixinNumber(); 37 | 38 | IntStream.rangeClosed(1, nbBalls).mapToObj(i -> this.peekFlavor()) 39 | .forEach(order::addBall); 40 | 41 | IntStream.rangeClosed(1, nbMixins).mapToObj(i -> this.peekMixin()) 42 | .forEach(order::addMixin); 43 | 44 | return order; 45 | } 46 | 47 | public Collection generateRange(int n) { 48 | Instant now = Instant.now(); 49 | 50 | List orderTimestamps = IntStream.range(0, n) 51 | .mapToObj(minutes -> now.minus(minutes, ChronoUnit.MINUTES)) 52 | .collect(Collectors.toList()); 53 | 54 | return IntStream.range(0, n) 55 | .mapToObj( 56 | i -> this.generate(i).withOrderTimestamp(orderTimestamps.get(i))) 57 | .collect(Collectors.toList()); 58 | } 59 | 60 | private int peekBallsNumber() { 61 | return BALLS_NUMBER[random.nextInt(BALLS_NUMBER.length)]; 62 | } 63 | 64 | private int peekMixinNumber() { 65 | return MIXIN_NUMBER[random.nextInt(MIXIN_NUMBER.length)]; 66 | } 67 | 68 | private Flavor peekFlavor() { 69 | return Flavor.values()[random.nextInt(Flavor.values().length)]; 70 | } 71 | 72 | private Mixin peekMixin() { 73 | return Mixin.values()[random.nextInt(Mixin.values().length)]; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /feign-reactor-webclient/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | io.github.reactivefeign 7 | feign-reactor 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | feign-reactor-webclient 12 | 13 | 14 | 15 | io.github.reactivefeign 16 | feign-reactor-core 17 | 18 | 19 | 20 | org.springframework 21 | spring-webflux 22 | 23 | 24 | 25 | org.springframework 26 | spring-web 27 | 28 | 29 | 30 | io.projectreactor.ipc 31 | reactor-netty 32 | 33 | 34 | 35 | io.netty 36 | netty-all 37 | 38 | 39 | 40 | 41 | io.github.reactivefeign 42 | feign-reactor-core 43 | 1.0.0-SNAPSHOT 44 | test-jar 45 | test 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-webflux 50 | 51 | 52 | spring-boot-starter-logging 53 | org.springframework.boot 54 | 55 | 56 | test 57 | 58 | 59 | org.springframework.boot 60 | spring-boot-starter-test 61 | test 62 | 63 | 64 | io.projectreactor 65 | reactor-test 66 | test 67 | 68 | 69 | com.github.tomakehurst 70 | wiremock 71 | test 72 | 73 | 74 | 75 | org.apache.logging.log4j 76 | log4j-slf4j-impl 77 | test 78 | 79 | 80 | 81 | org.awaitility 82 | awaitility 83 | test 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/main/java/reactivefeign/webclient/client/WebReactiveHttpResponse.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.webclient.client; 2 | 3 | import org.reactivestreams.Publisher; 4 | import org.springframework.core.ParameterizedTypeReference; 5 | import org.springframework.core.io.ByteArrayResource; 6 | import org.springframework.web.reactive.function.client.ClientResponse; 7 | import reactivefeign.client.ReactiveHttpResponse; 8 | import reactor.core.publisher.Flux; 9 | import reactor.core.publisher.Mono; 10 | 11 | import java.lang.reflect.Type; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | class WebReactiveHttpResponse implements ReactiveHttpResponse{ 16 | 17 | private final ClientResponse clientResponse; 18 | private final Type returnPublisherType; 19 | private final ParameterizedTypeReference returnActualType; 20 | 21 | WebReactiveHttpResponse(ClientResponse clientResponse, 22 | Type returnPublisherType, ParameterizedTypeReference returnActualType) { 23 | this.clientResponse = clientResponse; 24 | this.returnPublisherType = returnPublisherType; 25 | this.returnActualType = returnActualType; 26 | } 27 | 28 | @Override 29 | public int status() { 30 | return clientResponse.statusCode().value(); 31 | } 32 | 33 | @Override 34 | public Map> headers() { 35 | return clientResponse.headers().asHttpHeaders(); 36 | } 37 | 38 | @Override 39 | public Publisher body() { 40 | if (returnPublisherType == Mono.class) { 41 | return clientResponse.bodyToMono(returnActualType); 42 | } else if(returnPublisherType == Flux.class){ 43 | return clientResponse.bodyToFlux(returnActualType); 44 | } else { 45 | throw new IllegalArgumentException("Unknown returnPublisherType: " + returnPublisherType); 46 | } 47 | } 48 | 49 | @Override 50 | public Mono bodyData() { 51 | return clientResponse.bodyToMono(ByteArrayResource.class) 52 | .map(ByteArrayResource::getByteArray) 53 | .defaultIfEmpty(new byte[0]); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/CompressionTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class CompressionTest extends reactivefeign.CompressionTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 27 | return WebReactiveFeign.builder().options(options); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/ConnectionTimeoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class ConnectionTimeoutTest extends reactivefeign.ConnectionTimeoutTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 27 | return WebReactiveFeign.builder().options(options); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/ContractTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | 18 | /** 19 | * @author Sergii Karpenko 20 | */ 21 | public class ContractTest extends reactivefeign.ContractTest { 22 | 23 | @Override 24 | protected ReactiveFeign.Builder builder() { 25 | return WebReactiveFeign.builder(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/DefaultMethodTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class DefaultMethodTest extends reactivefeign.DefaultMethodTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder() { 27 | return WebReactiveFeign.builder(); 28 | } 29 | 30 | @Override 31 | protected ReactiveFeign.Builder builder(Class apiClass) { 32 | return WebReactiveFeign.builder(); 33 | } 34 | 35 | @Override 36 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 37 | return WebReactiveFeign.builder().options(options); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/LoggerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.testcase.IcecreamServiceApi; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public class LoggerTest extends reactivefeign.LoggerTest { 23 | 24 | @Override 25 | protected ReactiveFeign.Builder builder() { 26 | return WebReactiveFeign.builder(); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/NotFoundTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.testcase.IcecreamServiceApi; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public class NotFoundTest extends reactivefeign.NotFoundTest { 23 | 24 | @Override 25 | protected ReactiveFeign.Builder builder() { 26 | return WebReactiveFeign.builder(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/ReactivityTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import com.fasterxml.jackson.core.JsonProcessingException; 17 | import reactivefeign.ReactiveFeign; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | public class ReactivityTest extends reactivefeign.ReactivityTest { 21 | 22 | @Override 23 | protected ReactiveFeign.Builder builder() { 24 | return WebReactiveFeign.builder(); 25 | } 26 | 27 | @Override 28 | public void shouldRunReactively() throws JsonProcessingException { 29 | super.shouldRunReactively(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/ReadTimeoutTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.ReactiveOptions; 18 | import reactivefeign.testcase.IcecreamServiceApi; 19 | 20 | /** 21 | * @author Sergii Karpenko 22 | */ 23 | public class ReadTimeoutTest extends reactivefeign.ReadTimeoutTest { 24 | 25 | @Override 26 | protected ReactiveFeign.Builder builder(ReactiveOptions options) { 27 | return WebReactiveFeign.builder().options(options); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/RequestInterceptorTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.testcase.IcecreamServiceApi; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public class RequestInterceptorTest extends reactivefeign.RequestInterceptorTest { 23 | 24 | @Override 25 | protected ReactiveFeign.Builder builder() { 26 | return WebReactiveFeign.builder(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/RetryingTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.testcase.IcecreamServiceApi; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public class RetryingTest extends reactivefeign.RetryingTest { 23 | 24 | @Override 25 | protected ReactiveFeign.Builder builder() { 26 | return WebReactiveFeign.builder(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/SmokeTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.testcase.IcecreamServiceApi; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public class SmokeTest extends reactivefeign.SmokeTest { 23 | 24 | @Override 25 | protected ReactiveFeign.Builder builder() { 26 | return WebReactiveFeign.builder(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/StatusHandlerTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 The Feign Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 | * in compliance with the License. You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under the License 10 | * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 | * or implied. See the License for the specific language governing permissions and limitations under 12 | * the License. 13 | */ 14 | package reactivefeign.webclient; 15 | 16 | import reactivefeign.ReactiveFeign; 17 | import reactivefeign.testcase.IcecreamServiceApi; 18 | 19 | /** 20 | * @author Sergii Karpenko 21 | */ 22 | public class StatusHandlerTest extends reactivefeign.StatusHandlerTest { 23 | 24 | @Override 25 | protected ReactiveFeign.Builder builder() { 26 | return WebReactiveFeign.builder(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/AllFeaturesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013-2015 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package reactivefeign.webclient.allfeatures; 18 | 19 | import org.junit.Ignore; 20 | import org.junit.Test; 21 | import org.junit.runner.RunWith; 22 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 23 | import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; 24 | import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; 25 | import org.springframework.boot.test.context.SpringBootTest; 26 | import org.springframework.test.context.junit4.SpringRunner; 27 | import reactivefeign.ReactiveFeign; 28 | import reactivefeign.allfeatures.AllFeaturesController; 29 | import reactivefeign.webclient.WebReactiveFeign; 30 | 31 | /** 32 | * @author Sergii Karpenko 33 | * 34 | * Tests ReactiveFeign in conjunction with WebFlux rest controller. 35 | */ 36 | @EnableAutoConfiguration(exclude = {ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class}) 37 | public class AllFeaturesTest extends reactivefeign.allfeatures.AllFeaturesTest { 38 | 39 | @Override 40 | protected ReactiveFeign.Builder builder() { 41 | return WebReactiveFeign.builder(); 42 | } 43 | 44 | //Netty's WebClient is not able to do this trick 45 | @Ignore 46 | @Test 47 | @Override 48 | public void shouldReturnFirstResultBeforeSecondSent() { 49 | } 50 | 51 | //WebClient is not able to do this 52 | @Ignore 53 | @Test 54 | @Override 55 | public void shouldMirrorStringStreamBody() { 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesApi.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.webclient.allfeatures; 2 | 3 | import feign.Headers; 4 | import feign.RequestLine; 5 | import org.reactivestreams.Publisher; 6 | import org.springframework.core.io.Resource; 7 | import org.springframework.core.io.buffer.DataBuffer; 8 | import reactor.core.publisher.Flux; 9 | 10 | import java.nio.ByteBuffer; 11 | 12 | import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE; 13 | 14 | public interface WebClientFeaturesApi { 15 | 16 | @RequestLine("POST " + "/mirrorStreamingBinaryBodyReactive") 17 | @Headers({ "Content-Type: "+APPLICATION_OCTET_STREAM_VALUE }) 18 | Flux mirrorStreamingBinaryBodyReactive(Publisher body); 19 | 20 | @RequestLine("POST " + "/mirrorResourceReactiveWithZeroCopying") 21 | @Headers({ "Content-Type: "+APPLICATION_OCTET_STREAM_VALUE }) 22 | Flux mirrorResourceReactiveWithZeroCopying(Resource resource); 23 | } 24 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesController.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.webclient.allfeatures; 2 | 3 | import org.reactivestreams.Publisher; 4 | import org.springframework.core.io.Resource; 5 | import org.springframework.core.io.buffer.DataBuffer; 6 | import org.springframework.core.io.buffer.DataBufferUtils; 7 | import org.springframework.core.io.buffer.DefaultDataBufferFactory; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import reactor.core.publisher.Flux; 12 | 13 | @RestController 14 | public class WebClientFeaturesController implements WebClientFeaturesApi{ 15 | 16 | @PostMapping(path = "/mirrorStreamingBinaryBodyReactive") 17 | @Override 18 | public Flux mirrorStreamingBinaryBodyReactive(@RequestBody Publisher body) { 19 | return Flux.from(body); 20 | } 21 | 22 | @PostMapping(path = "/mirrorResourceReactiveWithZeroCopying") 23 | @Override 24 | public Flux mirrorResourceReactiveWithZeroCopying(@RequestBody Resource resource) { 25 | return DataBufferUtils.read(resource, new DefaultDataBufferFactory(), 3); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/java/reactivefeign/webclient/allfeatures/WebClientFeaturesTest.java: -------------------------------------------------------------------------------- 1 | package reactivefeign.webclient.allfeatures; 2 | 3 | 4 | import org.junit.Before; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.rules.ExpectedException; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 10 | import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.boot.web.server.LocalServerPort; 13 | import org.springframework.core.io.ByteArrayResource; 14 | import org.springframework.core.io.buffer.DataBuffer; 15 | import org.springframework.core.io.buffer.DataBufferUtils; 16 | import org.springframework.core.io.buffer.DefaultDataBufferFactory; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | import reactivefeign.webclient.WebReactiveFeign; 19 | import reactor.core.publisher.Flux; 20 | import reactor.test.StepVerifier; 21 | 22 | import static java.nio.ByteBuffer.wrap; 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | @RunWith(SpringRunner.class) 26 | @SpringBootTest( 27 | properties = {"spring.main.web-application-type=reactive"}, 28 | classes = {WebClientFeaturesController.class }, 29 | webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 30 | @EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class}) 31 | public class WebClientFeaturesTest { 32 | 33 | private WebClientFeaturesApi client; 34 | 35 | @LocalServerPort 36 | private int port; 37 | 38 | @Rule 39 | public ExpectedException expectedException = ExpectedException.none(); 40 | 41 | @Before 42 | public void setUp() { 43 | client = WebReactiveFeign.builder() 44 | .decode404() 45 | .target(WebClientFeaturesApi.class, "http://localhost:" + port); 46 | } 47 | 48 | @Test 49 | public void shouldMirrorStreamingBinaryBodyReactive() { 50 | 51 | Flux returned = client 52 | .mirrorStreamingBinaryBodyReactive(Flux.just( 53 | fromByteArray(new byte[]{1,2,3}), 54 | fromByteArray(new byte[]{4,5,6}))); 55 | 56 | StepVerifier.create(returned) 57 | .expectNextMatches(dataBuffer -> dataBuffer.asByteBuffer().equals(wrap(new byte[]{1,2,3}))) 58 | .expectNextMatches(dataBuffer -> dataBuffer.asByteBuffer().equals(wrap(new byte[]{4,5,6}))) 59 | .verifyComplete(); 60 | } 61 | 62 | private static DataBuffer fromByteArray(byte[] data){ 63 | return new DefaultDataBufferFactory().wrap(data); 64 | } 65 | 66 | @Test 67 | public void shouldMirrorResourceReactiveWithZeroCopying(){ 68 | byte[] data = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 69 | ByteArrayResource resource = new ByteArrayResource(data); 70 | Flux returned = client.mirrorResourceReactiveWithZeroCopying(resource); 71 | assertThat(DataBufferUtils.join(returned).block().asByteBuffer()).isEqualTo(wrap(data)); 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /feign-reactor-webclient/src/test/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /settings.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | bintray-kptfh-feign-reactive 8 | kptfh 9 | 4b4f2162048b2d8f2950ebd29fd604232bf5b2e4 10 | 11 | 12 | --------------------------------------------------------------------------------