diff --git a/feign-reactor-jetty/pom.xml b/feign-reactor-jetty/pom.xml new file mode 100644 index 00000000..67b61530 --- /dev/null +++ b/feign-reactor-jetty/pom.xml @@ -0,0 +1,102 @@ + + + 4.0.0 + + + com.playtika.reactivefeign + feign-reactor + 1.0.0-SNAPSHOT + + + feign-reactor-jetty + + + + com.playtika.reactivefeign + feign-reactor-core + + + + org.eclipse.jetty + jetty-reactive-httpclient + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + + io.kptfh.reactivejson + json-reactor + + + + + com.playtika.reactivefeign + feign-reactor-core + 1.0.0-SNAPSHOT + test-jar + test + + + io.projectreactor + reactor-test + test + + + + org.hamcrest + hamcrest-library + test + + + + com.github.tomakehurst + wiremock + test + + + + org.apache.logging.log4j + log4j-slf4j-impl + test + + + + org.mockito + mockito-all + test + + + + org.assertj + assertj-core + test + + + + org.awaitility + awaitility + test + + + + org.springframework.boot + spring-boot-starter-webflux + + + spring-boot-starter-logging + org.springframework.boot + + + test + + + org.springframework.boot + spring-boot-starter-test + test + + + + \ No newline at end of file diff --git a/feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveFeign.java b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveFeign.java new file mode 100644 index 00000000..d7b7a10b --- /dev/null +++ b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/JettyReactiveFeign.java @@ -0,0 +1,86 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import com.fasterxml.jackson.core.async_.JsonFactory; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.eclipse.jetty.client.HttpClient; +import reactivefeign.ReactiveFeign; +import reactivefeign.ReactiveOptions; +import reactivefeign.jetty.client.JettyReactiveHttpClient; + +/** + * Reactive Jetty client based implementation of reactive Feign + * + * @author Sergii Karpenko + */ +public class JettyReactiveFeign { + + public static Builder builder() { + try { + HttpClient httpClient = new HttpClient(); + httpClient.start(); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + return new Builder<>(httpClient, new JsonFactory(), objectMapper); + } catch (Exception e) { + throw new RuntimeException(e); + } + + } + + public static Builder builder(HttpClient httpClient, JsonFactory jsonFactory, ObjectMapper objectMapper) { + return new Builder<>(httpClient, jsonFactory, objectMapper); + } + + public static class Builder extends ReactiveFeign.Builder { + + protected HttpClient httpClient; + protected JsonFactory jsonFactory; + private ObjectMapper objectMapper; + protected ReactiveOptions options; + + protected Builder(HttpClient httpClient, JsonFactory jsonFactory, ObjectMapper objectMapper) { + setHttpClient(httpClient, jsonFactory, objectMapper); + this.jsonFactory = jsonFactory; + this.objectMapper = objectMapper; + } + + @Override + public Builder options(ReactiveOptions options) { + if (options.getConnectTimeoutMillis() != null) { + httpClient.setConnectTimeout(options.getConnectTimeoutMillis()); + } + if (options.getReadTimeoutMillis() != null) { + setHttpClient(httpClient, jsonFactory, objectMapper); + } + this.options = options; + return this; + } + + protected void setHttpClient(HttpClient httpClient, JsonFactory jsonFactory, ObjectMapper objectMapper){ + this.httpClient = httpClient; + clientFactory(methodMetadata -> { + JettyReactiveHttpClient jettyClient = JettyReactiveHttpClient.jettyClient(methodMetadata, httpClient, jsonFactory, objectMapper); + if (options != null && options.getReadTimeoutMillis() != null) { + jettyClient.setRequestTimeout(options.getReadTimeoutMillis()); + } + return jettyClient; + }); + } + } +} + + diff --git a/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpClient.java b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpClient.java new file mode 100644 index 00000000..5dfac06b --- /dev/null +++ b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpClient.java @@ -0,0 +1,229 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactivefeign.jetty.client; + +import com.fasterxml.jackson.core.async_.JsonFactory; +import com.fasterxml.jackson.core.util.ByteArrayBuilder; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.ObjectWriter; +import feign.MethodMetadata; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.Request; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.reactive.client.ContentChunk; +import org.eclipse.jetty.reactive.client.ReactiveRequest; +import org.reactivestreams.Publisher; +import reactivefeign.client.ReactiveHttpClient; +import reactivefeign.client.ReactiveHttpRequest; +import reactivefeign.client.ReactiveHttpResponse; +import reactivefeign.client.ReadTimeoutException; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.io.UncheckedIOException; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.util.concurrent.TimeUnit; + +import static feign.Util.resolveLastTypeParameter; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.singletonList; +import static org.eclipse.jetty.http.HttpHeader.ACCEPT; +import static org.eclipse.jetty.http.HttpHeader.CONTENT_TYPE; +import static reactivefeign.utils.FeignUtils.getBodyActualType; + +/** + * Uses reactive Jetty client to execute http requests + * @author Sergii Karpenko + */ +public class JettyReactiveHttpClient implements ReactiveHttpClient { + + public static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; + public static final String TEXT = "text/plain"; + public static final String TEXT_UTF_8 = TEXT+";charset=utf-8"; + + public static final String APPLICATION_JSON = "application/json"; + public static final String APPLICATION_JSON_UTF_8 = APPLICATION_JSON+";charset=utf-8"; + public static final String APPLICATION_STREAM_JSON = "application/stream+json"; + public static final String APPLICATION_STREAM_JSON_UTF_8 = APPLICATION_STREAM_JSON+";charset=utf-8"; + + + private static final byte[] NEWLINE_SEPARATOR = {'\n'}; + + private final HttpClient httpClient; + private final Class bodyActualClass; + private final Class returnPublisherClass; + private final Class returnActualClass; + private final JsonFactory jsonFactory; + private final ObjectWriter bodyWriter; + private final ObjectReader responseReader; + private long requestTimeout = -1; + + public static JettyReactiveHttpClient jettyClient( + MethodMetadata methodMetadata, + HttpClient httpClient, + JsonFactory jsonFactory, ObjectMapper objectMapper) { + + final Type returnType = methodMetadata.returnType(); + Class returnPublisherType = (Class)((ParameterizedType) returnType).getRawType(); + Class returnActualType = getClass(resolveLastTypeParameter(returnType, returnPublisherType)); + Class bodyActualType = getClass(getBodyActualType(methodMetadata.bodyType())); + + return new JettyReactiveHttpClient(httpClient, + bodyActualType, returnPublisherType, returnActualType, + jsonFactory, + objectMapper.writerFor(bodyActualType), + objectMapper.readerFor(returnActualType)); + } + + public JettyReactiveHttpClient(HttpClient httpClient, + Class bodyActualClass, Class returnPublisherClass, Class returnActualClass, + JsonFactory jsonFactory, ObjectWriter bodyWriter, ObjectReader responseReader) { + this.httpClient = httpClient; + this.bodyActualClass = bodyActualClass; + this.returnPublisherClass = returnPublisherClass; + this.returnActualClass = returnActualClass; + this.jsonFactory = jsonFactory; + this.bodyWriter = bodyWriter; + this.responseReader = responseReader; + } + + public JettyReactiveHttpClient setRequestTimeout(long timeoutInMillis){ + this.requestTimeout = timeoutInMillis; + return this; + } + + @Override + public Mono executeRequest(ReactiveHttpRequest request) { + Request jettyRequest = httpClient.newRequest(request.uri()).method(request.method()); + setUpHeaders(request, jettyRequest.getHeaders()); + if(requestTimeout > 0){ + jettyRequest.timeout(requestTimeout, TimeUnit.MILLISECONDS); + } + + ReactiveRequest.Builder requestBuilder = ReactiveRequest.newBuilder(jettyRequest); + if(bodyActualClass != null){ + ReactiveRequest.Content content = provideBody(request); + requestBuilder.content(content); + jettyRequest.getHeaders().put(CONTENT_TYPE.asString(), singletonList(content.getContentType())); + } + + Publisher responsePublisher = requestBuilder.build().response( + (reactiveResponse, contentChunkPublisher) -> Mono.just(new JettyReactiveHttpResponse( + reactiveResponse, contentChunkPublisher, returnPublisherClass, returnActualClass, + jsonFactory, responseReader))); + + return Mono.from(responsePublisher) + .onErrorMap(ex -> ex instanceof java.util.concurrent.TimeoutException, + ReadTimeoutException::new) + ; + } + + protected void setUpHeaders(ReactiveHttpRequest request, HttpFields httpHeaders) { + request.headers().forEach(httpHeaders::put); + + String acceptHeader; + if(CharSequence.class.isAssignableFrom(returnActualClass) && returnPublisherClass == Mono.class){ + acceptHeader = TEXT; + } + else if(returnActualClass == ByteBuffer.class || returnActualClass == byte[].class){ + acceptHeader = APPLICATION_OCTET_STREAM; + } + else if(returnPublisherClass == Mono.class){ + acceptHeader = APPLICATION_JSON; + } + else { + acceptHeader = APPLICATION_STREAM_JSON; + } + httpHeaders.put(ACCEPT.asString(), singletonList(acceptHeader)); + } + + protected ReactiveRequest.Content provideBody(ReactiveHttpRequest request) { + Publisher bodyPublisher; + String contentType; + if(request.body() instanceof Mono){ + if(bodyActualClass == ByteBuffer.class){ + bodyPublisher = ((Mono)request.body()).map(this::toByteBufferChunk); + contentType = APPLICATION_OCTET_STREAM; + } + else if(bodyActualClass == byte[].class){ + bodyPublisher = Flux.from(request.body()).map(this::toByteArrayChunk); + contentType = APPLICATION_OCTET_STREAM; + } + else if (CharSequence.class.isAssignableFrom(bodyActualClass)){ + bodyPublisher = Flux.from(request.body()).map(this::toCharSequenceChunk); + contentType = TEXT_UTF_8; + } + else { + bodyPublisher = Flux.from(request.body()).map(data -> toJsonChunk(data, false)); + contentType = APPLICATION_JSON_UTF_8; + } + + } else { + if(bodyActualClass == ByteBuffer.class){ + bodyPublisher = Flux.from(request.body()).map(this::toByteBufferChunk); + contentType = APPLICATION_OCTET_STREAM; + } + else if(bodyActualClass == byte[].class){ + bodyPublisher = Flux.from(request.body()).map(this::toByteArrayChunk); + contentType = APPLICATION_OCTET_STREAM; + } + else { + bodyPublisher = Flux.from(request.body()).map(data -> toJsonChunk(data, true)); + contentType = APPLICATION_STREAM_JSON_UTF_8; + } + } + + return ReactiveRequest.Content.fromPublisher(bodyPublisher, contentType); + } + + protected ContentChunk toByteBufferChunk(Object data){ + return new ContentChunk((ByteBuffer)data); + } + + protected ContentChunk toByteArrayChunk(Object data){ + return new ContentChunk(ByteBuffer.wrap((byte[])data)); + } + + protected ContentChunk toCharSequenceChunk(Object data){ + CharBuffer charBuffer = CharBuffer.wrap((CharSequence) data); + ByteBuffer byteBuffer = UTF_8.encode(charBuffer); + return new ContentChunk(byteBuffer); + } + + protected ContentChunk toJsonChunk(Object data, boolean stream){ + try { + ByteArrayBuilder byteArrayBuilder = new ByteArrayBuilder(); + bodyWriter.writeValue(byteArrayBuilder, data); + if(stream) { + byteArrayBuilder.write(NEWLINE_SEPARATOR); + } + ByteBuffer buffer = ByteBuffer.wrap(byteArrayBuilder.toByteArray()); + return new ContentChunk(buffer); + } catch (java.io.IOException e) { + throw new UncheckedIOException(e); + } + } + + public static Class getClass(Type type){ + return (Class)(type instanceof ParameterizedType + ? ((ParameterizedType) type).getRawType() : type); + } +} diff --git a/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpResponse.java b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpResponse.java new file mode 100644 index 00000000..5961fb29 --- /dev/null +++ b/feign-reactor-jetty/src/main/java/reactivefeign/jetty/client/JettyReactiveHttpResponse.java @@ -0,0 +1,136 @@ +package reactivefeign.jetty.client; + +import com.fasterxml.jackson.core.async_.JsonFactory; +import com.fasterxml.jackson.databind.ObjectReader; +import org.eclipse.jetty.http.HttpField; +import org.eclipse.jetty.reactive.client.ContentChunk; +import org.eclipse.jetty.reactive.client.ReactiveResponse; +import org.reactivestreams.Publisher; +import reactivefeign.client.ReactiveHttpResponse; +import reactivejson.ReactorObjectReader; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static java.util.Optional.ofNullable; +import static org.eclipse.jetty.http.HttpHeader.CONTENT_TYPE; + +class JettyReactiveHttpResponse implements ReactiveHttpResponse{ + + public static final String CHARSET_DELIMITER = ";charset="; + private final ReactiveResponse clientResponse; + private final Publisher contentChunks; + private final Class returnPublisherType; + private Class returnActualClass; + private final ObjectReader objectReader; + private final JsonFactory jsonFactory; + + JettyReactiveHttpResponse(ReactiveResponse clientResponse, Publisher contentChunks, + Class returnPublisherType, Class returnActualClass, + JsonFactory jsonFactory, ObjectReader objectReader) { + this.clientResponse = clientResponse; + this.contentChunks = contentChunks; + this.returnPublisherType = returnPublisherType; + this.returnActualClass = returnActualClass; + this.objectReader = objectReader; + this.jsonFactory = jsonFactory; + } + + @Override + public int status() { + return clientResponse.getStatus(); + } + + @Override + public Map> headers() { + return clientResponse.getHeaders().stream() + .collect(Collectors.toMap(HttpField::getName, field -> asList(field.getValues()))); + } + + @Override + public Publisher body() { + ReactorObjectReader reactorObjectReader = new ReactorObjectReader(jsonFactory); + + Flux content = content(); + + if (returnPublisherType == Mono.class) { + if(returnActualClass == ByteBuffer.class || returnActualClass == byte[].class){ + return joinChunks().map(ByteBuffer::wrap); + } else if(returnActualClass.isAssignableFrom(String.class)){ + Charset charset = getCharset(); + return joinChunks().map(bytes -> charset.decode(ByteBuffer.wrap(bytes)).toString()); + } else { + return reactorObjectReader.read(content, objectReader); + } + } + else if (returnPublisherType == Flux.class) { + if(returnActualClass == ByteBuffer.class || returnActualClass == byte[].class){ + return (Publisher)content; + } else { + return reactorObjectReader.readElements(content, objectReader); + } + } + else { + throw new IllegalArgumentException("Unknown returnPublisherType: " + returnPublisherType); + } + } + + private Charset getCharset() { + return ofNullable(clientResponse.getHeaders().get(CONTENT_TYPE.asString())) + .map(header -> { + int pos = header.indexOf(CHARSET_DELIMITER); + if(pos >= 0){ + return header.substring(pos + CHARSET_DELIMITER.length()); + } else { + return null; + } + }) + .map(Charset::forName) + .orElse(UTF_8); + } + + private Flux content() { + return Flux.from(contentChunks).map(chunk -> { + // See https://github.com/eclipse/jetty.project/issues/2429 + byte[] data = new byte[chunk.buffer.capacity()]; + chunk.buffer.get(data); + chunk.callback.succeeded(); + return ByteBuffer.wrap(data); + +// ByteBuffer duplicate = chunk.buffer.duplicate(); +// chunk.callback.succeeded(); +// return duplicate; + }); + } + + @Override + public Mono bodyData() { + return joinChunks(); + } + + private Mono joinChunks() { + return content() + .collect(Collectors.toList()) + .map(JettyReactiveHttpResponse::joinBuffers); + } + + private static byte[] joinBuffers(List buffers){ + int totalLength = buffers.stream() + .map(ByteBuffer::limit) + .reduce(0, (length1, length2) -> (length1 + length2)); + byte[] data = new byte[totalLength]; + int pos = 0; + for(ByteBuffer byteBuffer : buffers){ + byteBuffer.get(data, pos, byteBuffer.capacity()); + pos += byteBuffer.capacity(); + }; + return data; + } +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/CompressionTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/CompressionTest.java new file mode 100644 index 00000000..7588f405 --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/CompressionTest.java @@ -0,0 +1,29 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import reactivefeign.ReactiveFeign; +import reactivefeign.ReactiveOptions; +import reactivefeign.testcase.IcecreamServiceApi; + +/** + * @author Sergii Karpenko + */ +public class CompressionTest extends reactivefeign.CompressionTest { + + @Override + protected ReactiveFeign.Builder builder(ReactiveOptions options) { + return JettyReactiveFeign.builder().options(options); + } +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ConnectionTimeoutTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ConnectionTimeoutTest.java new file mode 100644 index 00000000..9d87b3a0 --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ConnectionTimeoutTest.java @@ -0,0 +1,29 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import reactivefeign.ReactiveFeign; +import reactivefeign.ReactiveOptions; +import reactivefeign.testcase.IcecreamServiceApi; + +/** + * @author Sergii Karpenko + */ +public class ConnectionTimeoutTest extends reactivefeign.ConnectionTimeoutTest { + + @Override + protected ReactiveFeign.Builder builder(ReactiveOptions options) { + return JettyReactiveFeign.builder().options(options); + } +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ContractTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ContractTest.java new file mode 100644 index 00000000..8641f28f --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ContractTest.java @@ -0,0 +1,27 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import reactivefeign.ReactiveFeign; + +/** + * @author Sergii Karpenko + */ +public class ContractTest extends reactivefeign.ContractTest { + + @Override + protected ReactiveFeign.Builder builder() { + return JettyReactiveFeign.builder(); + } +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/DefaultMethodTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/DefaultMethodTest.java new file mode 100644 index 00000000..9608650a --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/DefaultMethodTest.java @@ -0,0 +1,39 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import reactivefeign.ReactiveFeign; +import reactivefeign.ReactiveOptions; +import reactivefeign.testcase.IcecreamServiceApi; + +/** + * @author Sergii Karpenko + */ +public class DefaultMethodTest extends reactivefeign.DefaultMethodTest { + + @Override + protected ReactiveFeign.Builder builder() { + return JettyReactiveFeign.builder(); + } + + @Override + protected ReactiveFeign.Builder builder(Class apiClass) { + return JettyReactiveFeign.builder(); + } + + @Override + protected ReactiveFeign.Builder builder(ReactiveOptions options) { + return JettyReactiveFeign.builder().options(options); + } +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/LoggerTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/LoggerTest.java new file mode 100644 index 00000000..da1abc9a --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/LoggerTest.java @@ -0,0 +1,29 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +/** + * @author Sergii Karpenko + */ +public class LoggerTest extends reactivefeign.LoggerTest { + + @Override + protected ReactiveFeign.Builder builder() { + return JettyReactiveFeign.builder(); + } + +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/NotFoundTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/NotFoundTest.java new file mode 100644 index 00000000..f8c153e9 --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/NotFoundTest.java @@ -0,0 +1,32 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +/** + * @author Sergii Karpenko + */ +public class NotFoundTest extends reactivefeign.NotFoundTest { + + @Override + protected ReactiveFeign.Builder builder() { + return JettyReactiveFeign.builder(); + } + +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ReactivityTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ReactivityTest.java new file mode 100644 index 00000000..645f8d97 --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ReactivityTest.java @@ -0,0 +1,31 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import com.fasterxml.jackson.core.JsonProcessingException; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +public class ReactivityTest extends reactivefeign.ReactivityTest { + + @Override + protected ReactiveFeign.Builder builder() { + return JettyReactiveFeign.builder(); + } + + @Override + public void shouldRunReactively() throws JsonProcessingException { + super.shouldRunReactively(); + } +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ReadTimeoutTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ReadTimeoutTest.java new file mode 100644 index 00000000..c7373fa3 --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/ReadTimeoutTest.java @@ -0,0 +1,29 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import reactivefeign.ReactiveFeign; +import reactivefeign.ReactiveOptions; +import reactivefeign.testcase.IcecreamServiceApi; + +/** + * @author Sergii Karpenko + */ +public class ReadTimeoutTest extends reactivefeign.ReadTimeoutTest { + + @Override + protected ReactiveFeign.Builder builder(ReactiveOptions options) { + return JettyReactiveFeign.builder().options(options); + } +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/RequestInterceptorTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/RequestInterceptorTest.java new file mode 100644 index 00000000..e30b955c --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/RequestInterceptorTest.java @@ -0,0 +1,33 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +/** + * @author Sergii Karpenko + */ +public class RequestInterceptorTest extends reactivefeign.RequestInterceptorTest { + + @Override + protected ReactiveFeign.Builder builder() { + return JettyReactiveFeign.builder(); + } + + @Override + protected Class notAuthorizedException() { + return org.eclipse.jetty.client.HttpResponseException.class; + } +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/RetryingTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/RetryingTest.java new file mode 100644 index 00000000..5e2f73db --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/RetryingTest.java @@ -0,0 +1,35 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import org.junit.Ignore; +import org.junit.Test; +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +/** + * @author Sergii Karpenko + */ +public class RetryingTest extends reactivefeign.RetryingTest { + + @Override + protected ReactiveFeign.Builder builder() { + return JettyReactiveFeign.builder(); + } + + @Ignore + @Test + public void shouldFailAsNoMoreRetriesWithBackoff() { + } +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/SmokeTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/SmokeTest.java new file mode 100644 index 00000000..2fd3670e --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/SmokeTest.java @@ -0,0 +1,28 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +/** + * @author Sergii Karpenko + */ +public class SmokeTest extends reactivefeign.SmokeTest { + + @Override + protected ReactiveFeign.Builder builder() { + return JettyReactiveFeign.builder(); + } +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/StatusHandlerTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/StatusHandlerTest.java new file mode 100644 index 00000000..022a97ad --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/StatusHandlerTest.java @@ -0,0 +1,28 @@ +/** + * Copyright 2018 The Feign Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ +package reactivefeign.jetty; + +import reactivefeign.ReactiveFeign; +import reactivefeign.testcase.IcecreamServiceApi; + +/** + * @author Sergii Karpenko + */ +public class StatusHandlerTest extends reactivefeign.StatusHandlerTest { + + @Override + protected ReactiveFeign.Builder builder() { + return JettyReactiveFeign.builder(); + } +} diff --git a/feign-reactor-jetty/src/test/java/reactivefeign/jetty/allfeatures/AllFeaturesTest.java b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/allfeatures/AllFeaturesTest.java new file mode 100644 index 00000000..aa98bafd --- /dev/null +++ b/feign-reactor-jetty/src/test/java/reactivefeign/jetty/allfeatures/AllFeaturesTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013-2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package reactivefeign.jetty.allfeatures; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration; +import reactivefeign.ReactiveFeign; +import reactivefeign.jetty.JettyReactiveFeign; + +/** + * @author Sergii Karpenko + * + * Tests ReactiveFeign in conjunction with WebFlux rest controller. + */ +@EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration.class, ReactiveUserDetailsServiceAutoConfiguration.class}) +public class AllFeaturesTest extends reactivefeign.allfeatures.AllFeaturesTest { + + @Override + protected ReactiveFeign.Builder builder() { + return JettyReactiveFeign.builder(); + } +} diff --git a/feign-reactor-jetty/src/test/resources/log4j2.xml b/feign-reactor-jetty/src/test/resources/log4j2.xml new file mode 100644 index 00000000..0300f9e4 --- /dev/null +++ b/feign-reactor-jetty/src/test/resources/log4j2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3eb350dd..2d964f40 100644 --- a/pom.xml +++ b/pom.xml @@ -12,6 +12,7 @@ feign-reactor-webclient feign-reactor-cloud feign-reactor-rx2 + feign-reactor-jetty feign-reactive @@ -66,6 +67,10 @@ 3.1.6.RELEASE 2.2.2 + + 1.0.1 + 0.0.1 + 2.0.5.RELEASE 2.0.5.RELEASE @@ -329,6 +334,12 @@ ${jetty-reactive-httpclient.version} + + io.kptfh.reactivejson + json-reactor + ${json-reactor.version} + + junit @@ -430,6 +441,14 @@ + + + bintray-kptfh-feign-reactive + bintray + https://dl.bintray.com/kptfh/json-reactive + + +