requestInterceptor(ReactiveHttpRequestInterceptor requestInterceptor) {
136 | this.requestInterceptor = requestInterceptor;
137 | return this;
138 | }
139 |
140 | /**
141 | * This flag indicates that the reactive feign client should process responses with 404 status,
142 | * specifically returning empty {@link Mono} or {@link Flux} instead of throwing
143 | * {@link FeignException}.
144 | *
145 | *
146 | * This flag only works with 404, as opposed to all or arbitrary status codes. This was an
147 | * explicit decision: 404 - empty is safe, common and doesn't complicate redirection, retry or
148 | * fallback policy.
149 | *
150 | * @return this builder
151 | */
152 | public Builder decode404() {
153 | this.decode404 = true;
154 | return this;
155 | }
156 |
157 | public Builder statusHandler(ReactiveStatusHandler statusHandler) {
158 | this.statusHandler = statusHandler;
159 | return this;
160 | }
161 |
162 | /**
163 | * The most common way to introduce custom logic on handling http response
164 | *
165 | * @param responseMapper
166 | * @return
167 | */
168 | public Builder responseMapper(BiFunction responseMapper) {
169 | this.responseMapper = responseMapper;
170 | return this;
171 | }
172 |
173 | public Builder retryWhen(
174 | Function, Flux> retryFunction) {
175 | this.retryFunction = retryFunction;
176 | return this;
177 | }
178 |
179 | public Builder retryWhen(ReactiveRetryPolicy retryPolicy) {
180 | return retryWhen(retryPolicy.toRetryFunction());
181 | }
182 |
183 | /**
184 | * Defines target and builds client.
185 | *
186 | * @param apiType API interface
187 | * @param url base URL
188 | * @return built client
189 | */
190 | public T target(final Class apiType, final String url) {
191 | return target(new Target.HardCodedTarget<>(apiType, url));
192 | }
193 |
194 | /**
195 | * Defines target and builds client.
196 | *
197 | * @param target target instance
198 | * @return built client
199 | */
200 | public T target(final Target target) {
201 | this.target = target;
202 | return build().newInstance(target);
203 | }
204 |
205 | protected ReactiveFeign build() {
206 | final ParseHandlersByName handlersByName = new ParseHandlersByName(
207 | contract, buildReactiveMethodHandlerFactory());
208 | return new ReactiveFeign(handlersByName, invocationHandlerFactory);
209 | }
210 |
211 | protected ReactiveMethodHandlerFactory buildReactiveMethodHandlerFactory() {
212 | return new ReactiveClientMethodHandler.Factory(buildReactiveClientFactory());
213 | }
214 |
215 | protected ReactiveClientFactory buildReactiveClientFactory() {
216 | return methodMetadata -> {
217 |
218 | checkNotNull(clientFactory,
219 | "clientFactory wasn't provided in ReactiveFeign builder");
220 |
221 | ReactiveHttpClient reactiveClient = clientFactory.apply(methodMetadata);
222 |
223 | if (requestInterceptor != null) {
224 | reactiveClient = intercept(reactiveClient, requestInterceptor);
225 | }
226 |
227 | reactiveClient = log(reactiveClient, methodMetadata);
228 |
229 | if (responseMapper != null) {
230 | reactiveClient = mapResponse(reactiveClient, methodMetadata, responseMapper);
231 | }
232 |
233 | if (decode404) {
234 | reactiveClient = mapResponse(reactiveClient, methodMetadata, ignore404());
235 | }
236 |
237 | if (statusHandler != null) {
238 | reactiveClient = handleStatus(reactiveClient, methodMetadata, statusHandler);
239 | }
240 |
241 | if (retryFunction != null) {
242 | reactiveClient = retry(reactiveClient, methodMetadata, retryFunction);
243 | }
244 |
245 | return reactiveClient;
246 | };
247 | }
248 | }
249 |
250 | public static final class ParseHandlersByName {
251 | private final Contract contract;
252 | private final ReactiveMethodHandlerFactory factory;
253 |
254 | ParseHandlersByName(final Contract contract,
255 | final ReactiveMethodHandlerFactory factory) {
256 | this.contract = contract;
257 | this.factory = factory;
258 | }
259 |
260 | Map apply(final Target target) {
261 | final List metadata = contract
262 | .parseAndValidatateMetadata(target.type());
263 | final Map result = new LinkedHashMap<>();
264 |
265 | for (final MethodMetadata md : metadata) {
266 | ReactiveMethodHandler methodHandler = factory.create(target, md);
267 | result.put(md.configKey(), methodHandler);
268 | }
269 |
270 | return result;
271 | }
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor;
15 |
16 | import feign.InvocationHandlerFactory;
17 | import feign.InvocationHandlerFactory.MethodHandler;
18 | import feign.Target;
19 | import feign.reactor.client.ReactiveHttpClient;
20 | import org.reactivestreams.Publisher;
21 | import reactor.core.publisher.Flux;
22 | import reactor.core.publisher.Mono;
23 | import java.lang.reflect.InvocationHandler;
24 | import java.lang.reflect.Method;
25 | import java.lang.reflect.Proxy;
26 | import java.util.Map;
27 | import static feign.Util.checkNotNull;
28 |
29 | /**
30 | * {@link InvocationHandler} implementation that transforms calls to methods of feign contract into
31 | * asynchronous HTTP requests via spring WebClient.
32 | *
33 | * @author Sergii Karpenko
34 | */
35 | public final class ReactiveInvocationHandler implements InvocationHandler {
36 | private final Target> target;
37 | private final Map dispatch;
38 |
39 | private ReactiveInvocationHandler(final Target> target,
40 | final Map dispatch) {
41 | this.target = checkNotNull(target, "target must not be null");
42 | this.dispatch = checkNotNull(dispatch, "dispatch must not be null");
43 | defineObjectMethodsHandlers();
44 | }
45 |
46 | private void defineObjectMethodsHandlers() {
47 | try {
48 | dispatch.put(Object.class.getMethod("equals", Object.class),
49 | args -> {
50 | Object otherHandler = args.length > 0 && args[0] != null
51 | ? Proxy.getInvocationHandler(args[0])
52 | : null;
53 | return equals(otherHandler);
54 | });
55 | dispatch.put(Object.class.getMethod("hashCode"),
56 | args -> hashCode());
57 | dispatch.put(Object.class.getMethod("toString"),
58 | args -> toString());
59 | } catch (NoSuchMethodException e) {
60 | throw new RuntimeException(e);
61 | }
62 | }
63 |
64 | @Override
65 | public Object invoke(final Object proxy, final Method method, final Object[] args)
66 | throws Throwable {
67 | if (method.getDeclaringClass().equals(Object.class)) {
68 | return dispatch.get(method).invoke(args);
69 | } else {
70 | return invokeRequestMethod(method, args);
71 | }
72 | }
73 |
74 | /**
75 | * Transforms method invocation into request that executed by {@link ReactiveHttpClient}.
76 | *
77 | * @param method invoked method
78 | * @param args provided arguments to method
79 | * @return Publisher with decoded result
80 | */
81 | private Publisher invokeRequestMethod(final Method method, final Object[] args) {
82 | try {
83 | return (Publisher) dispatch.get(method).invoke(args);
84 | } catch (Throwable throwable) {
85 | return method.getReturnType() == Mono.class ? Mono.error(throwable)
86 | : Flux.error(throwable);
87 | }
88 | }
89 |
90 | @Override
91 | public boolean equals(final Object other) {
92 | if (other instanceof ReactiveInvocationHandler) {
93 | final ReactiveInvocationHandler otherHandler = (ReactiveInvocationHandler) other;
94 | return this.target.equals(otherHandler.target);
95 | }
96 | return false;
97 | }
98 |
99 | @Override
100 | public int hashCode() {
101 | return target.hashCode();
102 | }
103 |
104 | @Override
105 | public String toString() {
106 | return target.toString();
107 | }
108 |
109 | /**
110 | * Factory for ReactiveInvocationHandler.
111 | */
112 | public static final class Factory implements InvocationHandlerFactory {
113 |
114 | @Override
115 | public InvocationHandler create(final Target target,
116 | final Map dispatch) {
117 | return new ReactiveInvocationHandler(target, dispatch);
118 | }
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/ReactiveMethodHandler.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 feign.reactor;
15 |
16 | import feign.InvocationHandlerFactory;
17 | import org.reactivestreams.Publisher;
18 |
19 | /**
20 | * @author Sergii Karpenko
21 | */
22 | public interface ReactiveMethodHandler extends InvocationHandlerFactory.MethodHandler {
23 |
24 | @Override
25 | Publisher invoke(final Object[] argv);
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/ReactiveMethodHandlerFactory.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 feign.reactor;
15 |
16 | import feign.MethodMetadata;
17 | import feign.Target;
18 |
19 | public interface ReactiveMethodHandlerFactory {
20 |
21 | ReactiveMethodHandler create(final Target target, final MethodMetadata metadata);
22 | }
23 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor;
15 |
16 | /**
17 | * @author Sergii Karpenko
18 | */
19 | public class ReactiveOptions {
20 |
21 | private final Integer connectTimeoutMillis;
22 | private final Integer readTimeoutMillis;
23 | private final Boolean tryUseCompression;
24 |
25 | private ReactiveOptions(Integer connectTimeoutMillis, Integer readTimeoutMillis,
26 | Boolean tryUseCompression) {
27 | this.connectTimeoutMillis = connectTimeoutMillis;
28 | this.readTimeoutMillis = readTimeoutMillis;
29 | this.tryUseCompression = tryUseCompression;
30 | }
31 |
32 | public Integer getConnectTimeoutMillis() {
33 | return connectTimeoutMillis;
34 | }
35 |
36 | public Integer 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 Integer connectTimeoutMillis;
51 | private Integer readTimeoutMillis;
52 | private Boolean tryUseCompression;
53 |
54 | public Builder() {}
55 |
56 | public Builder setConnectTimeoutMillis(int connectTimeoutMillis) {
57 | this.connectTimeoutMillis = connectTimeoutMillis;
58 | return this;
59 | }
60 |
61 | public Builder setReadTimeoutMillis(int 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/feign/reactor/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 feign.reactor;
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/feign/reactor/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 feign.reactor;
15 |
16 | import feign.RetryableException;
17 | import java.util.Date;
18 |
19 | /**
20 | * @author Sergii Karpenko
21 | */
22 | public class ReactiveRetryers {
23 |
24 | public static ReactiveRetryPolicy retry(int maxRetries) {
25 | return (error, attemptNo) -> attemptNo <= maxRetries ? 0 : -1;
26 | }
27 |
28 | public static ReactiveRetryPolicy retryWithBackoff(int maxRetries, long periodInMs) {
29 | return (error, attemptNo) -> {
30 | if (attemptNo <= maxRetries) {
31 | long delay;
32 | Date retryAfter;
33 | // "Retry-After" header set
34 | if (error instanceof RetryableException
35 | && (retryAfter = ((RetryableException) error)
36 | .retryAfter()) != null) {
37 | delay = retryAfter.getTime() - System.currentTimeMillis();
38 | delay = Math.min(delay, periodInMs);
39 | delay = Math.max(delay, 0);
40 | } else {
41 | delay = periodInMs;
42 | }
43 | return delay;
44 | } else {
45 | return -1;
46 | }
47 | };
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor.client;
15 |
16 | import reactor.core.publisher.Mono;
17 | import java.util.List;
18 | import java.util.Map;
19 |
20 | /**
21 | * @author Sergii Karpenko
22 | */
23 | abstract public class DelegatingReactiveHttpResponse implements ReactiveHttpResponse {
24 |
25 | private final ReactiveHttpResponse response;
26 |
27 | protected DelegatingReactiveHttpResponse(ReactiveHttpResponse response) {
28 | this.response = response;
29 | }
30 |
31 | protected ReactiveHttpResponse getResponse() {
32 | return response;
33 | }
34 |
35 | @Override
36 | public int status() {
37 | return response.status();
38 | }
39 |
40 | @Override
41 | public Map> headers() {
42 | return response.headers();
43 | }
44 |
45 | @Override
46 | public Mono bodyData() {
47 | throw new UnsupportedOperationException();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor.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/feign/reactor/client/LoggerReactiveHttpClient.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 feign.reactor.client;
15 |
16 | import feign.MethodMetadata;
17 | import feign.reactor.utils.Pair;
18 | import org.reactivestreams.Publisher;
19 | import org.slf4j.LoggerFactory;
20 | import reactor.core.publisher.Flux;
21 | import reactor.core.publisher.Mono;
22 |
23 | import java.util.concurrent.atomic.AtomicLong;
24 | import java.util.function.Consumer;
25 | import java.util.function.Supplier;
26 | import java.util.stream.Collectors;
27 |
28 | import static feign.reactor.utils.FeignUtils.methodTag;
29 | import static reactor.core.publisher.Mono.just;
30 |
31 | /**
32 | * Wraps {@link ReactiveHttpClient} with log logic
33 | *
34 | * @author Sergii Karpenko
35 | */
36 | public class LoggerReactiveHttpClient implements ReactiveHttpClient {
37 |
38 | private final org.slf4j.Logger logger = LoggerFactory.getLogger(LoggerReactiveHttpClient.class);
39 |
40 | private final ReactiveHttpClient reactiveClient;
41 | private final String methodTag;
42 |
43 | public static ReactiveHttpClient log(ReactiveHttpClient reactiveClient, MethodMetadata methodMetadata) {
44 | return new LoggerReactiveHttpClient(reactiveClient, methodMetadata);
45 | }
46 |
47 | private LoggerReactiveHttpClient(ReactiveHttpClient reactiveClient,
48 | MethodMetadata methodMetadata) {
49 | this.reactiveClient = reactiveClient;
50 | this.methodTag = methodTag(methodMetadata);
51 | }
52 |
53 | @Override
54 | public Mono executeRequest(ReactiveHttpRequest request) {
55 |
56 | AtomicLong start = new AtomicLong(-1);
57 | return Mono
58 | .defer(() -> {
59 | start.set(System.currentTimeMillis());
60 | return just(request);
61 | })
62 | .flatMap(req -> {
63 | req = logRequest(methodTag, req);
64 |
65 | return reactiveClient.executeRequest(req)
66 | .doOnNext(resp -> logResponseHeaders(methodTag, resp,
67 | System.currentTimeMillis() - start.get()));
68 | })
69 | .map(resp -> new LoggerReactiveHttpResponse(resp, start));
70 | }
71 |
72 | private ReactiveHttpRequest logRequest(
73 | String feignMethodTag, ReactiveHttpRequest request) {
74 | if (logger.isDebugEnabled()) {
75 | logger.debug("[{}]--->{} {} HTTP/1.1", feignMethodTag, request.method(),
76 | request.uri());
77 | }
78 |
79 | if (logger.isTraceEnabled()) {
80 | logger.trace("[{}] REQUEST HEADERS\n{}", feignMethodTag,
81 | msg(() -> request.headers().entrySet().stream()
82 | .map(entry -> String.format("%s:%s", entry.getKey(),
83 | entry.getValue()))
84 | .collect(Collectors.joining("\n"))));
85 |
86 | if(request.body() != null) {
87 | Publisher bodyLogged;
88 | if (request.body() instanceof Mono) {
89 | bodyLogged = ((Mono) request.body()).doOnNext(body -> logger.trace(
90 | "[{}] REQUEST BODY\n{}", feignMethodTag, body));
91 | } else if (request.body() instanceof Flux) {
92 | bodyLogged = ((Flux) request.body()).doOnNext(body -> logger.trace(
93 | "[{}] REQUEST BODY ELEMENT\n{}", feignMethodTag, body));
94 | } else {
95 | throw new IllegalArgumentException("Unsupported publisher type: " + request.body().getClass());
96 | }
97 | return new ReactiveHttpRequest(request, bodyLogged);
98 | }
99 | }
100 |
101 | return request;
102 | }
103 |
104 | private void logResponseHeaders(String feignMethodTag,
105 | ReactiveHttpResponse httpResponse,
106 | long elapsedTime) {
107 | if (logger.isTraceEnabled()) {
108 | logger.trace("[{}] RESPONSE HEADERS\n{}", feignMethodTag,
109 | msg(() -> httpResponse.headers().entrySet().stream()
110 | .flatMap(entry -> entry.getValue().stream()
111 | .map(value -> new Pair<>(entry.getKey(), value)))
112 | .map(pair -> String.format("%s:%s", pair.left, pair.right))
113 | .collect(Collectors.joining("\n"))));
114 | }
115 | if (logger.isDebugEnabled()) {
116 | logger.debug("[{}]<--- headers takes {} milliseconds", feignMethodTag,
117 | elapsedTime);
118 | }
119 | }
120 |
121 | private void logResponseBodyAndTime(String feignMethodTag, Object response, long elapsedTime) {
122 | if (logger.isTraceEnabled()) {
123 | logger.trace("[{}] RESPONSE BODY\n{}", feignMethodTag, response);
124 | }
125 |
126 | if (logger.isDebugEnabled()) {
127 | logger.debug("[{}]<--- body takes {} milliseconds", feignMethodTag, elapsedTime);
128 | }
129 | }
130 |
131 | private class LoggerReactiveHttpResponse extends DelegatingReactiveHttpResponse {
132 |
133 | private final AtomicLong start;
134 |
135 | private LoggerReactiveHttpResponse(ReactiveHttpResponse response, AtomicLong start) {
136 | super(response);
137 | this.start = start;
138 | }
139 |
140 | @Override
141 | public Publisher body() {
142 | Publisher publisher = getResponse().body();
143 |
144 | if (publisher instanceof Mono) {
145 | return ((Mono) publisher).doOnNext(responseBodyLogger(start));
146 | } else {
147 | return ((Flux) publisher).doOnNext(responseBodyLogger(start));
148 | }
149 | }
150 |
151 | @Override
152 | public Mono bodyData() {
153 | Mono publisher = getResponse().bodyData();
154 |
155 | return publisher.doOnNext(responseBodyLogger(start));
156 | }
157 |
158 | private Consumer responseBodyLogger(AtomicLong start) {
159 | return result -> logResponseBodyAndTime(methodTag, result,
160 | System.currentTimeMillis() - start.get());
161 | }
162 | }
163 |
164 | private static MessageSupplier msg(Supplier> supplier) {
165 | return new MessageSupplier(supplier);
166 | }
167 |
168 | static class MessageSupplier {
169 | private Supplier> supplier;
170 |
171 | MessageSupplier(Supplier> supplier) {
172 | this.supplier = supplier;
173 | }
174 |
175 | @Override
176 | public String toString() {
177 | return supplier.get().toString();
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/client/ReactiveClientFactory.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 feign.reactor.client;
15 |
16 | import feign.MethodMetadata;
17 | import java.util.function.Function;
18 |
19 | /**
20 | * @author Sergii Karpenko
21 | */
22 |
23 | public interface ReactiveClientFactory extends Function {
24 | }
25 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/client/ReactiveHttpClient.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 feign.reactor.client;
15 |
16 | import org.reactivestreams.Publisher;
17 | import reactor.core.publisher.Mono;
18 | import java.lang.reflect.Type;
19 |
20 | /**
21 | * Client that execute http requests reactively
22 | *
23 | * @author Sergii Karpenko
24 | */
25 | public interface ReactiveHttpClient {
26 |
27 | Mono executeRequest(ReactiveHttpRequest request);
28 |
29 | default Publisher executeRequest(ReactiveHttpRequest request, Type returnPublisherType) {
30 | Mono response = executeRequest(request);
31 | if (returnPublisherType == Mono.class) {
32 | return response.flatMap(resp -> (Mono) resp.body());
33 | } else {
34 | return response.flatMapMany(ReactiveHttpResponse::body);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor.client;
15 |
16 | import org.reactivestreams.Publisher;
17 | import java.net.URI;
18 | import java.util.List;
19 | import java.util.Map;
20 | import static feign.Util.checkNotNull;
21 |
22 | /**
23 | * An immutable reactive request to an http server.
24 | *
25 | * @author Sergii Karpenko
26 | */
27 | public final class ReactiveHttpRequest {
28 |
29 | private final String method;
30 | private final URI uri;
31 | private final Map> headers;
32 | private final Publisher body;
33 |
34 | /**
35 | * No parameters can be null except {@code body}. All parameters must be effectively immutable,
36 | * via safe copies, not mutating or otherwise.
37 | */
38 | public ReactiveHttpRequest(String method, URI uri,
39 | Map> headers, Publisher body) {
40 | this.method = checkNotNull(method, "method of %s", uri);
41 | this.uri = checkNotNull(uri, "url");
42 | this.headers = checkNotNull(headers, "headers of %s %s", method, uri);
43 | this.body = body; // nullable
44 | }
45 |
46 | public ReactiveHttpRequest(ReactiveHttpRequest request, Publisher body){
47 | this(request.method, request.uri, request.headers, body);
48 | }
49 |
50 | /* Method to invoke on the server. */
51 | public String method() {
52 | return method;
53 | }
54 |
55 | /* Fully resolved URL including query. */
56 | public URI uri() {
57 | return uri;
58 | }
59 |
60 | /* Ordered list of headers that will be sent to the server. */
61 | public Map> headers() {
62 | return headers;
63 | }
64 |
65 | /**
66 | * If present, this is the replayable body to send to the server.
67 | */
68 | public Publisher body() {
69 | return body;
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor.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/feign/reactor/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 feign.reactor.client;
15 |
16 | import org.reactivestreams.Publisher;
17 | import reactor.core.publisher.Mono;
18 | import java.util.List;
19 | import java.util.Map;
20 |
21 | /**
22 | * Reactive response from an http server.
23 | *
24 | * @author Sergii Karpenko
25 | */
26 | public interface ReactiveHttpResponse {
27 |
28 | int status();
29 |
30 | Map> headers();
31 |
32 | Publisher body();
33 |
34 | /**
35 | * used by error decoders
36 | *
37 | * @return error message data
38 | */
39 | Mono bodyData();
40 | }
41 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor.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/feign/reactor/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 feign.reactor.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 | import java.util.function.BiFunction;
21 |
22 | /**
23 | * Maps 404 error response to successful empty response
24 | *
25 | * @author Sergii Karpenko
26 | */
27 | public class ResponseMappers {
28 |
29 | public static BiFunction ignore404() {
30 | return (MethodMetadata methodMetadata, ReactiveHttpResponse response) -> {
31 | if (response.status() == HttpStatus.SC_NOT_FOUND) {
32 | return new DelegatingReactiveHttpResponse(response) {
33 | @Override
34 | public int status() {
35 | return HttpStatus.SC_OK;
36 | }
37 |
38 | @Override
39 | public Publisher body() {
40 | return Mono.empty();
41 | }
42 | };
43 | }
44 | return response;
45 | };
46 | }
47 |
48 | public static ReactiveHttpClient mapResponse(
49 | ReactiveHttpClient reactiveHttpClient,
50 | MethodMetadata methodMetadata,
51 | BiFunction responseMapper) {
52 | return request -> reactiveHttpClient.executeRequest(request)
53 | .map(response -> responseMapper.apply(methodMetadata, response));
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/client/RetryReactiveHttpClient.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 feign.reactor.client;
15 |
16 | import feign.MethodMetadata;
17 | import org.reactivestreams.Publisher;
18 | import org.slf4j.LoggerFactory;
19 | import reactor.core.publisher.Flux;
20 | import reactor.core.publisher.Mono;
21 |
22 | import java.lang.reflect.Type;
23 | import java.util.function.Function;
24 |
25 | import static feign.reactor.utils.FeignUtils.methodTag;
26 |
27 | /**
28 | * Wraps {@link ReactiveHttpClient} with retry logic provided by retryFunction
29 | *
30 | * @author Sergii Karpenko
31 | */
32 | public class RetryReactiveHttpClient implements ReactiveHttpClient {
33 |
34 | private static final org.slf4j.Logger logger = LoggerFactory
35 | .getLogger(RetryReactiveHttpClient.class);
36 |
37 | private final String feignMethodTag;
38 | private final ReactiveHttpClient reactiveClient;
39 | private final Function, Flux>> retryFunction;
40 |
41 | public static ReactiveHttpClient retry(
42 | ReactiveHttpClient reactiveClient,
43 | MethodMetadata methodMetadata,
44 | Function, Flux> retryFunction) {
45 | return new RetryReactiveHttpClient<>(reactiveClient, methodMetadata, retryFunction);
46 | }
47 |
48 | private RetryReactiveHttpClient(ReactiveHttpClient reactiveClient,
49 | MethodMetadata methodMetadata,
50 | Function, Flux> retryFunction) {
51 | this.reactiveClient = reactiveClient;
52 | this.feignMethodTag = methodTag(methodMetadata);
53 | this.retryFunction = wrapWithLog(retryFunction, feignMethodTag);
54 | }
55 |
56 | @Override
57 | public Publisher executeRequest(ReactiveHttpRequest request, Type returnPublisherType) {
58 | Publisher response = reactiveClient.executeRequest(request, returnPublisherType);
59 | if (returnPublisherType == Mono.class) {
60 | return ((Mono) response).retryWhen(retryFunction).onErrorMap(outOfRetries());
61 | } else {
62 | return ((Flux) response).retryWhen(retryFunction).onErrorMap(outOfRetries());
63 | }
64 | }
65 |
66 | @Override
67 | public Mono executeRequest(ReactiveHttpRequest request) {
68 | return reactiveClient.executeRequest(request);
69 | }
70 |
71 | private Function outOfRetries() {
72 | return throwable -> {
73 | logger.debug("[{}]---> USED ALL RETRIES", feignMethodTag, throwable);
74 | return new OutOfRetriesException(throwable, feignMethodTag);
75 | };
76 | }
77 |
78 | private static Function, Flux>> wrapWithLog(
79 | Function, Flux> retryFunction,
80 | String feignMethodTag) {
81 | return throwableFlux -> retryFunction.apply(throwableFlux)
82 | .doOnNext(throwable -> {
83 | if (logger.isDebugEnabled()) {
84 | logger.debug("[{}]---> RETRYING on error", feignMethodTag, throwable);
85 | }
86 | });
87 | }
88 |
89 | public static class OutOfRetriesException extends Exception {
90 | OutOfRetriesException(Throwable cause, String feignMethodTag) {
91 | super("All retries used for: " + feignMethodTag, cause);
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor.client;
15 |
16 | import feign.MethodMetadata;
17 | import org.reactivestreams.Publisher;
18 | import feign.reactor.client.statushandler.ReactiveStatusHandler;
19 | import reactor.core.publisher.Flux;
20 | import reactor.core.publisher.Mono;
21 | import static feign.reactor.utils.FeignUtils.methodTag;
22 |
23 | /**
24 | * Uses statusHandlers to process status of http response
25 | *
26 | * @author Sergii Karpenko
27 | */
28 |
29 | public class StatusHandlerReactiveHttpClient implements ReactiveHttpClient {
30 |
31 | private final ReactiveHttpClient reactiveClient;
32 | private final String methodTag;
33 |
34 | private final ReactiveStatusHandler statusHandler;
35 |
36 | public static ReactiveHttpClient handleStatus(
37 | ReactiveHttpClient reactiveClient,
38 | MethodMetadata methodMetadata,
39 | ReactiveStatusHandler statusHandler) {
40 | return new StatusHandlerReactiveHttpClient(reactiveClient, methodMetadata, statusHandler);
41 | }
42 |
43 | private StatusHandlerReactiveHttpClient(ReactiveHttpClient reactiveClient,
44 | MethodMetadata methodMetadata,
45 | ReactiveStatusHandler statusHandler) {
46 | this.reactiveClient = reactiveClient;
47 | this.methodTag = methodTag(methodMetadata);
48 | this.statusHandler = statusHandler;
49 | }
50 |
51 | @Override
52 | public Mono executeRequest(ReactiveHttpRequest request) {
53 | return reactiveClient.executeRequest(request).map(response -> {
54 | if (statusHandler.shouldHandle(response.status())) {
55 | return new ErrorReactiveHttpResponse(response, statusHandler.decode(methodTag, response));
56 | } else {
57 | return response;
58 | }
59 | });
60 | }
61 |
62 | private class ErrorReactiveHttpResponse extends DelegatingReactiveHttpResponse {
63 |
64 | private final Mono extends Throwable> error;
65 |
66 | protected ErrorReactiveHttpResponse(ReactiveHttpResponse response, Mono extends Throwable> error) {
67 | super(response);
68 | this.error = error;
69 | }
70 |
71 | @Override
72 | public Publisher body() {
73 | if (getResponse().body() instanceof Mono) {
74 | return error.flatMap(Mono::error);
75 | } else {
76 | return error.flatMapMany(Flux::error);
77 | }
78 | }
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor.client.statushandler;
15 |
16 | import feign.reactor.client.ReactiveHttpResponse;
17 | import reactor.core.publisher.Mono;
18 | import java.util.List;
19 | import static java.util.Arrays.asList;
20 |
21 | /**
22 | * @author Sergii Karpenko
23 | */
24 | public class CompositeStatusHandler implements ReactiveStatusHandler {
25 |
26 | private final List handlers;
27 |
28 | public static CompositeStatusHandler compose(ReactiveStatusHandler... handlers) {
29 | return new CompositeStatusHandler(asList(handlers));
30 | }
31 |
32 | private CompositeStatusHandler(List handlers) {
33 | this.handlers = handlers;
34 | }
35 |
36 | @Override
37 | public boolean shouldHandle(int status) {
38 | return handlers.stream().anyMatch(handler -> handler.shouldHandle(status));
39 | }
40 |
41 | @Override
42 | public Mono extends Throwable> decode(String methodKey, ReactiveHttpResponse response) {
43 | return handlers.stream()
44 | .filter(statusHandler -> statusHandler
45 | .shouldHandle(response.status()))
46 | .findFirst()
47 | .map(statusHandler -> statusHandler.decode(methodKey, response))
48 | .orElse(null);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor.client.statushandler;
15 |
16 | import feign.reactor.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 extends Throwable> decode(String methodKey, ReactiveHttpResponse response);
27 | }
28 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor.client.statushandler;
15 |
16 | import feign.Response;
17 | import feign.codec.ErrorDecoder;
18 | import org.apache.commons.httpclient.HttpStatus;
19 | import feign.reactor.client.ReactiveHttpResponse;
20 | import reactor.core.publisher.Mono;
21 | import java.util.Map;
22 | import java.util.function.BiFunction;
23 | import java.util.function.Predicate;
24 | import java.util.stream.Collectors;
25 | import static feign.reactor.utils.HttpUtils.familyOf;
26 |
27 | public class ReactiveStatusHandlers {
28 |
29 | public static ReactiveStatusHandler defaultFeign(ErrorDecoder errorDecoder) {
30 | return new ReactiveStatusHandler() {
31 |
32 | @Override
33 | public boolean shouldHandle(int status) {
34 | return familyOf(status).isError();
35 | }
36 |
37 | @Override
38 | public Mono extends Throwable> decode(String methodTag, ReactiveHttpResponse response) {
39 | return response.bodyData().map(bodyData -> errorDecoder.decode(methodTag,
40 | Response.builder().status(response.status())
41 | .reason(HttpStatus.getStatusText(response.status()))
42 | .headers(response.headers().entrySet()
43 | .stream()
44 | .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
45 | .body(bodyData).build()));
46 | }
47 | };
48 | }
49 |
50 | public static ReactiveStatusHandler throwOnStatus(Predicate statusPredicate,
51 | BiFunction errorFunction) {
52 | return new ReactiveStatusHandler() {
53 | @Override
54 | public boolean shouldHandle(int status) {
55 | return statusPredicate.test(status);
56 | }
57 |
58 | @Override
59 | public Mono extends Throwable> decode(String methodKey, ReactiveHttpResponse response) {
60 | return Mono.just(errorFunction.apply(methodKey, response));
61 | }
62 | };
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor.utils;
15 |
16 | import feign.MethodMetadata;
17 | import org.reactivestreams.Publisher;
18 | import java.lang.reflect.ParameterizedType;
19 | import java.lang.reflect.Type;
20 | import static feign.Util.resolveLastTypeParameter;
21 | import static java.util.Optional.ofNullable;
22 |
23 | public class FeignUtils {
24 |
25 | public static String methodTag(MethodMetadata methodMetadata) {
26 | return methodMetadata.configKey().substring(0,
27 | methodMetadata.configKey().indexOf('('));
28 | }
29 |
30 | public static Type returnPublisherType(MethodMetadata methodMetadata) {
31 | final Type returnType = methodMetadata.returnType();
32 | return ((ParameterizedType) returnType).getRawType();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/main/java/feign/reactor/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 feign.reactor.utils;
15 |
16 | import static feign.reactor.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/feign/reactor/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 feign.reactor.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/feign/reactor/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 feign.reactor.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/feign/reactor/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 feign.reactor;
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 feign.reactor.testcase.IcecreamServiceApi;
20 | import feign.reactor.testcase.domain.Bill;
21 | import feign.reactor.testcase.domain.IceCreamOrder;
22 | import feign.reactor.testcase.domain.OrderGenerator;
23 | import org.junit.ClassRule;
24 | import org.junit.Test;
25 | import reactor.core.publisher.Mono;
26 | import reactor.test.StepVerifier;
27 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
28 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
29 | import static feign.reactor.TestUtils.equalsComparingFieldByFieldRecursively;
30 |
31 | /**
32 | * Test the new capability of Reactive Feign client to support both Feign Request.Options
33 | * (regression) and the new ReactiveOptions configuration.
34 | *
35 | * @author Sergii Karpenko
36 | */
37 |
38 | abstract public class CompressionTest {
39 |
40 | @ClassRule
41 | public static WireMockClassRule wireMockRule = new WireMockClassRule(
42 | wireMockConfig().dynamicPort());
43 |
44 | abstract protected ReactiveFeign.Builder builder(ReactiveOptions options);
45 |
46 | @Test
47 | public void testCompression() throws JsonProcessingException {
48 |
49 | IceCreamOrder order = new OrderGenerator().generate(20);
50 | Bill billExpected = Bill.makeBill(order);
51 |
52 | wireMockRule.stubFor(post(urlEqualTo("/icecream/orders"))
53 | .withHeader("Accept-Encoding", containing("gzip"))
54 | .withRequestBody(equalTo(TestUtils.MAPPER.writeValueAsString(order)))
55 | .willReturn(aResponse().withStatus(200)
56 | .withHeader("Content-Type", "application/json")
57 | .withHeader("Content-Encoding", "gzip")
58 | .withBody(Gzip.gzip(TestUtils.MAPPER.writeValueAsString(billExpected)))));
59 |
60 | IcecreamServiceApi client = builder(
61 | new ReactiveOptions.Builder()
62 | .setTryUseCompression(true)
63 | .build())
64 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
65 |
66 | Mono bill = client.makeOrder(order);
67 | StepVerifier.create(bill)
68 | .expectNextMatches(equalsComparingFieldByFieldRecursively(billExpected))
69 | .verifyComplete();
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor;
15 |
16 | import feign.reactor.testcase.IcecreamServiceApi;
17 | import org.hamcrest.Matchers;
18 | import org.junit.*;
19 | import org.junit.rules.ExpectedException;
20 | import java.io.IOException;
21 | import java.net.ConnectException;
22 | import java.net.ServerSocket;
23 | import java.net.Socket;
24 |
25 | /**
26 | * @author Sergii Karpenko
27 | */
28 | abstract public class ConnectionTimeoutTest {
29 |
30 | @Rule
31 | public ExpectedException expectedException = ExpectedException.none();
32 |
33 | private ServerSocket serverSocket;
34 | private Socket socket;
35 | private int port;
36 |
37 | abstract protected ReactiveFeign.Builder builder(ReactiveOptions options);
38 |
39 | @Before
40 | public void before() throws IOException {
41 | // server socket with single element backlog queue (1) and dynamicaly allocated
42 | // port (0)
43 | serverSocket = new ServerSocket(0, 1);
44 | // just get the allocated port
45 | port = serverSocket.getLocalPort();
46 | // fill backlog queue by this request so consequent requests will be blocked
47 | socket = new Socket();
48 | socket.connect(serverSocket.getLocalSocketAddress());
49 | }
50 |
51 | @After
52 | public void after() throws IOException {
53 | // some cleanup
54 | if (serverSocket != null && !serverSocket.isClosed()) {
55 | serverSocket.close();
56 | }
57 | }
58 |
59 | // TODO investigate why doesn't work on codecov.io but works locally
60 | @Ignore
61 | @Test
62 | public void shouldFailOnConnectionTimeout() {
63 |
64 | expectedException.expectCause(
65 |
66 | Matchers.any(ConnectException.class));
67 |
68 | IcecreamServiceApi client = builder(
69 | new ReactiveOptions.Builder()
70 | .setConnectTimeoutMillis(300)
71 | .setReadTimeoutMillis(100)
72 | .build())
73 | .target(IcecreamServiceApi.class, "http://localhost:" + port);
74 |
75 | client.findOrder(1).block();
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor;
15 |
16 | import feign.reactor.testcase.IcecreamServiceApi;
17 | import feign.reactor.testcase.IcecreamServiceApiBroken;
18 | import org.junit.Rule;
19 | import org.junit.Test;
20 | import org.junit.rules.ExpectedException;
21 | import static org.hamcrest.Matchers.containsString;
22 |
23 | /**
24 | * @author Sergii Karpenko
25 | */
26 |
27 | abstract public class ContractTest {
28 |
29 | @Rule
30 | public ExpectedException expectedException = ExpectedException.none();
31 |
32 | abstract protected ReactiveFeign.Builder builder();
33 |
34 | @Test
35 | public void shouldFailOnBrokenContract() {
36 |
37 | expectedException.expect(IllegalArgumentException.class);
38 | expectedException.expectMessage(containsString("Broken Contract"));
39 |
40 | this.builder()
41 | .contract(targetType -> {
42 | throw new IllegalArgumentException("Broken Contract");
43 | })
44 | .target(IcecreamServiceApi.class, "http://localhost:8888");
45 | }
46 |
47 | @Test
48 | public void shouldFailIfNotReactiveContract() {
49 |
50 | expectedException.expect(IllegalArgumentException.class);
51 | expectedException.expectMessage(containsString("IcecreamServiceApiBroken#findOrder(int)"));
52 |
53 | this.builder()
54 | .target(IcecreamServiceApiBroken.class, "http://localhost:8888");
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor;
15 |
16 | import com.fasterxml.jackson.core.JsonProcessingException;
17 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
18 | import feign.RequestLine;
19 | import feign.reactor.testcase.IcecreamServiceApi;
20 | import feign.reactor.testcase.domain.IceCreamOrder;
21 | import feign.reactor.testcase.domain.OrderGenerator;
22 | import org.junit.Before;
23 | import org.junit.ClassRule;
24 | import org.junit.Test;
25 | import reactor.core.publisher.Flux;
26 | import reactor.core.publisher.Mono;
27 | import reactor.test.StepVerifier;
28 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
29 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
30 | import static feign.reactor.TestUtils.equalsComparingFieldByFieldRecursively;
31 | import static org.assertj.core.api.Assertions.assertThat;
32 |
33 | /**
34 | * @author Sergii Karpenko
35 | */
36 | abstract public class DefaultMethodTest {
37 |
38 | @ClassRule
39 | public static WireMockClassRule wireMockRule = new WireMockClassRule(
40 | wireMockConfig().dynamicPort());
41 |
42 | @Before
43 | public void resetServers() {
44 | wireMockRule.resetAll();
45 | }
46 |
47 | abstract protected ReactiveFeign.Builder builder();
48 |
49 | abstract protected ReactiveFeign.Builder builder(Class apiClass);
50 |
51 | abstract protected ReactiveFeign.Builder builder(ReactiveOptions options);
52 |
53 | @Test
54 | public void shouldProcessDefaultMethodOnProxy() throws JsonProcessingException {
55 | IceCreamOrder orderGenerated = new OrderGenerator().generate(1);
56 | String orderStr = TestUtils.MAPPER.writeValueAsString(orderGenerated);
57 |
58 | wireMockRule.stubFor(get(urlEqualTo("/icecream/orders/1"))
59 | .withHeader("Accept", equalTo("application/json"))
60 | .willReturn(aResponse().withStatus(200)
61 | .withHeader("Content-Type", "application/json")
62 | .withBody(orderStr)));
63 |
64 | IcecreamServiceApi client = builder()
65 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
66 |
67 | StepVerifier.create(client.findFirstOrder())
68 | .expectNextMatches(equalsComparingFieldByFieldRecursively(orderGenerated))
69 | .verifyComplete();
70 | }
71 |
72 | @Test
73 | public void shouldWrapExceptionWithMono() {
74 | IceCreamOrder orderGenerated = new OrderGenerator().generate(1);
75 |
76 | IcecreamServiceApi client = builder()
77 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
78 |
79 | Mono errorOrder = client.throwExceptionMono().onErrorReturn(
80 | throwable -> throwable.equals(IcecreamServiceApi.RUNTIME_EXCEPTION),
81 | orderGenerated);
82 |
83 | StepVerifier.create(errorOrder)
84 | .expectNextMatches(equalsComparingFieldByFieldRecursively(orderGenerated))
85 | .verifyComplete();
86 | }
87 |
88 | @Test
89 | public void shouldWrapExceptionWithFlux() {
90 | IceCreamOrder orderGenerated = new OrderGenerator().generate(1);
91 |
92 | IcecreamServiceApi client = builder()
93 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
94 |
95 | Flux errorOrder = client.throwExceptionFlux().onErrorReturn(
96 | throwable -> throwable.equals(IcecreamServiceApi.RUNTIME_EXCEPTION),
97 | orderGenerated);
98 |
99 | StepVerifier.create(errorOrder)
100 | .expectNextMatches(equalsComparingFieldByFieldRecursively(orderGenerated))
101 | .verifyComplete();
102 | }
103 |
104 | @Test
105 | public void shouldOverrideEquals() {
106 |
107 | IcecreamServiceApi client = builder(
108 | new ReactiveOptions.Builder()
109 | .setConnectTimeoutMillis(300)
110 | .setReadTimeoutMillis(100).build())
111 | .target(IcecreamServiceApi.class,
112 | "http://localhost:" + wireMockRule.port());
113 |
114 | IcecreamServiceApi clientWithSameTarget = builder()
115 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
116 | assertThat(client).isEqualTo(clientWithSameTarget);
117 |
118 | IcecreamServiceApi clientWithOtherPort = builder()
119 | .target(IcecreamServiceApi.class, "http://localhost:" + (wireMockRule.port() + 1));
120 | assertThat(client).isNotEqualTo(clientWithOtherPort);
121 |
122 | OtherApi clientWithOtherInterface = builder(OtherApi.class)
123 | .target(OtherApi.class, "http://localhost:" + wireMockRule.port());
124 | assertThat(client).isNotEqualTo(clientWithOtherInterface);
125 | }
126 |
127 | interface OtherApi {
128 | @RequestLine("GET /icecream/flavors")
129 | Mono method(String arg);
130 | }
131 |
132 | @Test
133 | public void shouldOverrideHashcode() {
134 |
135 | IcecreamServiceApi client = builder()
136 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
137 |
138 | IcecreamServiceApi otherClientWithSameTarget = builder()
139 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
140 |
141 | assertThat(client.hashCode()).isEqualTo(otherClientWithSameTarget.hashCode());
142 | }
143 |
144 | @Test
145 | public void shouldOverrideToString() {
146 |
147 | IcecreamServiceApi client = builder()
148 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
149 |
150 | assertThat(client.toString())
151 | .isEqualTo("HardCodedTarget(type=IcecreamServiceApi, "
152 | + "url=http://localhost:" + wireMockRule.port() + ")");
153 | }
154 |
155 | }
156 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 |
15 | package feign.reactor;
16 |
17 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
18 | import feign.reactor.client.LoggerReactiveHttpClient;
19 | import feign.reactor.testcase.IcecreamServiceApi;
20 | import feign.reactor.testcase.domain.Bill;
21 | import feign.reactor.testcase.domain.IceCreamOrder;
22 | import feign.reactor.testcase.domain.OrderGenerator;
23 | import org.apache.logging.log4j.Level;
24 | import org.apache.logging.log4j.LogManager;
25 | import org.apache.logging.log4j.core.Appender;
26 | import org.apache.logging.log4j.core.LogEvent;
27 | import org.apache.logging.log4j.core.LoggerContext;
28 | import org.apache.logging.log4j.core.config.Configuration;
29 | import org.apache.logging.log4j.core.config.LoggerConfig;
30 | import org.assertj.core.api.Condition;
31 | import org.junit.Before;
32 | import org.junit.ClassRule;
33 | import org.junit.Test;
34 | import org.mockito.ArgumentCaptor;
35 | import org.mockito.Mockito;
36 | import reactor.core.publisher.Mono;
37 | import java.util.List;
38 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
39 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
40 | import static org.assertj.core.api.Assertions.assertThat;
41 | import static org.mockito.Mockito.never;
42 | import static org.mockito.Mockito.times;
43 | import static org.mockito.Mockito.when;
44 |
45 | /**
46 | * @author Sergii Karpenko
47 | */
48 | abstract public class LoggerTest {
49 |
50 | public static final String LOGGER_NAME = LoggerReactiveHttpClient.class.getName();
51 | @ClassRule
52 | public static WireMockClassRule wireMockRule = new WireMockClassRule(
53 | wireMockConfig()
54 | .asynchronousResponseEnabled(true)
55 | .dynamicPort());
56 |
57 | abstract protected ReactiveFeign.Builder builder();
58 |
59 | protected Appender appender;
60 |
61 | @Test
62 | public void shouldLog() throws Exception {
63 |
64 | setLogLevel(Level.TRACE);
65 |
66 | IceCreamOrder order = new OrderGenerator().generate(20);
67 | Bill billExpected = Bill.makeBill(order);
68 |
69 | wireMockRule.stubFor(post(urlEqualTo("/icecream/orders"))
70 | .withRequestBody(equalTo(TestUtils.MAPPER.writeValueAsString(order)))
71 | .willReturn(aResponse().withStatus(200)
72 | .withHeader("Content-Type", "application/json")
73 | .withBody(TestUtils.MAPPER.writeValueAsString(billExpected))));
74 |
75 | IcecreamServiceApi client = builder()
76 | .target(IcecreamServiceApi.class,
77 | "http://localhost:" + wireMockRule.port());
78 |
79 | Mono billMono = client.makeOrder(order);
80 |
81 | // no logs before subscription
82 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(LogEvent.class);
83 | Mockito.verify(appender, never()).append(argumentCaptor.capture());
84 |
85 | billMono.block();
86 |
87 | Mockito.verify(appender, times(7)).append(argumentCaptor.capture());
88 |
89 | List logEvents = argumentCaptor.getAllValues();
90 | assertLogEvent(logEvents, 0, Level.DEBUG,
91 | "[IcecreamServiceApi#makeOrder]--->POST http://localhost");
92 | assertLogEvent(logEvents, 1, Level.TRACE,
93 | "[IcecreamServiceApi#makeOrder] REQUEST HEADERS\n" +
94 | "Accept:[application/json]");
95 | assertLogEvent(logEvents, 2, Level.TRACE,
96 | "[IcecreamServiceApi#makeOrder] REQUEST BODY\n" +
97 | "IceCreamOrder{ id=20, balls=");
98 | assertLogEvent(logEvents, 3, Level.TRACE,
99 | "[IcecreamServiceApi#makeOrder] RESPONSE HEADERS\n" +
100 | "Content-Type:application/json");
101 | assertLogEvent(logEvents, 4, Level.DEBUG,
102 | "[IcecreamServiceApi#makeOrder]<--- headers takes");
103 | assertLogEvent(logEvents, 5, Level.TRACE,
104 | "[IcecreamServiceApi#makeOrder] RESPONSE BODY\n" +
105 | "feign.reactor.testcase.domain.Bill");
106 | assertLogEvent(logEvents, 6, Level.DEBUG,
107 | "[IcecreamServiceApi#makeOrder]<--- body takes");
108 | }
109 |
110 | private void assertLogEvent(List events, int index, Level level, String message) {
111 | assertThat(events).element(index)
112 | .hasFieldOrPropertyWithValue("level", level)
113 | .extracting("message")
114 | .extractingResultOf("getFormattedMessage")
115 | .have(new Condition<>(o -> ((String) o).contains(message), "check message"));
116 | }
117 |
118 | @Before
119 | public void before() {
120 | appender = Mockito.mock(Appender.class);
121 | when(appender.getName()).thenReturn("TestAppender");
122 | when(appender.isStarted()).thenReturn(true);
123 | getLoggerConfig().addAppender(appender, Level.ALL, null);
124 | }
125 |
126 | private static void setLogLevel(Level logLevel) {
127 | LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
128 | Configuration configuration = loggerContext.getConfiguration();
129 | configuration.getLoggerConfig(LOGGER_NAME).setLevel(logLevel);
130 | loggerContext.updateLoggers();
131 | }
132 |
133 | private static LoggerConfig getLoggerConfig() {
134 | LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
135 | Configuration configuration = loggerContext.getConfiguration();
136 | configuration.addLogger(LOGGER_NAME, new LoggerConfig());
137 | return configuration.getLoggerConfig(LOGGER_NAME);
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor;
15 |
16 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
17 | import feign.reactor.testcase.IcecreamServiceApi;
18 | import org.apache.http.HttpStatus;
19 | import org.junit.ClassRule;
20 | import org.junit.Test;
21 | import reactor.test.StepVerifier;
22 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
23 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
24 |
25 | /**
26 | * @author Sergii Karpenko
27 | */
28 | public abstract class NotFoundTest {
29 |
30 | @ClassRule
31 | public static WireMockClassRule wireMockRule = new WireMockClassRule(
32 | wireMockConfig().dynamicPort());
33 |
34 | abstract protected ReactiveFeign.Builder builder();
35 |
36 | @Test
37 | public void shouldReturnEmptyMono() {
38 |
39 | String orderUrl = "/icecream/orders/2";
40 | wireMockRule.stubFor(get(urlEqualTo(orderUrl))
41 | .withHeader("Accept", equalTo("application/json"))
42 | .willReturn(aResponse().withStatus(HttpStatus.SC_NOT_FOUND)));
43 |
44 | IcecreamServiceApi client = builder()
45 | .decode404()
46 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
47 |
48 | StepVerifier.create(client.findOrder(2))
49 | .expectNextCount(0)
50 | .verifyComplete();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor;
15 |
16 | import com.fasterxml.jackson.core.JsonProcessingException;
17 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
18 | import feign.reactor.testcase.IcecreamServiceApi;
19 | import feign.reactor.testcase.domain.IceCreamOrder;
20 | import feign.reactor.testcase.domain.OrderGenerator;
21 | import org.awaitility.Duration;
22 | import org.junit.ClassRule;
23 | import org.junit.Test;
24 | import java.util.concurrent.TimeUnit;
25 | import java.util.concurrent.atomic.AtomicInteger;
26 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
27 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
28 | import static org.assertj.core.api.Assertions.assertThat;
29 | import static org.awaitility.Awaitility.await;
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 = 100;
39 | public static final int REACTIVE_GAIN_RATIO = 20;
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 | int timeToCompleteReactively = CALLS_NUMBER * DELAY_IN_MILLIS / REACTIVE_GAIN_RATIO;
76 | waitAtMost(new Duration(timeToCompleteReactively, TimeUnit.MILLISECONDS))
77 | .until(() -> counter.get() == CALLS_NUMBER);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor;
15 |
16 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
17 | import feign.reactor.client.ReadTimeoutException;
18 | import feign.reactor.testcase.IcecreamServiceApi;
19 | import org.junit.ClassRule;
20 | import org.junit.Test;
21 | import reactor.test.StepVerifier;
22 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
23 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
24 |
25 | /**
26 | * @author Sergii Karpenko
27 | */
28 | abstract public class ReadTimeoutTest {
29 |
30 | @ClassRule
31 | public static WireMockClassRule wireMockRule = new WireMockClassRule(
32 | wireMockConfig().dynamicPort());
33 |
34 | abstract protected ReactiveFeign.Builder builder(ReactiveOptions options);
35 |
36 | @Test
37 | public void shouldFailOnReadTimeout() {
38 |
39 | String orderUrl = "/icecream/orders/1";
40 |
41 | wireMockRule.stubFor(get(urlEqualTo(orderUrl))
42 | .withHeader("Accept", equalTo("application/json"))
43 | .willReturn(aResponse().withFixedDelay(200)));
44 |
45 | IcecreamServiceApi client = builder(
46 | new ReactiveOptions.Builder()
47 | .setConnectTimeoutMillis(300)
48 | .setReadTimeoutMillis(100)
49 | .build())
50 | .target(IcecreamServiceApi.class,
51 | "http://localhost:" + wireMockRule.port());
52 |
53 | StepVerifier.create(client.findOrder(1))
54 | .expectError(ReadTimeoutException.class)
55 | .verify();
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor;
15 |
16 | import com.fasterxml.jackson.core.JsonProcessingException;
17 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
18 | import feign.FeignException;
19 | import feign.reactor.testcase.IcecreamServiceApi;
20 | import feign.reactor.testcase.domain.IceCreamOrder;
21 | import feign.reactor.testcase.domain.OrderGenerator;
22 | import org.apache.http.HttpStatus;
23 | import org.junit.ClassRule;
24 | import org.junit.Test;
25 | import reactor.test.StepVerifier;
26 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
27 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
28 | import static feign.reactor.TestUtils.equalsComparingFieldByFieldRecursively;
29 | import static feign.reactor.utils.MultiValueMapUtils.addOrdered;
30 |
31 | /**
32 | * @author Sergii Karpenko
33 | */
34 | abstract public class RequestInterceptorTest {
35 |
36 | @ClassRule
37 | public static WireMockClassRule wireMockRule = new WireMockClassRule(
38 | wireMockConfig().dynamicPort());
39 |
40 | abstract protected ReactiveFeign.Builder builder();
41 |
42 | @Test
43 | public void shouldInterceptRequestAndSetAuthHeader() throws JsonProcessingException {
44 |
45 | String orderUrl = "/icecream/orders/1";
46 |
47 | IceCreamOrder orderGenerated = new OrderGenerator().generate(1);
48 | String orderStr = TestUtils.MAPPER.writeValueAsString(orderGenerated);
49 |
50 | wireMockRule.stubFor(get(urlEqualTo(orderUrl))
51 | .withHeader("Accept", equalTo("application/json"))
52 | .willReturn(aResponse().withStatus(HttpStatus.SC_UNAUTHORIZED)))
53 | .setPriority(100);
54 |
55 | wireMockRule.stubFor(get(urlEqualTo(orderUrl))
56 | .withHeader("Accept", equalTo("application/json"))
57 | .withHeader("Authorization", equalTo("Bearer mytoken123"))
58 | .willReturn(aResponse().withStatus(200)
59 | .withHeader("Content-Type", "application/json")
60 | .withBody(orderStr)))
61 | .setPriority(1);
62 |
63 | IcecreamServiceApi clientWithoutAuth = builder()
64 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
65 |
66 | StepVerifier.create(clientWithoutAuth.findFirstOrder())
67 | .expectError(FeignException.class)
68 | .verify();
69 |
70 | IcecreamServiceApi clientWithAuth = builder()
71 | .requestInterceptor(request -> {
72 | addOrdered(request.headers(), "Authorization", "Bearer mytoken123");
73 | return request;
74 | })
75 | .target(IcecreamServiceApi.class,
76 | "http://localhost:" + wireMockRule.port());
77 |
78 | StepVerifier.create(clientWithAuth.findFirstOrder())
79 | .expectNextMatches(equalsComparingFieldByFieldRecursively(orderGenerated))
80 | .expectComplete();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor;
15 |
16 | import com.fasterxml.jackson.core.JsonProcessingException;
17 | import com.github.tomakehurst.wiremock.client.ResponseDefinitionBuilder;
18 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
19 | import feign.RetryableException;
20 | import feign.reactor.client.RetryReactiveHttpClient;
21 | import feign.reactor.testcase.IcecreamServiceApi;
22 | import feign.reactor.testcase.domain.IceCreamOrder;
23 | import feign.reactor.testcase.domain.Mixin;
24 | import feign.reactor.testcase.domain.OrderGenerator;
25 | import org.junit.Before;
26 | import org.junit.ClassRule;
27 | import org.junit.Test;
28 | import reactor.test.StepVerifier;
29 | import java.util.Arrays;
30 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
31 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
32 | import static com.github.tomakehurst.wiremock.stubbing.Scenario.STARTED;
33 | import static feign.reactor.TestUtils.equalsComparingFieldByFieldRecursively;
34 | import static org.apache.http.HttpHeaders.RETRY_AFTER;
35 | import static org.apache.http.HttpStatus.SC_OK;
36 | import static org.apache.http.HttpStatus.SC_SERVICE_UNAVAILABLE;
37 | import static org.hamcrest.CoreMatchers.allOf;
38 | import static org.hamcrest.Matchers.hasProperty;
39 | import static org.hamcrest.Matchers.isA;
40 |
41 | /**
42 | * @author Sergii Karpenko
43 | */
44 | public abstract class RetryingTest {
45 |
46 | @ClassRule
47 | public static WireMockClassRule wireMockRule = new WireMockClassRule(
48 | wireMockConfig().dynamicPort());
49 |
50 | abstract protected ReactiveFeign.Builder builder();
51 |
52 | @Before
53 | public void resetServers() {
54 | wireMockRule.resetAll();
55 | }
56 |
57 | @Test
58 | public void shouldSuccessOnRetriesMono() throws JsonProcessingException {
59 |
60 | IceCreamOrder orderGenerated = new OrderGenerator().generate(1);
61 | String orderStr = TestUtils.MAPPER.writeValueAsString(orderGenerated);
62 |
63 | mockResponseAfterSeveralAttempts(wireMockRule, 2, "testRetrying_success",
64 | "/icecream/orders/1",
65 | aResponse().withStatus(503).withHeader(RETRY_AFTER, "1"),
66 | aResponse().withStatus(200).withHeader("Content-Type", "application/json")
67 | .withBody(orderStr));
68 |
69 | IcecreamServiceApi client = builder()
70 | .retryWhen(ReactiveRetryers.retryWithBackoff(3, 0))
71 | .target(IcecreamServiceApi.class,
72 | "http://localhost:" + wireMockRule.port());
73 |
74 | StepVerifier.create(client.findOrder(1))
75 | .expectNextMatches(equalsComparingFieldByFieldRecursively(orderGenerated))
76 | .verifyComplete();
77 | }
78 |
79 | @Test
80 | public void shouldSuccessOnRetriesFlux() throws JsonProcessingException {
81 |
82 | String mixinsStr = TestUtils.MAPPER.writeValueAsString(Mixin.values());
83 |
84 | mockResponseAfterSeveralAttempts(wireMockRule, 2, "testRetrying_success",
85 | "/icecream/mixins",
86 | aResponse().withStatus(SC_SERVICE_UNAVAILABLE).withHeader(RETRY_AFTER,
87 | "1"),
88 | aResponse().withStatus(SC_OK)
89 | .withHeader("Content-Type", "application/json")
90 | .withBody(mixinsStr));
91 |
92 | IcecreamServiceApi client = builder()
93 | .retryWhen(ReactiveRetryers.retryWithBackoff(3, 0))
94 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
95 |
96 | StepVerifier.create(client.getAvailableMixins())
97 | .expectNextSequence(Arrays.asList(Mixin.values()))
98 | .verifyComplete();
99 | }
100 |
101 | @Test
102 | public void shouldSuccessOnRetriesWoRetryAfter() throws JsonProcessingException {
103 |
104 | IceCreamOrder orderGenerated = new OrderGenerator().generate(1);
105 | String orderStr = TestUtils.MAPPER.writeValueAsString(orderGenerated);
106 |
107 | mockResponseAfterSeveralAttempts(wireMockRule, 2, "testRetrying_success",
108 | "/icecream/orders/1", aResponse().withStatus(SC_SERVICE_UNAVAILABLE),
109 | aResponse().withStatus(SC_OK)
110 | .withHeader("Content-Type", "application/json")
111 | .withBody(orderStr));
112 |
113 | IcecreamServiceApi client = builder()
114 | .retryWhen(ReactiveRetryers.retryWithBackoff(3, 0))
115 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
116 |
117 | StepVerifier.create(client.findOrder(1))
118 | .expectNextMatches(equalsComparingFieldByFieldRecursively(orderGenerated))
119 | .verifyComplete();
120 | }
121 |
122 | private static void mockResponseAfterSeveralAttempts(WireMockClassRule rule,
123 | int failedAttemptsNo,
124 | String scenario,
125 | String url,
126 | ResponseDefinitionBuilder failResponse,
127 | ResponseDefinitionBuilder response) {
128 | String state = STARTED;
129 | for (int attempt = 0; attempt < failedAttemptsNo; attempt++) {
130 | String nextState = "attempt" + attempt;
131 | rule.stubFor(
132 | get(urlEqualTo(url)).withHeader("Accept", equalTo("application/json"))
133 | .inScenario(scenario).whenScenarioStateIs(state)
134 | .willReturn(failResponse).willSetStateTo(nextState));
135 |
136 | state = nextState;
137 | }
138 |
139 | rule.stubFor(get(urlEqualTo(url))
140 | .withHeader("Accept", equalTo("application/json")).inScenario(scenario)
141 | .whenScenarioStateIs(state).willReturn(response));
142 | }
143 |
144 | @Test
145 | public void shouldFailAsNoMoreRetries() {
146 |
147 | String orderUrl = "/icecream/orders/1";
148 |
149 | wireMockRule.stubFor(get(urlEqualTo(orderUrl))
150 | .withHeader("Accept", equalTo("application/json"))
151 | .willReturn(aResponse().withStatus(503).withHeader(RETRY_AFTER, "1")));
152 |
153 | IcecreamServiceApi client = builder()
154 | .retryWhen(ReactiveRetryers.retry(3))
155 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
156 |
157 | StepVerifier.create(client.findOrder(1))
158 | .expectErrorMatches(throwable -> throwable instanceof RetryReactiveHttpClient.OutOfRetriesException
159 | && hasProperty("cause", isA(RetryableException.class)).matches(throwable))
160 | .verify();
161 | }
162 |
163 | @Test
164 | public void shouldFailAsNoMoreRetriesWithBackoff() {
165 |
166 | String orderUrl = "/icecream/orders/1";
167 |
168 | wireMockRule.stubFor(get(urlEqualTo(orderUrl))
169 | .withHeader("Accept", equalTo("application/json"))
170 | .willReturn(aResponse().withStatus(503).withHeader(RETRY_AFTER, "1")));
171 |
172 | IcecreamServiceApi client = builder()
173 | .retryWhen(ReactiveRetryers.retryWithBackoff(3, 5))
174 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
175 |
176 | StepVerifier.create(client.findOrder(1))
177 | .expectErrorMatches(throwable -> throwable instanceof RetryReactiveHttpClient.OutOfRetriesException
178 | && hasProperty("cause", isA(RetryableException.class)).matches(throwable))
179 | .verify();
180 | }
181 |
182 | }
183 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor;
15 |
16 | import com.fasterxml.jackson.core.JsonProcessingException;
17 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
18 | import feign.reactor.testcase.IcecreamServiceApi;
19 | import feign.reactor.testcase.domain.*;
20 | import org.junit.Before;
21 | import org.junit.ClassRule;
22 | import org.junit.Rule;
23 | import org.junit.Test;
24 | import org.junit.rules.ExpectedException;
25 | import reactor.core.publisher.Flux;
26 | import reactor.core.publisher.Mono;
27 | import reactor.test.StepVerifier;
28 | import java.util.Map;
29 | import java.util.stream.Collectors;
30 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
31 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
32 | import static feign.reactor.TestUtils.equalsComparingFieldByFieldRecursively;
33 | import static java.util.Arrays.asList;
34 |
35 | /**
36 | * @author Sergii Karpenko
37 | */
38 |
39 | abstract public class SmokeTest {
40 |
41 | @ClassRule
42 | public static WireMockClassRule wireMockRule = new WireMockClassRule(
43 | wireMockConfig().dynamicPort());
44 |
45 | @Before
46 | public void resetServers() {
47 | wireMockRule.resetAll();
48 | }
49 |
50 | abstract protected ReactiveFeign.Builder builder();
51 |
52 | private IcecreamServiceApi client;
53 |
54 | private OrderGenerator generator = new OrderGenerator();
55 | private Map orders = generator.generateRange(10).stream()
56 | .collect(Collectors.toMap(IceCreamOrder::getId, o -> o));
57 |
58 | @Rule
59 | public ExpectedException expectedException = ExpectedException.none();
60 |
61 | @Before
62 | public void setUp() {
63 | String targetUrl = "http://localhost:" + wireMockRule.port();
64 | client = builder()
65 | .decode404()
66 | .target(IcecreamServiceApi.class, targetUrl);
67 | }
68 |
69 | @Test
70 | public void testSimpleGet_success() throws JsonProcessingException {
71 |
72 | wireMockRule.stubFor(get(urlEqualTo("/icecream/flavors"))
73 | .willReturn(aResponse().withStatus(200)
74 | .withHeader("Content-Type", "application/json")
75 | .withBody(TestUtils.MAPPER.writeValueAsString(Flavor.values()))));
76 |
77 | wireMockRule.stubFor(get(urlEqualTo("/icecream/mixins"))
78 | .willReturn(aResponse().withStatus(200)
79 | .withHeader("Content-Type", "application/json")
80 | .withBody(TestUtils.MAPPER.writeValueAsString(Mixin.values()))));
81 |
82 | Flux flavors = client.getAvailableFlavors();
83 | Flux mixins = client.getAvailableMixins();
84 |
85 | StepVerifier.create(flavors)
86 | .expectNextSequence(asList(Flavor.values()))
87 | .verifyComplete();
88 | StepVerifier.create(mixins)
89 | .expectNextSequence(asList(Mixin.values()))
90 | .verifyComplete();
91 |
92 | }
93 |
94 | @Test
95 | public void testFindOrder_success() throws JsonProcessingException {
96 |
97 | IceCreamOrder orderExpected = orders.get(1);
98 | wireMockRule.stubFor(get(urlEqualTo("/icecream/orders/1"))
99 | .willReturn(aResponse().withStatus(200)
100 | .withHeader("Content-Type", "application/json")
101 | .withBody(TestUtils.MAPPER.writeValueAsString(orderExpected))));
102 |
103 | Mono order = client.findOrder(1);
104 |
105 | StepVerifier.create(order)
106 | .expectNextMatches(equalsComparingFieldByFieldRecursively(orderExpected))
107 | .verifyComplete();
108 | }
109 |
110 | @Test
111 | public void testFindOrder_empty() {
112 |
113 | Mono orderEmpty = client.findOrder(123);
114 |
115 | StepVerifier.create(orderEmpty)
116 | .expectNextCount(0)
117 | .verifyComplete();
118 | }
119 |
120 | @Test
121 | public void testMakeOrder_success() throws JsonProcessingException {
122 |
123 | IceCreamOrder order = new OrderGenerator().generate(20);
124 | Bill billExpected = Bill.makeBill(order);
125 |
126 | wireMockRule.stubFor(post(urlEqualTo("/icecream/orders"))
127 | .withRequestBody(equalTo(TestUtils.MAPPER.writeValueAsString(order)))
128 | .willReturn(aResponse().withStatus(200)
129 | .withHeader("Content-Type", "application/json")
130 | .withBody(TestUtils.MAPPER.writeValueAsString(billExpected))));
131 |
132 | Mono bill = client.makeOrder(order);
133 | StepVerifier.create(bill)
134 | .expectNextMatches(equalsComparingFieldByFieldRecursively(billExpected))
135 | .verifyComplete();
136 | }
137 |
138 | @Test
139 | public void testPayBill_success() {
140 |
141 | Bill bill = Bill.makeBill(new OrderGenerator().generate(30));
142 |
143 | Mono result = client.payBill(bill);
144 | StepVerifier.create(result)
145 | .verifyComplete();
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor;
15 |
16 | import com.github.tomakehurst.wiremock.junit.WireMockClassRule;
17 | import feign.RetryableException;
18 | import feign.reactor.testcase.IcecreamServiceApi;
19 | import org.apache.http.HttpStatus;
20 | import org.junit.Before;
21 | import org.junit.ClassRule;
22 | import org.junit.Test;
23 | import reactor.test.StepVerifier;
24 | import static com.github.tomakehurst.wiremock.client.WireMock.*;
25 | import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
26 | import static feign.reactor.client.statushandler.CompositeStatusHandler.compose;
27 | import static feign.reactor.client.statushandler.ReactiveStatusHandlers.throwOnStatus;
28 |
29 | /**
30 | * @author Sergii Karpenko
31 | */
32 | public abstract class StatusHandlerTest {
33 |
34 | @ClassRule
35 | public static WireMockClassRule wireMockRule = new WireMockClassRule(
36 | wireMockConfig().dynamicPort());
37 |
38 | abstract protected ReactiveFeign.Builder builder();
39 |
40 | @Before
41 | public void resetServers() {
42 | wireMockRule.resetAll();
43 | }
44 |
45 | @Test
46 | public void shouldThrowRetryException() {
47 |
48 | wireMockRule.stubFor(get(urlEqualTo("/icecream/orders/1"))
49 | .withHeader("Accept", equalTo("application/json"))
50 | .willReturn(aResponse().withStatus(HttpStatus.SC_SERVICE_UNAVAILABLE)));
51 | IcecreamServiceApi client = builder()
52 | .statusHandler(throwOnStatus(
53 | status -> status == HttpStatus.SC_SERVICE_UNAVAILABLE,
54 | (methodTag, response) -> new RetryableException("Should retry on next node", null)))
55 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
56 |
57 | StepVerifier.create(client.findFirstOrder())
58 | .expectError(RetryableException.class);
59 | }
60 |
61 | @Test
62 | public void shouldThrowOnStatusCode() {
63 | wireMockRule.stubFor(get(urlEqualTo("/icecream/orders/1"))
64 | .withHeader("Accept", equalTo("application/json"))
65 | .willReturn(aResponse().withStatus(HttpStatus.SC_SERVICE_UNAVAILABLE)));
66 |
67 | wireMockRule.stubFor(get(urlEqualTo("/icecream/orders/2"))
68 | .withHeader("Accept", equalTo("application/json"))
69 | .willReturn(aResponse().withStatus(HttpStatus.SC_UNAUTHORIZED)));
70 |
71 |
72 | IcecreamServiceApi client = builder()
73 | .statusHandler(compose(
74 | throwOnStatus(
75 | status -> status == HttpStatus.SC_SERVICE_UNAVAILABLE,
76 | (methodTag, response) -> new RetryableException("Should retry on next node", null)),
77 | throwOnStatus(
78 | status -> status == HttpStatus.SC_UNAUTHORIZED,
79 | (methodTag, response) -> new RuntimeException("Should login", null))))
80 | .target(IcecreamServiceApi.class, "http://localhost:" + wireMockRule.port());
81 |
82 | StepVerifier.create(client.findFirstOrder())
83 | .expectError(RetryableException.class)
84 | .verify();
85 |
86 | StepVerifier.create(client.findOrder(2))
87 | .expectError(RuntimeException.class)
88 | .verify();
89 |
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor;
15 |
16 | import com.fasterxml.jackson.core.JsonProcessingException;
17 | import com.fasterxml.jackson.databind.ObjectMapper;
18 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
19 | import java.util.function.Predicate;
20 |
21 | /**
22 | * Helper methods for tests.
23 | */
24 | class TestUtils {
25 | static final ObjectMapper MAPPER;
26 |
27 | static {
28 | MAPPER = new ObjectMapper();
29 | MAPPER.registerModule(new JavaTimeModule());
30 | }
31 |
32 | public static Predicate equalsComparingFieldByFieldRecursively(T rhs) {
33 | return lhs -> {
34 | try {
35 | return MAPPER.writeValueAsString(lhs).equals(MAPPER.writeValueAsString(rhs));
36 | } catch (JsonProcessingException e) {
37 | throw new RuntimeException(e);
38 | }
39 | };
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor.testcase;
15 |
16 | import feign.Headers;
17 | import feign.Param;
18 | import feign.RequestLine;
19 | import feign.reactor.testcase.domain.Bill;
20 | import feign.reactor.testcase.domain.Flavor;
21 | import feign.reactor.testcase.domain.IceCreamOrder;
22 | import feign.reactor.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 throwExceptionMono() {
58 | throw RUNTIME_EXCEPTION;
59 | }
60 |
61 | default Flux throwExceptionFlux() {
62 | throw RUNTIME_EXCEPTION;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor.testcase;
15 |
16 | import feign.Headers;
17 | import feign.Param;
18 | import feign.RequestLine;
19 | import feign.reactor.ReactiveDelegatingContract;
20 | import feign.reactor.testcase.domain.Bill;
21 | import feign.reactor.testcase.domain.Flavor;
22 | import feign.reactor.testcase.domain.IceCreamOrder;
23 | import feign.reactor.testcase.domain.Mixin;
24 | import reactor.core.publisher.Flux;
25 | import reactor.core.publisher.Mono;
26 | import java.util.Collection;
27 |
28 | /**
29 | * API of an iceream web service with one method that doesn't returns {@link Mono} or {@link Flux}
30 | * and violates {@link ReactiveDelegatingContract}s rules.
31 | *
32 | * @author Sergii Karpenko
33 | */
34 | public interface IcecreamServiceApiBroken {
35 |
36 | @RequestLine("GET /icecream/flavors")
37 | Mono> getAvailableFlavors();
38 |
39 | @RequestLine("GET /icecream/mixins")
40 | Mono> getAvailableMixins();
41 |
42 | @RequestLine("POST /icecream/orders")
43 | @Headers("Content-Type: application/json")
44 | Mono makeOrder(IceCreamOrder order);
45 |
46 | /**
47 | * Method that doesn't respects contract.
48 | */
49 | @RequestLine("GET /icecream/orders/{orderId}")
50 | IceCreamOrder findOrder(@Param("orderId") int orderId);
51 | }
52 |
--------------------------------------------------------------------------------
/feign-reactor-core/src/test/java/feign/reactor/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 feign.reactor.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/feign/reactor/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 feign.reactor.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/feign/reactor/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 feign.reactor.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/feign/reactor/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 feign.reactor.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/feign/reactor/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 feign.reactor.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-fake-impl/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
20 | 4.0.0
21 |
22 |
23 | io.github.openfeign.incubating
24 | feign-reactor
25 | 0.1.0
26 |
27 |
28 | feign-reactor-fake-impl
29 | jar
30 | Feign Reactive Fake Implementation
31 |
32 |
33 | ${project.basedir}/..
34 |
35 | 5.0.5.RELEASE
36 |
37 |
38 |
39 |
40 |
41 | io.github.openfeign.incubating
42 | feign-reactor-core
43 | 0.1.0
44 | test
45 |
46 |
47 |
48 | io.github.openfeign.incubating
49 | feign-reactor-core
50 | 0.1.0
51 | test-jar
52 | test
53 |
54 |
55 |
56 | io.projectreactor
57 | reactor-core
58 |
59 |
60 |
61 | io.github.openfeign
62 | feign-core
63 |
64 |
65 |
66 | org.slf4j
67 | slf4j-api
68 |
69 |
70 | commons-httpclient
71 | commons-httpclient
72 |
73 |
74 |
75 |
76 | io.projectreactor
77 | reactor-test
78 | test
79 |
80 |
81 | junit
82 | junit
83 | test
84 |
85 |
86 |
87 | org.assertj
88 | assertj-core
89 | test
90 |
91 |
92 |
93 | com.github.tomakehurst
94 | wiremock
95 | test
96 |
97 |
98 |
99 | org.awaitility
100 | awaitility
101 | test
102 |
103 |
104 |
105 | org.hamcrest
106 | hamcrest-library
107 | test
108 |
109 |
110 |
111 |
112 | org.springframework
113 | spring-core
114 | ${spring.framework.version}
115 | test
116 |
117 |
118 | org.springframework
119 | spring-context
120 | ${spring.framework.version}
121 | test
122 |
123 |
124 | org.springframework
125 | spring-web
126 | ${spring.framework.version}
127 | test
128 |
129 |
130 | com.fasterxml.jackson.core
131 | jackson-core
132 | ${jackson.version}
133 | test
134 |
135 |
136 | com.fasterxml.jackson.core
137 | jackson-databind
138 | ${jackson.version}
139 | test
140 |
141 |
142 | com.fasterxml.jackson.core
143 | jackson-annotations
144 | ${jackson.version}
145 | test
146 |
147 |
148 | com.fasterxml.jackson.datatype
149 | jackson-datatype-jsr310
150 | test
151 |
152 |
153 | org.apache.logging.log4j
154 | log4j-slf4j-impl
155 | ${log4j.version}
156 | test
157 |
158 |
159 | org.mockito
160 | mockito-all
161 | test
162 |
163 |
164 |
165 |
166 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.ReactiveOptions;
18 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
19 | import feign.reactor.testcase.IcecreamServiceApi;
20 |
21 | /**
22 | * @author Sergii Karpenko
23 | */
24 | public class CompressionTest extends feign.reactor.CompressionTest {
25 |
26 | @Override
27 | protected ReactiveFeign.Builder builder(ReactiveOptions options) {
28 | return RestTemplateFakeReactiveFeign.builder(options);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.ReactiveOptions;
18 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
19 | import feign.reactor.testcase.IcecreamServiceApi;
20 |
21 | /**
22 | * @author Sergii Karpenko
23 | */
24 | public class ConnectionTimeoutTest extends feign.reactor.ConnectionTimeoutTest {
25 |
26 | @Override
27 | protected ReactiveFeign.Builder builder(ReactiveOptions options) {
28 | return RestTemplateFakeReactiveFeign.builder(options);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
18 |
19 | /**
20 | * @author Sergii Karpenko
21 | */
22 | public class ContractTest extends feign.reactor.ContractTest {
23 |
24 | @Override
25 | protected ReactiveFeign.Builder builder() {
26 | return RestTemplateFakeReactiveFeign.builder();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.ReactiveOptions;
18 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
19 | import feign.reactor.testcase.IcecreamServiceApi;
20 |
21 | /**
22 | * @author Sergii Karpenko
23 | */
24 | public class DefaultMethodTest extends feign.reactor.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);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
18 | import feign.reactor.testcase.IcecreamServiceApi;
19 |
20 | /**
21 | * @author Sergii Karpenko
22 | */
23 | public class LoggerTest extends feign.reactor.LoggerTest {
24 |
25 | @Override
26 | protected ReactiveFeign.Builder builder() {
27 | return RestTemplateFakeReactiveFeign.builder();
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
18 | import feign.reactor.testcase.IcecreamServiceApi;
19 |
20 | /**
21 | * @author Sergii Karpenko
22 | */
23 | public class NotFoundTest extends feign.reactor.NotFoundTest {
24 |
25 | @Override
26 | protected ReactiveFeign.Builder builder() {
27 | return RestTemplateFakeReactiveFeign.builder();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import com.fasterxml.jackson.core.JsonProcessingException;
17 | import feign.reactor.ReactiveFeign;
18 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
19 | import feign.reactor.testcase.IcecreamServiceApi;
20 | import org.awaitility.core.ConditionTimeoutException;
21 | import org.junit.Test;
22 |
23 | public class ReactivityTest extends feign.reactor.ReactivityTest {
24 |
25 | @Override
26 | protected ReactiveFeign.Builder builder() {
27 | return RestTemplateFakeReactiveFeign.builder();
28 | }
29 |
30 | @Test(expected = ConditionTimeoutException.class)
31 | @Override
32 | public void shouldRunReactively() throws JsonProcessingException {
33 | super.shouldRunReactively();
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.ReactiveOptions;
18 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
19 | import feign.reactor.testcase.IcecreamServiceApi;
20 |
21 | /**
22 | * @author Sergii Karpenko
23 | */
24 | public class ReadTimeoutTest extends feign.reactor.ReadTimeoutTest {
25 |
26 | @Override
27 | protected ReactiveFeign.Builder builder(ReactiveOptions options) {
28 | return RestTemplateFakeReactiveFeign.builder(options);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
18 | import feign.reactor.testcase.IcecreamServiceApi;
19 |
20 | /**
21 | * @author Sergii Karpenko
22 | */
23 | public class RequestInterceptorTest extends feign.reactor.RequestInterceptorTest {
24 |
25 | @Override
26 | protected ReactiveFeign.Builder builder() {
27 | return RestTemplateFakeReactiveFeign.builder();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
18 | import feign.reactor.testcase.IcecreamServiceApi;
19 |
20 | /**
21 | * @author Sergii Karpenko
22 | */
23 | public class RetryingTest extends feign.reactor.RetryingTest {
24 |
25 | @Override
26 | protected ReactiveFeign.Builder builder() {
27 | return RestTemplateFakeReactiveFeign.builder();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
18 | import feign.reactor.testcase.IcecreamServiceApi;
19 |
20 | /**
21 | * @author Sergii Karpenko
22 | */
23 | public class SmokeTest extends feign.reactor.SmokeTest {
24 |
25 | @Override
26 | protected ReactiveFeign.Builder builder() {
27 | return RestTemplateFakeReactiveFeign.builder();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.resttemplate.client.RestTemplateFakeReactiveFeign;
18 | import feign.reactor.testcase.IcecreamServiceApi;
19 |
20 | /**
21 | * @author Sergii Karpenko
22 | */
23 | public class StatusHandlerTest extends feign.reactor.StatusHandlerTest {
24 |
25 | @Override
26 | protected ReactiveFeign.Builder builder() {
27 | return RestTemplateFakeReactiveFeign.builder();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/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 feign.reactor.resttemplate.client;
15 |
16 | import feign.reactor.ReactiveFeign;
17 | import feign.reactor.ReactiveOptions;
18 | import feign.reactor.client.ReactiveHttpClient;
19 | import org.apache.http.impl.client.HttpClientBuilder;
20 | import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
21 | import org.springframework.web.client.RestTemplate;
22 |
23 | /**
24 | * {@link RestTemplate} based implementation
25 | *
26 | * @author Sergii Karpenko
27 | */
28 | public class RestTemplateFakeReactiveFeign {
29 |
30 | public static ReactiveFeign.Builder builder() {
31 | return builder(new RestTemplate(), false);
32 | }
33 |
34 | public static ReactiveFeign.Builder builder(ReactiveOptions options) {
35 | HttpComponentsClientHttpRequestFactory requestFactory =
36 | new HttpComponentsClientHttpRequestFactory(
37 | HttpClientBuilder.create().build());
38 | if (options.getConnectTimeoutMillis() != null) {
39 | requestFactory.setConnectTimeout(options.getConnectTimeoutMillis());
40 | }
41 | if (options.getReadTimeoutMillis() != null) {
42 | requestFactory.setReadTimeout(options.getReadTimeoutMillis());
43 | }
44 | return builder(new RestTemplate(requestFactory),
45 | options.isTryUseCompression() != null && options.isTryUseCompression());
46 | }
47 |
48 | public static ReactiveFeign.Builder builder(RestTemplate restTemplate,
49 | boolean acceptGzip) {
50 | return new ReactiveFeign.Builder(){}.clientFactory(
51 | methodMetadata -> new RestTemplateFakeReactiveHttpClient(
52 | methodMetadata, restTemplate, acceptGzip));
53 | }
54 | }
55 |
56 |
57 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/java/feign/reactor/resttemplate/client/RestTemplateFakeReactiveHttpClient.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 feign.reactor.resttemplate.client;
15 |
16 | import com.fasterxml.jackson.databind.ObjectMapper;
17 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
18 | import feign.MethodMetadata;
19 | import feign.reactor.client.ReactiveHttpClient;
20 | import feign.reactor.client.ReactiveHttpRequest;
21 | import feign.reactor.client.ReactiveHttpResponse;
22 | import feign.reactor.client.ReadTimeoutException;
23 | import org.reactivestreams.Publisher;
24 | import org.springframework.core.ParameterizedTypeReference;
25 | import org.springframework.http.HttpEntity;
26 | import org.springframework.http.HttpMethod;
27 | import org.springframework.http.ResponseEntity;
28 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
29 | import org.springframework.util.LinkedMultiValueMap;
30 | import org.springframework.util.MultiValueMap;
31 | import org.springframework.web.client.HttpStatusCodeException;
32 | import org.springframework.web.client.ResourceAccessException;
33 | import org.springframework.web.client.RestTemplate;
34 | import reactor.core.publisher.Flux;
35 | import reactor.core.publisher.Mono;
36 |
37 | import java.lang.reflect.ParameterizedType;
38 | import java.lang.reflect.Type;
39 | import java.net.SocketTimeoutException;
40 | import java.util.List;
41 | import java.util.Map;
42 |
43 | import static feign.Util.resolveLastTypeParameter;
44 | import static org.springframework.core.ParameterizedTypeReference.forType;
45 |
46 | public class RestTemplateFakeReactiveHttpClient implements ReactiveHttpClient {
47 |
48 | private final RestTemplate restTemplate;
49 | private final boolean acceptGzip;
50 | private final Type returnPublisherType;
51 | private final ParameterizedTypeReference returnActualType;
52 |
53 | RestTemplateFakeReactiveHttpClient(MethodMetadata methodMetadata,
54 | RestTemplate restTemplate,
55 | boolean acceptGzip) {
56 | this.restTemplate = restTemplate;
57 | this.acceptGzip = acceptGzip;
58 |
59 | ObjectMapper mapper = new ObjectMapper();
60 | mapper.registerModule(new JavaTimeModule());
61 |
62 | MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
63 | converter.setObjectMapper(mapper);
64 | restTemplate.getMessageConverters().add(0, converter);
65 |
66 |
67 | final Type returnType = methodMetadata.returnType();
68 | returnPublisherType = ((ParameterizedType) returnType).getRawType();
69 | returnActualType = forType(
70 | resolveLastTypeParameter(returnType, (Class>) returnPublisherType));
71 | }
72 |
73 | @Override
74 | public Mono executeRequest(ReactiveHttpRequest request) {
75 |
76 | Mono bodyMono;
77 | if (request.body() instanceof Mono) {
78 | bodyMono = ((Mono) request.body());
79 | } else if (request.body() instanceof Flux) {
80 | bodyMono = ((Flux) request.body()).collectList();
81 | } else {
82 | bodyMono = Mono.just(request.body());
83 | }
84 | bodyMono = bodyMono.switchIfEmpty(Mono.just(new byte[0]));
85 |
86 | return bodyMono.flatMap(body -> {
87 | MultiValueMap headers = new LinkedMultiValueMap<>(request.headers());
88 | if (acceptGzip) {
89 | headers.add("Accept-Encoding", "gzip");
90 | }
91 |
92 | ResponseEntity response =
93 | restTemplate.exchange(request.uri().toString(), HttpMethod.valueOf(request.method()),
94 | new HttpEntity<>(body, headers), responseType());
95 |
96 | return Mono.just(new FakeReactiveHttpResponse(response, returnPublisherType));
97 | })
98 | .onErrorMap(ex -> ex instanceof ResourceAccessException
99 | && ex.getCause() instanceof SocketTimeoutException,
100 | ReadTimeoutException::new)
101 | .onErrorResume(HttpStatusCodeException.class,
102 | ex -> Mono.just(new ErrorReactiveHttpResponse(ex)));
103 | }
104 |
105 | private ParameterizedTypeReference responseType(){
106 | if (returnPublisherType == Mono.class) {
107 | return returnActualType;
108 | } else {
109 | return forType(new ParameterizedType() {
110 | @Override
111 | public Type[] getActualTypeArguments() {
112 | return new Type[] {returnActualType.getType()};
113 | }
114 |
115 | @Override
116 | public Type getRawType() {
117 | return List.class;
118 | }
119 |
120 | @Override
121 | public Type getOwnerType() {
122 | return null;
123 | }
124 | });
125 | }
126 | }
127 |
128 | private static class FakeReactiveHttpResponse implements ReactiveHttpResponse{
129 |
130 | private final ResponseEntity response;
131 | private final Type returnPublisherType;
132 |
133 | private FakeReactiveHttpResponse(ResponseEntity response, Type returnPublisherType) {
134 | this.response = response;
135 | this.returnPublisherType = returnPublisherType;
136 | }
137 |
138 | @Override
139 | public int status() {
140 | return response.getStatusCodeValue();
141 | }
142 |
143 | @Override
144 | public Map> headers() {
145 | return response.getHeaders();
146 | }
147 |
148 | @Override
149 | public Publisher body() {
150 | if (returnPublisherType == Mono.class) {
151 | return Mono.just(response.getBody());
152 | } else {
153 | return Flux.fromIterable((List)response.getBody());
154 | }
155 | }
156 |
157 | @Override
158 | public Mono bodyData() {
159 | return Mono.just(new byte[0]);
160 | }
161 | }
162 |
163 | private static class ErrorReactiveHttpResponse implements ReactiveHttpResponse{
164 |
165 | private final HttpStatusCodeException ex;
166 |
167 | private ErrorReactiveHttpResponse(HttpStatusCodeException ex) {
168 | this.ex = ex;
169 | }
170 |
171 | @Override
172 | public int status() {
173 | return ex.getStatusCode().value();
174 | }
175 |
176 | @Override
177 | public Map> headers() {
178 | return ex.getResponseHeaders();
179 | }
180 |
181 | @Override
182 | public Publisher body() {
183 | return Mono.empty();
184 | }
185 |
186 | @Override
187 | public Mono bodyData() {
188 | return Mono.just(ex.getResponseBodyAsByteArray());
189 | }
190 | }
191 | }
192 |
--------------------------------------------------------------------------------
/feign-reactor-fake-impl/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.rootLogger=TRACE, stdout
2 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender
3 | log4j.appender.stdout.Target=System.out
4 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
5 | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | #
58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look
59 | # for the new JDKs provided by Oracle.
60 | #
61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
62 | #
63 | # Apple JDKs
64 | #
65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
66 | fi
67 |
68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
69 | #
70 | # Apple JDKs
71 | #
72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
73 | fi
74 |
75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
76 | #
77 | # Oracle JDKs
78 | #
79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
80 | fi
81 |
82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
83 | #
84 | # Apple JDKs
85 | #
86 | export JAVA_HOME=`/usr/libexec/java_home`
87 | fi
88 | ;;
89 | esac
90 |
91 | if [ -z "$JAVA_HOME" ] ; then
92 | if [ -r /etc/gentoo-release ] ; then
93 | JAVA_HOME=`java-config --jre-home`
94 | fi
95 | fi
96 |
97 | if [ -z "$M2_HOME" ] ; then
98 | ## resolve links - $0 may be a link to maven's home
99 | PRG="$0"
100 |
101 | # need this for relative symlinks
102 | while [ -h "$PRG" ] ; do
103 | ls=`ls -ld "$PRG"`
104 | link=`expr "$ls" : '.*-> \(.*\)$'`
105 | if expr "$link" : '/.*' > /dev/null; then
106 | PRG="$link"
107 | else
108 | PRG="`dirname "$PRG"`/$link"
109 | fi
110 | done
111 |
112 | saveddir=`pwd`
113 |
114 | M2_HOME=`dirname "$PRG"`/..
115 |
116 | # make it fully qualified
117 | M2_HOME=`cd "$M2_HOME" && pwd`
118 |
119 | cd "$saveddir"
120 | # echo Using m2 at $M2_HOME
121 | fi
122 |
123 | # For Cygwin, ensure paths are in UNIX format before anything is touched
124 | if $cygwin ; then
125 | [ -n "$M2_HOME" ] &&
126 | M2_HOME=`cygpath --unix "$M2_HOME"`
127 | [ -n "$JAVA_HOME" ] &&
128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
129 | [ -n "$CLASSPATH" ] &&
130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
131 | fi
132 |
133 | # For Migwn, ensure paths are in UNIX format before anything is touched
134 | if $mingw ; then
135 | [ -n "$M2_HOME" ] &&
136 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
137 | [ -n "$JAVA_HOME" ] &&
138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
139 | # TODO classpath?
140 | fi
141 |
142 | if [ -z "$JAVA_HOME" ]; then
143 | javaExecutable="`which javac`"
144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
145 | # readlink(1) is not available as standard on Solaris 10.
146 | readLink=`which readlink`
147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
148 | if $darwin ; then
149 | javaHome="`dirname \"$javaExecutable\"`"
150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
151 | else
152 | javaExecutable="`readlink -f \"$javaExecutable\"`"
153 | fi
154 | javaHome="`dirname \"$javaExecutable\"`"
155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
156 | JAVA_HOME="$javaHome"
157 | export JAVA_HOME
158 | fi
159 | fi
160 | fi
161 |
162 | if [ -z "$JAVACMD" ] ; then
163 | if [ -n "$JAVA_HOME" ] ; then
164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
165 | # IBM's JDK on AIX uses strange locations for the executables
166 | JAVACMD="$JAVA_HOME/jre/sh/java"
167 | else
168 | JAVACMD="$JAVA_HOME/bin/java"
169 | fi
170 | else
171 | JAVACMD="`which java`"
172 | fi
173 | fi
174 |
175 | if [ ! -x "$JAVACMD" ] ; then
176 | echo "Error: JAVA_HOME is not defined correctly." >&2
177 | echo " We cannot execute $JAVACMD" >&2
178 | exit 1
179 | fi
180 |
181 | if [ -z "$JAVA_HOME" ] ; then
182 | echo "Warning: JAVA_HOME environment variable is not set."
183 | fi
184 |
185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
186 |
187 | # traverses directory structure from process work directory to filesystem root
188 | # first directory with .mvn subdirectory is considered project base directory
189 | find_maven_basedir() {
190 | local basedir=$(pwd)
191 | local wdir=$(pwd)
192 | while [ "$wdir" != '/' ] ; do
193 | if [ -d "$wdir"/.mvn ] ; then
194 | basedir=$wdir
195 | break
196 | fi
197 | wdir=$(cd "$wdir/.."; pwd)
198 | done
199 | echo "${basedir}"
200 | }
201 |
202 | # concatenates all lines of a file
203 | concat_lines() {
204 | if [ -f "$1" ]; then
205 | echo "$(tr -s '\n' ' ' < "$1")"
206 | fi
207 | }
208 |
209 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
210 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
211 |
212 | # For Cygwin, switch paths to Windows format before running java
213 | if $cygwin; then
214 | [ -n "$M2_HOME" ] &&
215 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
216 | [ -n "$JAVA_HOME" ] &&
217 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
218 | [ -n "$CLASSPATH" ] &&
219 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
220 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
221 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
222 | fi
223 |
224 | # Provide a "standardized" way to retrieve the CLI args that will
225 | # work with both Windows and non-Windows executions.
226 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
227 | export MAVEN_CMD_LINE_ARGS
228 |
229 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
230 |
231 | # avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in $@
232 | exec "$JAVACMD" \
233 | $MAVEN_OPTS \
234 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
235 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
236 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
237 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
40 |
41 | @REM set %HOME% to equivalent of $HOME
42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43 |
44 | @REM Execute a user defined script before this one
45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49 | :skipRcPre
50 |
51 | @setlocal
52 |
53 | set ERROR_CODE=0
54 |
55 | @REM To isolate internal variables from possible post scripts, we use another setlocal
56 | @setlocal
57 |
58 | @REM ==== START VALIDATION ====
59 | if not "%JAVA_HOME%" == "" goto OkJHome
60 |
61 | echo.
62 | echo Error: JAVA_HOME not found in your environment. >&2
63 | echo Please set the JAVA_HOME variable in your environment to match the >&2
64 | echo location of your Java installation. >&2
65 | echo.
66 | goto error
67 |
68 | :OkJHome
69 | if exist "%JAVA_HOME%\bin\java.exe" goto init
70 |
71 | echo.
72 | echo Error: JAVA_HOME is set to an invalid directory. >&2
73 | echo JAVA_HOME = "%JAVA_HOME%" >&2
74 | echo Please set the JAVA_HOME variable in your environment to match the >&2
75 | echo location of your Java installation. >&2
76 | echo.
77 | goto error
78 |
79 | @REM ==== END VALIDATION ====
80 |
81 | :init
82 |
83 | set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %*
84 |
85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
86 | @REM Fallback to current working directory if not found.
87 |
88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
90 |
91 | set EXEC_DIR=%CD%
92 | set WDIR=%EXEC_DIR%
93 | :findBaseDir
94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
95 | cd ..
96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
97 | set WDIR=%CD%
98 | goto findBaseDir
99 |
100 | :baseDirFound
101 | set MAVEN_PROJECTBASEDIR=%WDIR%
102 | cd "%EXEC_DIR%"
103 | goto endDetectBaseDir
104 |
105 | :baseDirNotFound
106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
107 | cd "%EXEC_DIR%"
108 |
109 | :endDetectBaseDir
110 |
111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
112 |
113 | @setlocal EnableExtensions EnableDelayedExpansion
114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
116 |
117 | :endReadAdditionalConfig
118 |
119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
120 |
121 | set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar""
122 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
123 |
124 | # avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in %*
125 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
126 | if ERRORLEVEL 1 goto error
127 | goto end
128 |
129 | :error
130 | set ERROR_CODE=1
131 |
132 | :end
133 | @endlocal & set ERROR_CODE=%ERROR_CODE%
134 |
135 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
136 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
137 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
138 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
139 | :skipRcPost
140 |
141 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
142 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
143 |
144 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
145 |
146 | exit /B %ERROR_CODE%
147 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | 4.0.0
19 |
20 | io.github.openfeign.incubating
21 | feign-reactor
22 | 0.1.0
23 | pom
24 |
25 |
26 | feign-reactor-core
27 | feign-reactor-fake-impl
28 |
29 |
30 | Feign Reactive
31 | https://github.com/OpenFeign/feign-reactive
32 | 2018
33 |
34 |
35 | OpenFeign
36 | https://github.com/openfeign
37 |
38 |
39 |
40 | Github
41 | https://github.com/OpenFeign/feign-reactive/issues
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | The Apache Software License, Version 2.0
54 | http://www.apache.org/licenses/LICENSE-2.0.txt
55 | repo
56 |
57 |
58 |
59 |
60 |
61 | kptfh
62 | Sergii Karpenko
63 | sergey.karpenko@gmail.com
64 |
65 |
66 |
67 |
68 | 1.8
69 | ${java.version}
70 | ${java.version}
71 | UTF-8
72 | UTF-8
73 |
74 | ${project.basedir}
75 |
76 | 3.1.8.RELEASE
77 | 9.5.1
78 | 1.7.25
79 | 3.1
80 |
81 |
82 | 4.12
83 | 3.9.0
84 | 2.15.0
85 | 3.6.0
86 | 2.9.3
87 | 1.8.0-beta0
88 | 20.0
89 | 1.3
90 |
91 |
92 | 0.7.7.201606060606
93 | 4.1.0
94 |
95 | 3.5.1
96 | 2.19.1
97 | 2.3
98 | 3.0.0
99 | 1.9.5
100 | 2.11.0
101 |
102 |
103 |
104 |
105 |
106 | io.github.openfeign
107 | feign-reactor-core
108 | ${project.version}
109 |
110 |
111 | io.projectreactor
112 | reactor-core
113 | ${reactor.version}
114 |
115 |
116 | io.github.openfeign
117 | feign-core
118 | ${feign.version}
119 |
120 |
121 | org.slf4j
122 | slf4j-api
123 | ${slf4j.version}
124 |
125 |
126 | commons-httpclient
127 | commons-httpclient
128 | ${commons-httpclient.version}
129 |
130 |
131 |
132 | io.github.openfeign
133 | feign-reactor-core
134 | test-jar
135 | ${project.version}
136 | test
137 |
138 |
139 | io.projectreactor
140 | reactor-test
141 | ${reactor.version}
142 | test
143 |
144 |
145 | junit
146 | junit
147 | ${junit.version}
148 | test
149 |
150 |
151 |
152 | org.assertj
153 | assertj-core
154 | ${assertj.version}
155 | test
156 |
157 |
158 |
159 | com.github.tomakehurst
160 | wiremock
161 | ${wiremock.version}
162 | test
163 |
164 |
165 |
166 | org.awaitility
167 | awaitility
168 | ${awaitility.version}
169 | test
170 |
171 |
172 |
173 | org.hamcrest
174 | hamcrest-library
175 | ${hamcrest.version}
176 | test
177 |
178 |
179 |
180 | com.fasterxml.jackson.core
181 | jackson-databind
182 | ${jackson.version}
183 | test
184 |
185 |
186 | com.fasterxml.jackson.core
187 | jackson-annotations
188 | ${jackson.version}
189 | test
190 |
191 |
192 | com.fasterxml.jackson.datatype
193 | jackson-datatype-jsr310
194 | ${jackson.version}
195 | test
196 |
197 |
198 |
199 | org.mockito
200 | mockito-all
201 | ${mockito.version}
202 | test
203 |
204 |
205 |
206 | org.apache.logging.log4j
207 | log4j-core
208 | ${log4j.version}
209 | test
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 | org.springframework.boot
223 | spring-boot-maven-plugin
224 |
225 |
226 |
227 | org.apache.maven.plugins
228 | maven-compiler-plugin
229 | ${maven-compiler-plugin.version}
230 |
231 | ${java.version}
232 | ${java.version}
233 |
234 |
235 |
236 |
237 |
238 | org.apache.maven.plugins
239 | maven-surefire-plugin
240 | ${maven-surefire-plugin.version}
241 |
242 |
243 |
244 |
245 | org.jacoco
246 | jacoco-maven-plugin
247 | ${jacoco-plugin.version}
248 |
249 |
250 |
251 |
252 | org.apache.maven.plugins
253 | maven-source-plugin
254 |
255 |
256 | attach-sources
257 |
258 | jar
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 | org.eluder.coveralls
267 | coveralls-maven-plugin
268 | ${coveralls-plugin.version}
269 |
270 |
271 |
272 |
273 | org.apache.maven.plugins
274 | maven-javadoc-plugin
275 |
276 |
277 | attach-javadocs
278 |
279 | jar
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 | com.mycila
290 | license-maven-plugin
291 | 3.0
292 |
293 |
294 | ${main.basedir}/src/etc/header.txt
295 |
296 | .travis.yml
297 | .editorconfig
298 | .gitattributes
299 | .gitignore
300 | .mvn/**
301 | mvnw*
302 | etc/header.txt
303 | **/.idea/**
304 | LICENSE
305 | **/*.md
306 | bnd.bnd
307 | src/test/resources/**
308 | src/main/resources/**
309 |
310 | true
311 |
312 |
313 |
314 | com.mycila
315 | license-maven-plugin-git
316 | 3.0
317 |
318 |
319 |
320 |
321 |
322 | check
323 |
324 | compile
325 |
326 |
327 |
328 |
329 | com.marvinformatics.formatter
330 | formatter-maven-plugin
331 | 2.2.0
332 |
333 | LF
334 | ${main.basedir}/src/config/eclipse-java-style.xml
335 |
336 |
337 |
338 |
339 | format
340 |
341 | verify
342 |
343 |
344 |
345 |
346 | maven-release-plugin
347 | 2.4.1
348 |
349 | false
350 | release
351 | true
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
--------------------------------------------------------------------------------
/src/etc/header.txt:
--------------------------------------------------------------------------------
1 | Copyright ${license.git.copyrightYears} The Feign Authors
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4 | in compliance with the License. You may obtain a copy of the License at
5 |
6 | http://www.apache.org/licenses/LICENSE-2.0
7 |
8 | Unless required by applicable law or agreed to in writing, software distributed under the License
9 | is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10 | or implied. See the License for the specific language governing permissions and limitations under
11 | the License.
12 |
--------------------------------------------------------------------------------