Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions core/src/main/java/feign/Types.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@
* @author Bob Lee
* @author Jesse Wilson
*/
final class Types {
public final class Types {

private static final Type[] EMPTY_TYPE_ARRAY = new Type[0];

private Types() {
// No instances.
}

static Class<?> getRawType(Type type) {
public static Class<?> getRawType(Type type) {
if (type instanceof Class<?>) {
// Type is a normal class.
return (Class<?>) type;
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
<module>ribbon</module>
<module>sax</module>
<module>slf4j</module>
<module>reactive</module>
<module>example-github</module>
<module>example-wikipedia</module>
<!-- remove after feign 10.0 release -->
Expand Down
113 changes: 113 additions & 0 deletions reactive/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
Reactive Streams Wrapper
---

This module wraps Feign's http requests in a [Reactive Streams](https://reactive-streams.org)
Publisher, enabling the use of Reactive Stream `Publisher` return types. Supported Reactive Streams implementations are:

* [Reactor](https://project-reactor.org) (`Mono` and `Flux`)
* [ReactiveX (RxJava)](https://reactivex.io) (`Flowable` only)

To use these wrappers, add the `feign-reactive-wrappers` module, and your desired `reactive-streams`
implementation to your classpath. Then configure Feign to use the reactive streams wrappers.

```java
public interface GitHubReactor {

@RequestLine("GET /repos/{owner}/{repo}/contributors")
Flux<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

class Contributor {
String login;

public Contributor(String login) {
this.login = login;
}
}
}

public class ExampleReactor {
public static void main(String args[]) {
GitHubReactor gitHub = ReactorFeign.builder()
.target(GitHubReactor.class, "https://api.github.com");

List<Contributor> contributors = gitHub.contributors("OpenFeign", "feign")
.map(Contributor::new)
.collect(Collectors.toList())
.block();
}
}

public interface GitHubReactiveX {

@RequestLine("GET /repos/{owner}/{repo}/contributors")
Flowable<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

class Contributor {
String login;

public Contributor(String login) {
this.login = login;
}
}
}

public class ExampleRxJava2 {
public static void main(String args[]) {
GitHubReactiveX gitHub = RxJavaFeign.builder()
.target(GitHub.class, "https://api.github.com");

List<Contributor> contributors = gitHub.contributors("OpenFeign", "feign")
.map(Contributor::new)
.collect(Collectors.toList())
.block();
}
}

```

Considerations
---

These wrappers are not *reactive all the way down*, given that Feign generated requests are
synchronous. Requests still block, but execution is controlled by the `Publisher` and their
related `Scheduler`. While this may not be ideal in terms of a fully reactive application, providing these
wrappers provide an intermediate upgrade path for Feign.

### Streaming

Methods that return `java.util.streams` Types are not supported. Responses are read fully,
the wrapped in the appropriate reactive wrappers.

### Iterable and Collections responses

Due to the Synchronous nature of Feign requests, methods that return `Iterable` types must specify the collection
in the `Publisher`. For `Reactor` types, this limits the use of `Flux` as a response type. If you
want to use `Flux`, you will need to manually convert the `Mono` or `Iterable` response types into
`Flux` using the `fromIterable` method.


```java
public interface GitHub {

@RequestLine("GET /repos/{owner}/{repo}/contributors")
Mono<List<Contributor>> contributors(@Param("owner") String owner, @Param("repo") String repo);

class Contributor {
String login;

public Contributor(String login) {
this.login = login;
}
}
}

public class ExampleApplication {
public static void main(String[] args) {
GitHub gitHub = ReactorFeign.builder()
.target(GitHub.class, "https://api.github.com");

Mono<List<Contributor>> contributors = gitHub.contributors("OpenFeign", "feign");
Flux<Contributor> contributorFlux = Flux.fromIterable(contributors.block());
}
}
```
95 changes: 95 additions & 0 deletions reactive/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--

Copyright 2012-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.

-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.github.openfeign</groupId>
<artifactId>parent</artifactId>
<version>10.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<name>Feign Reactive Wrappers</name>
<artifactId>feign-reactive-wrappers</artifactId>
<description>Reactive Wrapper for Feign Clients</description>

<properties>
<main.basedir>${project.basedir}/..</main.basedir>
<reactor.version>3.1.8.RELEASE</reactor.version>
<reactive.streams.version>1.0.2</reactive.streams.version>
<reactivex.version>2.2.2</reactivex.version>
<mockito.version>1.9.5</mockito.version>
</properties>

<dependencies>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
<version>${reactive.streams.version}</version>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>${reactor.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.reactivex.rxjava2</groupId>
<artifactId>rxjava</artifactId>
<version>${reactivex.version}</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jackson</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-jaxrs</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Copyright 2012-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 feign.reactive;

import feign.Contract;
import feign.MethodMetadata;
import feign.Types;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.reactivestreams.Publisher;

public class ReactiveDelegatingContract implements Contract {

private final Contract delegate;

ReactiveDelegatingContract(Contract delegate) {
this.delegate = delegate;
}

@Override
public List<MethodMetadata> parseAndValidatateMetadata(Class<?> targetType) {
List<MethodMetadata> methodsMetadata = this.delegate.parseAndValidatateMetadata(targetType);

for (final MethodMetadata metadata : methodsMetadata) {
final Type type = metadata.returnType();
if (!isReactive(type)) {
throw new IllegalArgumentException(String.format(
"Method %s of contract %s doesn't returns a org.reactivestreams.Publisher",
metadata.configKey(), targetType.getSimpleName()));
}

/*
* we will need to change the return type of the method to match the return type contained
* within the Publisher
*/
Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
if (actualTypes.length > 1) {
throw new IllegalStateException("Expected only one contained type.");
} else {
Class<?> actual = Types.getRawType(actualTypes[0]);
if (Stream.class.isAssignableFrom(actual)) {
throw new IllegalArgumentException(
"Streams are not supported when using Reactive Wrappers");
}
metadata.returnType(actualTypes[0]);
}
}

return methodsMetadata;
}

/**
* Ensure that the type provided implements a Reactive Streams Publisher.
*
* @param type to inspect.
* @return true if the type implements the Reactive Streams Publisher specification.
*/
private boolean isReactive(Type type) {
if (!ParameterizedType.class.isAssignableFrom(type.getClass())) {
return false;
}
ParameterizedType parameterizedType = (ParameterizedType) type;
Type raw = parameterizedType.getRawType();
return Arrays.asList(((Class) raw).getInterfaces())
.contains(Publisher.class);
}
}
61 changes: 61 additions & 0 deletions reactive/src/main/java/feign/reactive/ReactiveFeign.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright 2012-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 feign.reactive;

import feign.Contract;
import feign.Feign;
import feign.InvocationHandlerFactory;

abstract class ReactiveFeign {



public static class Builder extends Feign.Builder {

private Contract contract = new Contract.Default();

/**
* Extend the current contract to support Reactive Stream return types.
*
* @param contract to extend.
* @return a Builder for chaining.
*/
@Override
public Builder contract(Contract contract) {
this.contract = contract;
return this;
}

/**
* Build the Feign instance.
*
* @return a new Feign Instance.
*/
@Override
public Feign build() {
if (!(this.contract instanceof ReactiveDelegatingContract)) {
super.contract(new ReactiveDelegatingContract(this.contract));
} else {
super.contract(this.contract);
}
return super.build();
}

@Override
public Feign.Builder doNotCloseAfterDecode() {
throw new UnsupportedOperationException("Streaming Decoding is not supported.");
}
}
}
Loading