Giter Site home page Giter Site logo

feign's Introduction

Feign simplifies the process of writing Java HTTP clients

Join the chat at https://gitter.im/OpenFeign/feign CircleCI Maven Central

Feign is a Java to HTTP client binder inspired by Retrofit, JAXRS-2.0, and WebSocket. Feign's first goal was reducing the complexity of binding Denominator uniformly to HTTP APIs regardless of ReSTfulness.


Why Feign and not X?

Feign uses tools like Jersey and CXF to write Java clients for ReST or SOAP services. Furthermore, Feign allows you to write your own code on top of http libraries such as Apache HC. Feign connects your code to http APIs with minimal overhead and code via customizable decoders and error handling, which can be written to any text-based http API.

How does Feign work?

Feign works by processing annotations into a templatized request. Arguments are applied to these templates in a straightforward fashion before output. Although Feign is limited to supporting text-based APIs, it dramatically simplifies system aspects such as replaying requests. Furthermore, Feign makes it easy to unit test your conversions knowing this.

Java Version Compatibility

Feign 10.x and above are built on Java 8 and should work on Java 9, 10, and 11. For those that need JDK 6 compatibility, please use Feign 9.x

Feature overview

This is a map with current key features provided by feign:

MindMap overview

Roadmap

Feign 11 and beyond

Making API clients easier

Short Term - What we're working on now. ⏰

  • Response Caching
    • Support caching of api responses. Allow for users to define under what conditions a response is eligible for caching and what type of caching mechanism should be used.
    • Support in-memory caching and external cache implementations (EhCache, Google, Spring, etc...)
  • Complete URI Template expression support
  • Logger API refactor
    • Refactor the Logger API to adhere closer to frameworks like SLF4J providing a common mental model for logging within Feign. This model will be used by Feign itself throughout and provide clearer direction on how the Logger will be used.
  • Retry API refactor
    • Refactor the Retry API to support user-supplied conditions and better control over back-off policies. This may result in non-backward-compatible breaking changes

Medium Term - What's up next. ⏲

  • Async execution support via CompletableFuture
    • Allow for Future chaining and executor management for the request/response lifecycle. Implementation will require non-backward-compatible breaking changes. However this feature is required before Reactive execution can be considered.
  • Reactive execution support via Reactive Streams
    • For JDK 9+, consider a native implementation that uses java.util.concurrent.Flow.
    • Support for Project Reactor and RxJava 2+ implementations on JDK 8.

Long Term - The future ☁️

  • Additional Circuit Breaker Support.
    • Support additional Circuit Breaker implementations like Resilience4J and Spring Circuit Breaker

Usage

The feign library is available from Maven Central.

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-core</artifactId>
    <version>??feign.version??</version>
</dependency>

Basics

Usage typically looks like this, an adaptation of the canonical Retrofit sample.

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

  @RequestLine("POST /repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);

}

public static class Contributor {
  String login;
  int contributions;
}

public static class Issue {
  String title;
  String body;
  List<String> assignees;
  int milestone;
  List<String> labels;
}

public class MyApp {
  public static void main(String... args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");

    // Fetch and print a list of the contributors to this library.
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

Interface Annotations

Feign annotations define the Contract between the interface and how the underlying client should work. Feign's default contract defines the following annotations:

Annotation Interface Target Usage
@RequestLine Method Defines the HttpMethod and UriTemplate for request. Expressions, values wrapped in curly-braces {expression} are resolved using their corresponding @Param annotated parameters.
@Param Parameter Defines a template variable, whose value will be used to resolve the corresponding template Expression, by name provided as annotation value. If value is missing it will try to get the name from bytecode method parameter name (if the code was compiled with -parameters flag).
@Headers Method, Type Defines a HeaderTemplate; a variation on a UriTemplate. that uses @Param annotated values to resolve the corresponding Expressions. When used on a Type, the template will be applied to every request. When used on a Method, the template will apply only to the annotated method.
@QueryMap Parameter Defines a Map of name-value pairs, or POJO, to expand into a query string.
@HeaderMap Parameter Defines a Map of name-value pairs, to expand into Http Headers
@Body Method Defines a Template, similar to a UriTemplate and HeaderTemplate, that uses @Param annotated values to resolve the corresponding Expressions.

Overriding the Request Line

If there is a need to target a request to a different host then the one supplied when the Feign client was created, or you want to supply a target host for each request, include a java.net.URI parameter and Feign will use that value as the request target.

@RequestLine("POST /repos/{owner}/{repo}/issues")
void createIssue(URI host, Issue issue, @Param("owner") String owner, @Param("repo") String repo);

Templates and Expressions

Feign Expressions represent Simple String Expressions (Level 1) as defined by URI Template - RFC 6570. Expressions are expanded using their corresponding Param annotated method parameters.

Example

public interface GitHub {

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

  class Contributor {
    String login;
    int contributions;
  }
}

public class MyApp {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");

    /* The owner and repository parameters will be used to expand the owner and repo expressions
     * defined in the RequestLine.
     *
     * the resulting uri will be https://api.github.com/repos/OpenFeign/feign/contributors
     */
    github.contributors("OpenFeign", "feign");
  }
}

Expressions must be enclosed in curly braces {} and may contain regular expression patterns, separated by a colon : to restrict resolved values. Example owner must be alphabetic. {owner:[a-zA-Z]*}

Request Parameter Expansion

RequestLine and QueryMap templates follow the URI Template - RFC 6570 specification for Level 1 templates, which specifies the following:

  • Unresolved expressions are omitted.
  • All literals and variable values are pct-encoded, if not already encoded or marked encoded via a @Param annotation.

We also have limited support for Level 3, Path Style Expressions, with the following restrictions:

  • Maps and Lists are expanded by default.
  • Only Single variable templates are supported.

Examples:

{;who}             ;who=fred
{;half}            ;half=50%25
{;empty}           ;empty
{;list}            ;list=red;list=green;list=blue
{;map}             ;semi=%3B;dot=.;comma=%2C
public interface MatrixService {

  @RequestLine("GET /repos{;owners}")
  List<Contributor> contributors(@Param("owners") List<String> owners);

  class Contributor {
    String login;
    int contributions;
  }
}

If owners in the above example is defined as Matt, Jeff, Susan, the uri will expand to /repos;owners=Matt;owners=Jeff;owners=Susan

For more information see RFC 6570, Section 3.2.7

Undefined vs. Empty Values

Undefined expressions are expressions where the value for the expression is an explicit null or no value is provided. Per URI Template - RFC 6570, it is possible to provide an empty value for an expression. When Feign resolves an expression, it first determines if the value is defined, if it is then the query parameter will remain. If the expression is undefined, the query parameter is removed. See below for a complete breakdown.

Empty String

public void test() {
   Map<String, Object> parameters = new LinkedHashMap<>();
   parameters.put("param", "");
   this.demoClient.test(parameters);
}

Result

http://localhost:8080/test?param=

Missing

public void test() {
   Map<String, Object> parameters = new LinkedHashMap<>();
   this.demoClient.test(parameters);
}

Result

http://localhost:8080/test

Undefined

public void test() {
   Map<String, Object> parameters = new LinkedHashMap<>();
   parameters.put("param", null);
   this.demoClient.test(parameters);
}

Result

http://localhost:8080/test

See Advanced Usage for more examples.

What about slashes? /

@RequestLine templates do not encode slash / characters by default. To change this behavior, set the decodeSlash property on the @RequestLine to false.

What about plus? +

Per the URI specification, a + sign is allowed in both the path and query segments of a URI, however, handling of the symbol on the query can be inconsistent. In some legacy systems, the + is equivalent to the a space. Feign takes the approach of modern systems, where a + symbol should not represent a space and is explicitly encoded as %2B when found on a query string.

If you wish to use + as a space, then use the literal character or encode the value directly as %20

Custom Expansion

The @Param annotation has an optional property expander allowing for complete control over the individual parameter's expansion. The expander property must reference a class that implements the Expander interface:

public interface Expander {
    String expand(Object value);
}

The result of this method adheres to the same rules stated above. If the result is null or an empty string, the value is omitted. If the value is not pct-encoded, it will be. See Custom @Param Expansion for more examples.

Request Headers Expansion

Headers and HeaderMap templates follow the same rules as Request Parameter Expansion with the following alterations:

  • Unresolved expressions are omitted. If the result is an empty header value, the entire header is removed.
  • No pct-encoding is performed.

See Headers for examples.

A Note on @Param parameters and their names:

All expressions with the same name, regardless of their position on the @RequestLine, @QueryMap, @BodyTemplate, or @Headers will resolve to the same value. In the following example, the value of contentType, will be used to resolve both the header and path expression:

public interface ContentService {
  @RequestLine("GET /api/documents/{contentType}")
  @Headers("Accept: {contentType}")
  String getDocumentByType(@Param("contentType") String type);
}

Keep this in mind when designing your interfaces.

Request Body Expansion

Body templates follow the same rules as Request Parameter Expansion with the following alterations:

  • Unresolved expressions are omitted.
  • Expanded value will not be passed through an Encoder before being placed on the request body.
  • A Content-Type header must be specified. See Body Templates for examples.

Customization

Feign has several aspects that can be customized.
For simple cases, you can use Feign.builder() to construct an API interface with your custom components.
For request setting, you can use options(Request.Options options) on target() to set connectTimeout, connectTimeoutUnit, readTimeout, readTimeoutUnit, followRedirects.
For example:

interface Bank {
  @RequestLine("POST /account/{id}")
  Account getAccountInfo(@Param("id") String id);
}

public class BankService {
  public static void main(String[] args) {
    Bank bank = Feign.builder()
        .decoder(new AccountDecoder())
        .options(new Request.Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, true))
        .target(Bank.class, "https://api.examplebank.com");
  }
}

Multiple Interfaces

Feign can produce multiple api interfaces. These are defined as Target<T> (default HardCodedTarget<T>), which allow for dynamic discovery and decoration of requests prior to execution.

For example, the following pattern might decorate each request with the current url and auth token from the identity service.

public class CloudService {
  public static void main(String[] args) {
    CloudDNS cloudDNS = Feign.builder()
      .target(new CloudIdentityTarget<CloudDNS>(user, apiKey));
  }

  class CloudIdentityTarget extends Target<CloudDNS> {
    /* implementation of a Target */
  }
}

Examples

Feign includes example GitHub and Wikipedia clients. The denominator project can also be scraped for Feign in practice. Particularly, look at its example daemon.


Integrations

Feign intends to work well with other Open Source tools. Modules are welcome to integrate with your favorite projects!

Encoder/Decoder

Gson

Gson includes an encoder and decoder you can use with a JSON API.

Add GsonEncoder and/or GsonDecoder to your Feign.Builder like so:

public class Example {
  public static void main(String[] args) {
    GsonCodec codec = new GsonCodec();
    GitHub github = Feign.builder()
                         .encoder(new GsonEncoder())
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");
  }
}

Jackson

Jackson includes an encoder and decoder you can use with a JSON API.

Add JacksonEncoder and/or JacksonDecoder to your Feign.Builder like so:

public class Example {
  public static void main(String[] args) {
      GitHub github = Feign.builder()
                     .encoder(new JacksonEncoder())
                     .decoder(new JacksonDecoder())
                     .target(GitHub.class, "https://api.github.com");
  }
}

For the lighter weight Jackson Jr, use JacksonJrEncoder and JacksonJrDecoder from the Jackson Jr Module.

Moshi

Moshi includes an encoder and decoder you can use with a JSON API. Add MoshiEncoder and/or MoshiDecoder to your Feign.Builder like so:

GitHub github = Feign.builder()
                     .encoder(new MoshiEncoder())
                     .decoder(new MoshiDecoder())
                     .target(GitHub.class, "https://api.github.com");

Sax

SaxDecoder allows you to decode XML in a way that is compatible with normal JVM and also Android environments.

Here's an example of how to configure Sax response parsing:

public class Example {
  public static void main(String[] args) {
      Api api = Feign.builder()
         .decoder(SAXDecoder.builder()
                            .registerContentHandler(UserIdHandler.class)
                            .build())
         .target(Api.class, "https://apihost");
    }
}

JAXB

JAXB includes an encoder and decoder you can use with an XML API.

Add JAXBEncoder and/or JAXBDecoder to your Feign.Builder like so:

public class Example {
  public static void main(String[] args) {
    Api api = Feign.builder()
             .encoder(new JAXBEncoder())
             .decoder(new JAXBDecoder())
             .target(Api.class, "https://apihost");
  }
}

SOAP

SOAP includes an encoder and decoder you can use with an XML API.

This module adds support for encoding and decoding SOAP Body objects via JAXB and SOAPMessage. It also provides SOAPFault decoding capabilities by wrapping them into the original javax.xml.ws.soap.SOAPFaultException, so that you'll only need to catch SOAPFaultException in order to handle SOAPFault.

Add SOAPEncoder and/or SOAPDecoder to your Feign.Builder like so:

public class Example {
  public static void main(String[] args) {
    Api api = Feign.builder()
	     .encoder(new SOAPEncoder(jaxbFactory))
	     .decoder(new SOAPDecoder(jaxbFactory))
	     .errorDecoder(new SOAPErrorDecoder())
	     .target(MyApi.class, "http://api");
  }
}

NB: you may also need to add SOAPErrorDecoder if SOAP Faults are returned in response with error http codes (4xx, 5xx, ...)

Fastjson2

fastjson2 includes an encoder and decoder you can use with a JSON API.

Add Fastjson2Encoder and/or Fastjson2Decoder to your Feign.Builder like so:

public class Example {
  public static void main(String[] args) {
      GitHub github = Feign.builder()
                     .encoder(new Fastjson2Encoder())
                     .decoder(new Fastjson2Decoder())
                     .target(GitHub.class, "https://api.github.com");
  }
}

Contract

JAX-RS

JAXRSContract overrides annotation processing to instead use standard ones supplied by the JAX-RS specification. This is currently targeted at the 1.1 spec.

Here's the example above re-written to use JAX-RS:

interface GitHub {
  @GET @Path("/repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@PathParam("owner") String owner, @PathParam("repo") String repo);
}

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                       .contract(new JAXRSContract())
                       .target(GitHub.class, "https://api.github.com");
  }
}

Client

OkHttp

OkHttpClient directs Feign's http requests to OkHttp, which enables SPDY and better network control.

To use OkHttp with Feign, add the OkHttp module to your classpath. Then, configure Feign to use the OkHttpClient:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .client(new OkHttpClient())
                     .target(GitHub.class, "https://api.github.com");
  }
}

Ribbon

RibbonClient overrides URL resolution of Feign's client, adding smart routing and resiliency capabilities provided by Ribbon.

Integration requires you to pass your ribbon client name as the host part of the url, for example myAppProd.

public class Example {
  public static void main(String[] args) {
    MyService api = Feign.builder()
          .client(RibbonClient.create())
          .target(MyService.class, "https://myAppProd");
  }
}

Java 11 Http2

Http2Client directs Feign's http requests to Java11 New HTTP/2 Client that implements HTTP/2.

To use New HTTP/2 Client with Feign, use Java SDK 11. Then, configure Feign to use the Http2Client:

GitHub github = Feign.builder()
                     .client(new Http2Client())
                     .target(GitHub.class, "https://api.github.com");

Breaker

Hystrix

HystrixFeign configures circuit breaker support provided by Hystrix.

To use Hystrix with Feign, add the Hystrix module to your classpath. Then use the HystrixFeign builder:

public class Example {
  public static void main(String[] args) {
    MyService api = HystrixFeign.builder().target(MyService.class, "https://myAppProd");
  }
}

Logger

SLF4J

SLF4JModule allows directing Feign's logging to SLF4J, allowing you to easily use a logging backend of your choice (Logback, Log4J, etc.)

To use SLF4J with Feign, add both the SLF4J module and an SLF4J binding of your choice to your classpath. Then, configure Feign to use the Slf4jLogger:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .logger(new Slf4jLogger())
                     .logLevel(Level.FULL)
                     .target(GitHub.class, "https://api.github.com");
  }
}

Decoders

Feign.builder() allows you to specify additional configuration such as how to decode a response.

If any methods in your interface return types besides Response, String, byte[] or void, you'll need to configure a non-default Decoder.

Here's how to configure JSON decoding (using the feign-gson extension):

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .decoder(new GsonDecoder())
                     .target(GitHub.class, "https://api.github.com");
  }
}

If you need to pre-process the response before give it to the Decoder, you can use the mapAndDecode builder method. An example use case is dealing with an API that only serves jsonp, you will maybe need to unwrap the jsonp before send it to the Json decoder of your choice:

public class Example {
  public static void main(String[] args) {
    JsonpApi jsonpApi = Feign.builder()
                         .mapAndDecode((response, type) -> jsopUnwrap(response, type), new GsonDecoder())
                         .target(JsonpApi.class, "https://some-jsonp-api.com");
  }
}

If any methods in your interface return type Stream, you'll need to configure a StreamDecoder.

Here's how to configure Stream decoder without delegate decoder:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
            .decoder(StreamDecoder.create((r, t) -> {
              BufferedReader bufferedReader = new BufferedReader(r.body().asReader(UTF_8));
              return bufferedReader.lines().iterator();
            }))
            .target(GitHub.class, "https://api.github.com");
  }
}

Here's how to configure Stream decoder with delegate decoder:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
            .decoder(StreamDecoder.create((r, t) -> {
              BufferedReader bufferedReader = new BufferedReader(r.body().asReader(UTF_8));
              return bufferedReader.lines().iterator();
            }, (r, t) -> "this is delegate decoder"))
            .target(GitHub.class, "https://api.github.com");
  }
}

Encoders

The simplest way to send a request body to a server is to define a POST method that has a String or byte[] parameter without any annotations on it. You will likely need to add a Content-Type header.

interface LoginClient {
  @RequestLine("POST /")
  @Headers("Content-Type: application/json")
  void login(String content);
}

public class Example {
  public static void main(String[] args) {
    client.login("{\"user_name\": \"denominator\", \"password\": \"secret\"}");
  }
}

By configuring an Encoder, you can send a type-safe request body. Here's an example using the feign-gson extension:

static class Credentials {
  final String user_name;
  final String password;

  Credentials(String user_name, String password) {
    this.user_name = user_name;
    this.password = password;
  }
}

interface LoginClient {
  @RequestLine("POST /")
  void login(Credentials creds);
}

public class Example {
  public static void main(String[] args) {
    LoginClient client = Feign.builder()
                              .encoder(new GsonEncoder())
                              .target(LoginClient.class, "https://foo.com");

    client.login(new Credentials("denominator", "secret"));
  }
}

@Body templates

The @Body annotation indicates a template to expand using parameters annotated with @Param. You will likely need to add a Content-Type header.

interface LoginClient {

  @RequestLine("POST /")
  @Headers("Content-Type: application/xml")
  @Body("<login \"user_name\"=\"{user_name}\" \"password\"=\"{password}\"/>")
  void xml(@Param("user_name") String user, @Param("password") String password);

  @RequestLine("POST /")
  @Headers("Content-Type: application/json")
  // json curly braces must be escaped!
  @Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")
  void json(@Param("user_name") String user, @Param("password") String password);
}

public class Example {
  public static void main(String[] args) {
    client.xml("denominator", "secret"); // <login "user_name"="denominator" "password"="secret"/>
    client.json("denominator", "secret"); // {"user_name": "denominator", "password": "secret"}
  }
}

Headers

Feign supports settings headers on requests either as part of the api or as part of the client depending on the use case.

Set headers using apis

In cases where specific interfaces or calls should always have certain header values set, it makes sense to define headers as part of the api.

Static headers can be set on an api interface or method using the @Headers annotation.

@Headers("Accept: application/json")
interface BaseApi<V> {
  @Headers("Content-Type: application/json")
  @RequestLine("PUT /api/{key}")
  void put(@Param("key") String key, V value);
}

Methods can specify dynamic content for static headers using variable expansion in @Headers.

public interface Api {
   @RequestLine("POST /")
   @Headers("X-Ping: {token}")
   void post(@Param("token") String token);
}

In cases where both the header field keys and values are dynamic and the range of possible keys cannot be known ahead of time and may vary between different method calls in the same api/client (e.g. custom metadata header fields such as "x-amz-meta-*" or "x-goog-meta-*"), a Map parameter can be annotated with HeaderMap to construct a query that uses the contents of the map as its header parameters.

public interface Api {
   @RequestLine("POST /")
   void post(@HeaderMap Map<String, Object> headerMap);
}

These approaches specify header entries as part of the api and do not require any customizations when building the Feign client.

Setting headers per target

To customize headers for each request method on a Target, a RequestInterceptor can be used. RequestInterceptors can be shared across Target instances and are expected to be thread-safe. RequestInterceptors are applied to all request methods on a Target.

If you need per method customization, a custom Target is required, as the a RequestInterceptor does not have access to the current method metadata.

For an example of setting headers using a RequestInterceptor, see the Request Interceptors section.

Headers can be set as part of a custom Target.

  static class DynamicAuthTokenTarget<T> implements Target<T> {
    public DynamicAuthTokenTarget(Class<T> clazz,
                                  UrlAndTokenProvider provider,
                                  ThreadLocal<String> requestIdProvider);

    @Override
    public Request apply(RequestTemplate input) {
      TokenIdAndPublicURL urlAndToken = provider.get();
      if (input.url().indexOf("http") != 0) {
        input.insert(0, urlAndToken.publicURL);
      }
      input.header("X-Auth-Token", urlAndToken.tokenId);
      input.header("X-Request-ID", requestIdProvider.get());

      return input.request();
    }
  }

  public class Example {
    public static void main(String[] args) {
      Bank bank = Feign.builder()
              .target(new DynamicAuthTokenTarget(Bank.class, provider, requestIdProvider));
    }
  }

These approaches depend on the custom RequestInterceptor or Target being set on the Feign client when it is built and can be used as a way to set headers on all api calls on a per-client basis. This can be useful for doing things such as setting an authentication token in the header of all api requests on a per-client basis. The methods are run when the api call is made on the thread that invokes the api call, which allows the headers to be set dynamically at call time and in a context-specific manner -- for example, thread-local storage can be used to set different header values depending on the invoking thread, which can be useful for things such as setting thread-specific trace identifiers for requests.

Advanced usage

Base Apis

In many cases, apis for a service follow the same conventions. Feign supports this pattern via single-inheritance interfaces.

Consider the example:

interface BaseAPI {
  @RequestLine("GET /health")
  String health();

  @RequestLine("GET /all")
  List<Entity> all();
}

You can define and target a specific api, inheriting the base methods.

interface CustomAPI extends BaseAPI {
  @RequestLine("GET /custom")
  String custom();
}

In many cases, resource representations are also consistent. For this reason, type parameters are supported on the base api interface.

@Headers("Accept: application/json")
interface BaseApi<V> {

  @RequestLine("GET /api/{key}")
  V get(@Param("key") String key);

  @RequestLine("GET /api")
  List<V> list();

  @Headers("Content-Type: application/json")
  @RequestLine("PUT /api/{key}")
  void put(@Param("key") String key, V value);
}

interface FooApi extends BaseApi<Foo> { }

interface BarApi extends BaseApi<Bar> { }

Logging

You can log the http messages going to and from the target by setting up a Logger. Here's the easiest way to do that:

public class Example {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                     .decoder(new GsonDecoder())
                     .logger(new Logger.JavaLogger("GitHub.Logger").appendToFile("logs/http.log"))
                     .logLevel(Logger.Level.FULL)
                     .target(GitHub.class, "https://api.github.com");
  }
}

A Note on JavaLogger: Avoid using of default JavaLogger() constructor - it was marked as deprecated and will be removed soon.

The SLF4JLogger (see above) may also be of interest.

To filter out sensitive information like authorization or tokens override methods shouldLogRequestHeader or shouldLogResponseHeader.

Request Interceptors

When you need to change all requests, regardless of their target, you'll want to configure a RequestInterceptor. For example, if you are acting as an intermediary, you might want to propagate the X-Forwarded-For header.

static class ForwardedForInterceptor implements RequestInterceptor {
  @Override public void apply(RequestTemplate template) {
    template.header("X-Forwarded-For", "origin.host.com");
  }
}

public class Example {
  public static void main(String[] args) {
    Bank bank = Feign.builder()
                 .decoder(accountDecoder)
                 .requestInterceptor(new ForwardedForInterceptor())
                 .target(Bank.class, "https://api.examplebank.com");
  }
}

Another common example of an interceptor would be authentication, such as using the built-in BasicAuthRequestInterceptor.

public class Example {
  public static void main(String[] args) {
    Bank bank = Feign.builder()
                 .decoder(accountDecoder)
                 .requestInterceptor(new BasicAuthRequestInterceptor(username, password))
                 .target(Bank.class, "https://api.examplebank.com");
  }
}

Custom @Param Expansion

Parameters annotated with Param expand based on their toString. By specifying a custom Param.Expander, users can control this behavior, for example formatting dates.

public interface Api {
  @RequestLine("GET /?since={date}") Result list(@Param(value = "date", expander = DateToMillis.class) Date date);
}

Dynamic Query Parameters

A Map parameter can be annotated with QueryMap to construct a query that uses the contents of the map as its query parameters.

public interface Api {
  @RequestLine("GET /find")
  V find(@QueryMap Map<String, Object> queryMap);
}

This may also be used to generate the query parameters from a POJO object using a QueryMapEncoder.

public interface Api {
  @RequestLine("GET /find")
  V find(@QueryMap CustomPojo customPojo);
}

When used in this manner, without specifying a custom QueryMapEncoder, the query map will be generated using member variable names as query parameter names. You can annotate a specific field of CustomPojo with the @Param annotation to specify a different name to the query parameter. The following POJO will generate query params of "/find?name={name}&number={number}&region_id={regionId}" (order of included query parameters not guaranteed, and as usual, if any value is null, it will be left out).

public class CustomPojo {
  private final String name;
  private final int number;
  @Param("region_id")
  private final String regionId;

  public CustomPojo (String name, int number, String regionId) {
    this.name = name;
    this.number = number;
    this.regionId = regionId;
  }
}

To setup a custom QueryMapEncoder:

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .queryMapEncoder(new MyCustomQueryMapEncoder())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

When annotating objects with @QueryMap, the default encoder uses reflection to inspect provided objects Fields to expand the objects values into a query string. If you prefer that the query string be built using getter and setter methods, as defined in the Java Beans API, please use the BeanQueryMapEncoder

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .queryMapEncoder(new BeanQueryMapEncoder())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

Error Handling

If you need more control over handling unexpected responses, Feign instances can register a custom ErrorDecoder via the builder.

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .errorDecoder(new MyErrorDecoder())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}

All responses that result in an HTTP status not in the 2xx range will trigger the ErrorDecoder's decode method, allowing you to handle the response, wrap the failure into a custom exception or perform any additional processing. If you want to retry the request again, throw a RetryableException. This will invoke the registered Retryer.

Retry

Feign, by default, will automatically retry IOExceptions, regardless of HTTP method, treating them as transient network related exceptions, and any RetryableException thrown from an ErrorDecoder. To customize this behavior, register a custom Retryer instance via the builder.

The following example shows how to refresh token and retry with ErrorDecoder and Retryer when received a 401 response.

public class Example {
    public static void main(String[] args) {
        var github = Feign.builder()
                .decoder(new GsonDecoder())
                .retryer(new MyRetryer(100, 3))
                .errorDecoder(new MyErrorDecoder())
                .target(Github.class, "https://api.github.com");

        var contributors = github.contributors("foo", "bar", "invalid_token");
        for (var contributor : contributors) {
            System.out.println(contributor.login + " " + contributor.contributions);
        }
    }

    static class MyErrorDecoder implements ErrorDecoder {

        private final ErrorDecoder defaultErrorDecoder = new Default();

        @Override
        public Exception decode(String methodKey, Response response) {
            // wrapper 401 to RetryableException in order to retry
            if (response.status() == 401) {
                return new RetryableException(response.status(), response.reason(), response.request().httpMethod(), null, response.request());
            }
            return defaultErrorDecoder.decode(methodKey, response);
        }
    }

    static class MyRetryer implements Retryer {

        private final long period;
        private final int maxAttempts;
        private int attempt = 1;

        public MyRetryer(long period, int maxAttempts) {
            this.period = period;
            this.maxAttempts = maxAttempts;
        }

        @Override
        public void continueOrPropagate(RetryableException e) {
            if (++attempt > maxAttempts) {
                throw e;
            }
            if (e.status() == 401) {
                // remove Authorization first, otherwise Feign will add a new Authorization header
                // cause github responses a 400 bad request
                e.request().requestTemplate().removeHeader("Authorization");
                e.request().requestTemplate().header("Authorization", "Bearer " + getNewToken());
                try {
                    Thread.sleep(period);
                } catch (InterruptedException ex) {
                    throw e;
                }
            } else {
                throw e;
            }
        }

        // Access an external api to obtain new token
        // In this example, we can simply return a fixed token to demonstrate how Retryer works
        private String getNewToken() {
            return "newToken";
        }

        @Override
        public Retryer clone() {
            return new MyRetryer(period, maxAttempts);
        }
}

Retryers are responsible for determining if a retry should occur by returning either a true or false from the method continueOrPropagate(RetryableException e); A Retryer instance will be created for each Client execution, allowing you to maintain state bewteen each request if desired.

If the retry is determined to be unsuccessful, the last RetryException will be thrown. To throw the original cause that led to the unsuccessful retry, build your Feign client with the exceptionPropagationPolicy() option.

Response Interceptor

If you need to treat what would otherwise be an error as a success and return a result rather than throw an exception then you may use a ResponseInterceptor.

As an example Feign includes a simple RedirectionInterceptor that can be used to extract the location header from redirection responses.

public interface Api {
  // returns a 302 response
  @RequestLine("GET /location")
  String location();
}

public class MyApp {
  public static void main(String[] args) {
    // Configure the HTTP client to ignore redirection
    Api api = Feign.builder()
                   .options(new Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, false))
                   .responseInterceptor(new RedirectionInterceptor())
                   .target(Api.class, "https://redirect.example.com");
  }
}

Metrics

By default, feign won't collect any metrics.

But, it's possible to add metric collection capabilities to any feign client.

Metric Capabilities provide a first-class Metrics API that users can tap into to gain insight into the request/response lifecycle.

Dropwizard Metrics 4

public class MyApp {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                         .addCapability(new Metrics4Capability())
                         .target(GitHub.class, "https://api.github.com");

    github.contributors("OpenFeign", "feign");
    // metrics will be available from this point onwards
  }
}

Dropwizard Metrics 5

public class MyApp {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                         .addCapability(new Metrics5Capability())
                         .target(GitHub.class, "https://api.github.com");

    github.contributors("OpenFeign", "feign");
    // metrics will be available from this point onwards
  }
}

Micrometer

public class MyApp {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
                         .addCapability(new MicrometerCapability())
                         .target(GitHub.class, "https://api.github.com");

    github.contributors("OpenFeign", "feign");
    // metrics will be available from this point onwards
  }
}

Static and Default Methods

Interfaces targeted by Feign may have static or default methods (if using Java 8+). These allows Feign clients to contain logic that is not expressly defined by the underlying API. For example, static methods make it easy to specify common client build configurations; default methods can be used to compose queries or define default parameters.

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

  @RequestLine("GET /users/{username}/repos?sort={sort}")
  List<Repo> repos(@Param("username") String owner, @Param("sort") String sort);

  default List<Repo> repos(String owner) {
    return repos(owner, "full_name");
  }

  /**
   * Lists all contributors for all repos owned by a user.
   */
  default List<Contributor> contributors(String user) {
    MergingContributorList contributors = new MergingContributorList();
    for(Repo repo : this.repos(owner)) {
      contributors.addAll(this.contributors(user, repo.getName()));
    }
    return contributors.mergeResult();
  }

  static GitHub connect() {
    return Feign.builder()
                .decoder(new GsonDecoder())
                .target(GitHub.class, "https://api.github.com");
  }
}

Async execution via CompletableFuture

Feign 10.8 introduces a new builder AsyncFeign that allow methods to return CompletableFuture instances.

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

public class MyApp {
  public static void main(String... args) {
    GitHub github = AsyncFeign.builder()
                         .decoder(new GsonDecoder())
                         .target(GitHub.class, "https://api.github.com");

    // Fetch and print a list of the contributors to this library.
    CompletableFuture<List<Contributor>> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors.get(1, TimeUnit.SECONDS)) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

Initial implementation include 2 async clients:

  • AsyncClient.Default
  • AsyncApacheHttp5Client

Maven’s Bill of Material (BOM)

Keeping all feign libraries on the same version is essential to avoid incompatible binaries. When consuming external dependencies, can be tricky to make sure only one version is present.

With that in mind, feign build generates a module called feign-bom that locks the versions for all feign-* modules.

The Bill Of Material is a special POM file that groups dependency versions that are known to be valid and tested to work together. This will reduce the developers’ pain of having to test the compatibility of different versions and reduce the chances to have version mismatches.

Here is one example of what feign BOM file looks like.

Usage

<project>

...

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.github.openfeign</groupId>
        <artifactId>feign-bom</artifactId>
        <version>??feign.version??</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

feign's People

Contributors

aalmiray avatar adriancole avatar ahus1 avatar arthurscchan avatar ashleyfrieze avatar bstick12 avatar davidmc24 avatar dependabot[bot] avatar flolei avatar gimbimloki avatar gromspys avatar jerzykrlk avatar jkomoroski avatar jkschneider avatar kdavisk6 avatar mroccyen avatar mstryoda avatar nmiyake avatar pilak avatar pnepywoda avatar rfalke avatar ricardordzg avatar rspieldenner avatar santhosh-tekuri avatar simy4 avatar snyk-bot avatar spencergibb avatar velo avatar vitalijr2 avatar wplong11 avatar

Stargazers

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

Watchers

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

feign's Issues

Skip Query parameters when value is null

Currently, when a @QueryParam value is specified, and the user passes null, there's a literal null in the http request. Using as close to JAX-RS and URI-Template semantics as we can, we should allow the client (only in jax-rs) to skip a query parameter on null.

Support Basic Authentication

I've got a working implementation of this as a RequestInterceptor that I'd be happy to contribute. Assuming that there's interest in this, would you prefer it within core, in a separate module in the main repo, or publishing it as a standalone repo elsewhere?

Using header to pass additional information

I've seen the RequestInterceptor example. Does Feign allow user to put stuff in header for passing additional information on each call without recreating the client?

I have client code that will create a Feign client one time, but based on the Request Interceptor example, we need to recreate the feign client every time the code is called.

Support query param encoders

It'd be nice to be able to supply custom query param encoders. For example, sometimes we need to encode a Date query param as a String with a given format.

Release 7.1.0

Let's cut 7.1, as it has the feign.Param annotation needed to bridge to 8.x. Also includes OkHttp support and some bug fixes.

Investigate using types alone for Decoder bindings

currently, we use method key (ex. IAM or IAM#auth(String,String)) to wire decoders to the methods that need them. This is consistent with error handlers, etc, but possibly overkill for decoders, where the intended result is always correlated to the return type of the method (or the type param of the observer).

If we switch to type-only, it is possible for us to use Set bindings, to collect decoders. Something far less complicated then our current approach.

Release Version 6.1.1

It would be really helpful if you can please release version 6.1.1 in maven central?

interface level header defaults

I implemented an enhanced contract that extends the default contract but adds the ability to set interface level Headers as defaults unless overridden at the method level..

I also added convenience annotations for ContentType & Accept since those (at least in our experience) are most typically set.
The annotations follow more of the jaxrs style, but another option I could see would be adding these as settables in the Feign.Builder when creating the client.. I'm on fence as which one i like better.

Are either of these of interest as a pull? @adriancole

Support binary request bodies

There are cases where it may be useful to submit binary content as the body for a request. Examples of this include uploading files or using content encodings (such as compression).

This was first discussed under issue #52.

Check for a default Accept header in mock tests

If feign doesn't explicitly set an Accept header then HttpURLConnection.java (line 376) will set the Accept header below.

Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2

This can cause API validations to choke, if they're expecting no Accept header or an Accept header from a predefined list of application/json, application/xml, etc.

Feign should check for default Accept header in mock tests to prevent this scenario from occurring.

See this comment in Denominator for more context.

Provide means to trace calls

A vendor management system at netflix started looking at feign and realized they needed a way to trace every java call that results in feign calls.

While this could be done via the feign Logger, it is cleaner to extract out a profiling interface. I don't have a specific design, yet, but we could borrow from retrofit's Profiler.

Before making a final choice, we should document what metadata is needed in the requests and see how (if at all) this is addressed in other systems such as JAX-RS 2.0, WebSocket, Netty, etc.

Named Query Parameters on GET requests

Picking up the latest version I'd expect to be able to do something like the following

@RequestLine("GET /users?name={name}")
public JsonObject getUsersByName(@nAmed("name") String name);

Unfortunately this doesn't seem to work. The default encoder receives a LinkedHashMap and complains about it not being a String. The GsonEncoder copes fine with it but the end result is that a POST is made with the query parameters encoded into the body.

Specific HTTP error code FeignExceptions

Currently any error caused by the executed HTTP call results in a FeignException. This makes it impossible to handle according to the type of HTTP error. (Or am I missing something?)

Would it be an idea to:

  • have HTTP error code ranged exceptions, like Http4xxFeignException for any 400-499 HTTP error, and Http5xxFeignException for 500-599 HTTP errors
  • have the specific error code available in the exception

Feature Request: add support for custom request Content-Encoding (such as gzip)

I have an API that I'd like to use Feign to connect to that requires that the POST request body use gzip content encoding.

This feels to me like something that should be possible using a RequestInterceptor, but the current structure of RequestTemplate (storing body as String, and converting to bytes in the client) doesn't really seem like it supports it. I know I could add support for this using a custom Client implementation. Is there a better approach?

feign with hystrix

I'd like to use hystrix with feign, but it appears i'd have to shoehorn it into the invocation handler factory, and if i wanted the ability to do things async it would be more changes to methodmetadata to look for Future<?> return types..
I could do this on the outside of the feign, but that seems kludgy so i wanted to see if there were any better ideas floating around?

Logger should emit on IOException

It would be really helpful to trackdown things like socket timeout errors, if the logger emitted a message when an IOException occurs.

This came up using denominator where a service emitted read timeouts longer than expected.

Ability to register modules w/ Target

Maybe I'm missing something, but it doesn't seem possible to mix a module with the .target()-style builder. It'd be great to have a .module(Object...) method so targets can be mixed with modules such as the JAXRS module. I'd be happy to PR it if I'm not missing something.

Disabling exceptions for error statuses when using Response.

As far as I can tell this isn't currently possible. I'm trying to use feign for writing integration tests against an internal service. At the moment I've had to write my own ErrorDecoder to intercept and clone the Response object. It would be much easier from my perspective to disable the exceptions.

I'll try to get it implemented myself. But I'd need a little help with working out why the gradle build doesn't appear to work.
"Artifact 'org.codehaus.plexus:plexus-utils:2.0.5@jar' not found."

I can't even run gradle dependencies to find out what's pulling it in.

Add example of a nasty api

The GitHub example seems like a nice intro to Feign, but it is actually a poor choice. The json model is clean, as are the way rest calls are made. Most apis are not even close to as clean as this, so the example leads to a cliff.

@benjchristensen had a good idea to add an example a nastier, more realistic api, such as wikipedia. Let's do that.

[question] How to use Feign with a JAX-RS interface defining jaxrs.Response as return types

Hi,

I'm using Feign with a JAX-RS interface using JAX-RS Response return types (not feign.Response).
For instance:

@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
public interface UserContract {
    @POST
    @Path("/")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response create(User user);

    ...

When using feign and calling the "create" method, it seems that feigns tries to deserialize the jaxrs.Response object from the HTTP reponse.

Well it makes sense since it's the return type but it's obviously not what I expect. What would be the best way to handle jaxrs.Response type with feign ?

Thanks

Support parameterized base apis

Consider the example:

public interface BaseAPI {
     @RequestLine("GET /health")
     String health();
     @RequestLine("GET /all")
     List<Entity> all();
}

And then some specific API:

public interface CustomAPI extends BaseAPI {
     @RequestLine("GET /custom")
     String custom();
}

I'd like to use CustomAPI when creating Feign client and be able to access methods of the BaseAPI.

Currently only methods defined directly in the CustomAPI are bound to handlers.

It looks like the only required changes to be made are:

1 feign.Contract.BaseContract.parseAndValidatateMetadata(Class<?> declaring):

//      for (Method method : declaring.getDeclaredMethods()) {
      for (Method method : declaring.getMethods()) {

2 feign.ReflectiveFeign.newInstance(Target<T> target):

//    for (Method method : target.type().getDeclaredMethods()) {
    for (Method method : target.type().getMethods()) {

I was able to get the required functionality with the above changes.
Would be great to have this functionality either out of the box or easily configurable.

Move to Dagger 2

Dagger 1.x is not likely to continue. Dagger 2 is.

I'm interested enough to help port everything here (and maybe fix some bugs etc along the way), plus revamp denominator accordingly.

Please ping this issue if you feel strongly that feign should not move to Dagger 2.

Ensure unused Provides methods fail at compile-time

For many users, feign will be the first time they see Dagger. Dagger has a wonderful system of compile-time failures. However, when Module(library=true) is set, unused bindings will not fail. This causes an issue when for example, someone binds a parameterized type where a raw type is needed.

Let's hunt down anything that implies users will have to supply Module(library=true) and kill it or identify why.

cc @benjchristensen

Simplify Feign

There are a number of ideas in feign that didn't work out very well, are edge case, or pure over-engineering. We should get rid of them as feign is used in production and should focus its code base, failure cases, and WTFs on things relevant to those use cases.

  • For those who do not use dagger, or do not wish to, we could expose a builder which defines all dependencies explicitly that can be provided. see issue #34
  • Codec types such as Decoder, Encoder, should not have a type parameter. The implicit responsibility of type adapters is better taken by implementations like gson. Moreover, binding to a raw-type Set<Decoder> is confusing when the type parameter is meaningful.
  • IncrementalDecoder should be axed. This is even more hairy than above, and even less often used. Streaming is application specific (twitter newlines vs S3 buckets) and rare. There are no feign clients I'm aware of that actually employ incremental parsing. Finally, multiplexed (SPDY, HTTP2) or message oriented (WebSocket) approaches imply less need for really long stream decodes over time.
  • Pattern decoders should be axed. Existing use of RegEx decoders was limited to XML responses, so rather than maintain a sizable chunk of code for both SAX and RegEx, let's prefer SAX.
  • Observer/Observable should be axed. Due to above, getting an Observer to actually work per-element is application-specific, not format specific, and would imply a lot of confusing feign config. While there is usefulness in exposing the response (or even its reader) as a callback, Observable overshoots this and adds complexity. Similarly to IncrementalDecoder, Observable isn't used in feign client in production I'm aware of! Currently, those wanting Observer could do so by submitting a Callable to a feign method w/ rx.Observer. Experimentation would end up in an extension per #25 and #26

This doesn't mean we shouldn't play with ideas or that these ideas will never reach prod. Just that we shouldn't commit all ideas to trunk as that increases the cognitive and maintenance load of feign without helping real production use. This may imply we need a "labs" repo or persistent forks to facilitate research.

Release 7.2.0

Adds features needed to cut a denominator release.

Remove need to specify overrides = true

When Module(overrides=true) is set, any @Provides(type = SET) methods will override as opposed to adding to ones specified by feign.

This leads to hacks inside ReflectiveFeign like below, which manually enforce a base set of decoders. These cannot be overridden by dagger at all.

      this.decoders.put(void.class, stringDecoder);
      this.decoders.put(Response.class, stringDecoder);
      this.decoders.put(String.class, stringDecoder);

As soon as Dagger 1.1 is out (likely this week), we'll have access to the SET_VALUES feature, which should allow us to eliminate the troublesome Module(overrides=true) features.

With this in, we should update examples and any how-to docs to show how to wire up feign so as to not require or suggest Module(overrides=true), except in very specific cases.

cc @benjchristensen

Provide easier support for empty targets

In some cases, it may be useful to have Feign instances that don't have any base URL at all. Instead, all methods would include a URI parameter.

Currently, this can be accomplished by using a custom implementation of Target that requires a URL to be provided. I've included an example below. This target can then be passed to Feign.builder().target(Target) Feign.create(Target, Object...).

If such an EmptyTarget were included in feign-core, we could add signatures like Feign.builder().target(Class) that allow creating Feign instances without a base URL and without explicitly specifying a Target type. A signature for Feign.create will require a bit more thought, since Feign.create(Class, Object...) would conflict with the pre-existing Feign.create(Class, String, Object...).

public class EmptyTarget<T> implements Target<T> {
    private final Class<T> type;
    private final String name;

    public EmptyTarget(Class<T> type) {
      this.type = checkNotNull(type, "type");
      this.name = "empty:" + type.getSimpleName();
    }

    @Override
    public Class<T> type() {
        return type;
    }

    @Override
    public String name() {
        return name;
    }

    @Override
    public String url() {
        throw new UnsupportedOperationException("Empty targets don't have URLs");
    }

    @Override
    public Request apply(RequestTemplate input) {
        if (input.url().indexOf("http") != 0) {
            throw new UnsupportedOperationException("Request with non-absolute URL not supported with empty target");
        }
        return input.request();
    }

    @Override
    public int hashCode() {
        return Arrays.hashCode(new Object[]{type, name});
    }

    @Override
    public boolean equals(Object obj) {
      if (obj == null) {
          return false;
      }
      if (this == obj) {
          return true;
      }
      if (EmptyTarget.class != obj.getClass()) {
          return false;
      }
      EmptyTarget<?> that = EmptyTarget.class.cast(obj);
      return this.type.equals(that.type) && this.name.equals(that.name);
    }
}

Add Feign.Builder

For those who do not use dagger, or do not wish to, we could expose a builder which defines all dependencies explicitly that can be provided. This includes logging config, decoders, etc. This could be modeled similarly to retrofit RestAdapter.Builder or jclouds ContextBuilder

ex.

api = Feign.builder()
                .loggingLevel(BASIC)
                .decoder(customType)
                .target(GitHub.class, "http://foo");

Note that this will still use dagger under the scenes, albeit via reflection. When/if someone wants to switch to pure dagger, they could use the module system directly.

cc @benjchristensen

investigate async http client integration

Per a discussion with @NiteshKant, I believe we can make a compatible http response callback interface to facilitate nio support.

Context

Many async http clients provide a callback interface such as below

Future<Integer> f = asyncHttpClient.prepareGet("http://www.ning.com/").execute(
   new AsyncCompletionHandler<Integer>(){

    @Override
    public Integer onCompleted(Response response) throws Exception{
        // Do something with the Response
        return response.getStatusCode();
    }

    @Override
    public void onThrowable(Throwable t){
        // Something wrong happened.
    }
});

https://github.com/AsyncHttpClient/async-http-client

How

It should be possible to extend our Client to be compatible with the callback system from one or more async http clients, avoiding the need to independently manage threads in Feign.

ex.

interface Client {
  // existing
  Response execute(Request request, Options options) throws IOException;
  // new
  void execute(Request request, Options options, IncrementalCallback<Response> responseCallback);

An asynchronous client would need to map their callback to ours.

Any synchronous http client (including our default) could extend from a base class that implements the callback method like so

void execute(Request request, Options options, IncrementalCallback<Response> responseCallback) {
      httpExecutor.get().execute(new Runnable() {
        @Override public void run() {
          Error error = null;
          try {
            responseCallback.onNext(execute(request, options));
            responseCallback.onSuccess();
          } catch (Error cause) {
            // assign to a variable in case .onFailure throws a RTE
            error = cause;
            responseCallback.onFailure(cause);
          } catch (Throwable cause) {
            responseCallback.onFailure(cause);
          } finally {
            Thread.currentThread().setName(IDLE_THREAD_NAME);
            if (error != null)
              throw error;
          }
        }
      }
  }

release feign 5.4.0 and 6.0.0

We haven't had a release in a while..

release 5.4.0

  • update 5.x branch gradle.properties 5.4.0-SNAPSHOT
  • kick off 5.x release job to release 5.4.0 from 5.x branch
  • don't forget to close and release the oss sonatype staging repo!

release 6.0.0

  • cut branch 6.x from master
  • update master branch gradle.properties 7.0.0-SNAPSHOT
  • create 6.x release job based on 5.x one
  • kick off 6.x release job to release 6.0.0 from 6.x branch
  • don't forget to close and release the oss sonatype staging repo!
  • update main example-github and example-wikipedia to use version 6.0.0
  • update denominator to use version 6.0.0 

cc @cfieber @davidmc24

Add interceptor

A vendor management system at netflix started looking at feign and realized they needed a way to add contextual headers to a request, such as application id, and request id.

While this could be done with Target.apply, the essence of the change is more environment-specific. Exposing an Interceptor interface could be a better way to model this.

class FooInterceptor implements RequestInterceptor {
  @Override public void intercept(RequestTemplate request) {
    request.header("extra", "value");
  }
}

Enforce JAX-RS interfaces are interpreted as defined for services

Particularly in relation to @Provides and @Consumes, we need to interpret jax-rs annotations knowing that they are defined on service apis. For example, @Consumes implies that the server accepts the media type. So, feign's interpretation should be that @Consumes affects the content-type header, not the accept header, sent.

cc @syed-haq

Support for Iterable Query Param

Currently feign doesn't support iterable query param. Could you add this support?

Also, it should probably throw a better exception then the current one when using iterable query param:

feign.FeignException: status 505 reading ; content:

implement Observable

making this v4 so that we can delete IncrementalCallback :)

If viewed in the light that Iterator is to Observer as Iterable is to Observable, the essential relationships needed to make a functional Observer pattern are relatively tight.

  • add Subscription interface, with a single method unsubscribe() which means "stop giving me stuff!"
  • rename IncrementalCallback to Observer
  • return Subscription on methods that accept an Observer param.
    • This is the Async equiv to returning a lazy Iterator from a single http request
    • ex. `Subscription contributors(String account, String repo, Observer contributors);
  • allow Observable as a return type
    • Observer has a single method: Subscription subscribe(Observer<T> observer);
    • each time subscribe is called, a new http request is sent and the observer observes that.
    • This is the Async equiv to an Iterable, where each Iterator is a new http request

RibbonClient doesn't support HTTPS

Using the following test - https://gist.github.com/bstick12/2a21ffd30b14ec68716f#file-ribbonclienttest

You will see the following exception raised in the logs

21:04:25.583 [pool-1-thread-2] DEBUG c.n.l.LoadBalancerExecutor - Got error java.net.SocketException: Unexpected end of file from server when executed on server api.github.com:443

This is due to the request being made to http://api.github.com:443/.

The URI created in RibbonClient does include a scheme and eventually com.netflix.loadbalancer.LoadBalancerContext.deriveSchemeAndPortFromPartialUri(URI) will default the scheme to http.

Getting started guide

While there are a couple examples, there's no step-by-step introduction for using feign, or how to make custom decoders, etc.

IncrementalCallback support

Incremental callbacks facilitate asynchronous invocations, where the http connection and decoding of elements inside the response are decoupled from the calling thread.

In a typical synchronous response, the calling thread blocks on the http connect, reading from the socket, and also decoding of the response data into a java type. As such, a java type is returned.

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  Iterator<Contributor> contributors(@Named("owner") String owner, @Named("repo") String repo);
}

In an incremental callback pattern, the calling thread blocks until the request is queued, but processing of the response (including connect, read, decode) are free to be implemented on different threads. To unblock the caller, void is returned.

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  contributors(@Named("owner") String owner, @Named("repo") String repo, IncrementalCallback<Contributor> contributors);
}

Motivation

Denominator works primarily on high latency collections, where responses can take up to minutes to return. Even-though denominator returns Iterators, allowing iterative response processing, usually update granularity is per http request due to response parsing semantics of returning a list.

Ex. Sax parsing into a list which is later returned.

    if (qName.equals("HostedZone")) {
            zones.add(Zone.create(this.name, id));
...
    @Override
    public ZoneList getResult() {
        return zones;

Ex. Incremental callback pattern allows for immediate visibility of new elements

    if (qName.equals("HostedZone")) {
            observer.onNext(Zone.create(this.name, id));

Also, Netflix RxJava is increasingly employed, so optimizing for Observers makes sense.

IncrementalCallback Design

The following design takes from RxJava Observer and Retrofit Callback.

public interface IncrementalCallback<T> {
  void onNext(T element);
  void onSuccess();
  void onFailure(Throwable cause);
}

Why Throwable onFailure instead of Exception

The design we are discussing is how to address async callbacks in a Java idiomatic way. We issue Throwable on error instead of Exception, as Feign doesn't have a catch-all exception wrapper and Errors could be the reason why the failure occurred. This allows visibility of Errors such as AssertionErrors or thread starvation to propagate without confusing exception wrappers. It also allows us to simply propagate exception.getCause() without a wrapper.

The above rationale supports the case for Throwable vs Exception where Feign exists in isolation. Where Feign interacts with other libraries unveils other considerations. For example, Every current java async web callback issues throwable, not exception, for error signals. If we chose to retain Exception as opposed to Throwable, converting to or from these interfaces will be lossy.

how do we make this callback asynchronous?

should we decode the response?

IncrementalCallback vs ...

In order to support asynchronous processing of http responses, we need to implement responses of Future, (RxJava) Observable, or allow users to pass in a Callback or and Observer.

As Observable is the centerpiece of RxJava's reactive pattern, it is a relatively wide interface. As such, the source for Observable.java is larger than the entire codebase of feign including tests. Making a move to couple to Observable seems overkill just to afford asynchronous invocation.

Future semantics are hard to guarantee, as operations such as cancel() rarely perform as expected with blocking i/o. For example, many implementations of http futures simply fire a callable which has no means to stop the connection or stop reading from it. Moreover timeouts are hard to make sense in future. For example connect vs read timeouts are hard to understand. Typically a response is returned after connect, but during connection you have no means to stop it. After future.get is returned, another timeout is possible reading from the stream. In summary while Future is often used, it is a tricky abstraction.

Callback and Observer approaches are simplest and incur no core dependencies. They can both be type-safe interface, such as the one in Retrofit or android-http. IncrementalCallback slightly wins as it allows for incremental parsing, and doesn't imply that Feign is Observable.

Why not reuse RxJava Observer?

RxJava Observer is not a wide interface, so much easier to grok than Observable. However, the Observer type in rx is a concrete class and implies a dependency on rxjava, a dependency which is an order of magnitude larger than feign itself. Decoupling in core, and recoupling as an extension is the leanest way to interop.

Release 7.0.0

Let's release 7.0.0, particularly as some backports are only partially merged into 6.x. Doing this also frees up master to do more bold things.

  • cut branch 7.x from master
  • update master branch gradle.properties 8.0.0-SNAPSHOT
  • create 7.x release job based on 6.x one
  • kick off 7.x release job to release 7.0.0 from 7.x branch
  • don't forget to close and release the oss sonatype staging repo!
  • update main example-github and example-wikipedia to use version 7.0.0
  • update denominator to use version 7.0.0 

cc @qualidafial @davidmc24 @rspieldenner

Use of `java.lang.reflect.Type` not sufficient for proper generic types

As you may be aware, Java's support for handling and resolving generic types is not particularly good. For background info, here -- http://www.cowtowncoder.com/blog/archives/2010/12/entry_436.html -- is a blog entry I wrote a while back explaining why use of java.lang.reflect.Type is not sufficient for handling generic types of method calls.

Unfortunately many frameworks, such as JAX-RS, have used this is their canonical type representation.
Note that this is not just a theoretical issue: it is very difficult to, for example, implement java.lang.reflect.Type. The only uses for it really are:

  1. Pass Class<?> instances, OR
  2. Pass Type one gets via reflection from Method or Field instances.

Of these, second case is the problematic one, since it is not possible to resolve many cases that involve type parameters.

An immediate problem is that there aren't many obviously better mechanisms. For what it is worth, Classmate:

https://github.com/cowtowncoder/java-classmate

does actually solve the problem of handling of generic types. But downside is that not many codec libraries support ClassMate -- because of this, it may or may not be a good solution here.

Another smaller improvement would be introduction of "type token" type (Jackson and Gson have their own token types), which at least would allow static passing of generic types, with constructs like:

new TypeReference<Map<String,Integer>>() { }

The problem here is similar to ClassMate case: since JDK does not provide such standard wrapper, nor is there a universally used 3rd party lib, it may be difficult to add full support.

JmxMonitorRegistry race condition with RibbonClient

If you run the following test - https://gist.github.com/bstick12/2a21ffd30b14ec68716f#file-ribbonclienttest

You will occasionally get the the following warning issued from servo.

The com.netflix.loadbalancer.LoadBalancerContext.initWithNiwsConfig(IClientConfig) registers the load balancer and the RibbonClient is recreating the LBClient on each request.

0:44:38.155 [pool-1-thread-2] WARN  c.n.servo.jmx.JmxMonitorRegistry - Unable to register Monitor:MonitorConfig{name=Client_github, tags=class=LBClient, policy=DefaultPublishingPolicy}
javax.management.InstanceNotFoundException: com.netflix.servo:name=github_LoadBalancerExecutionTimer,statistic=max,class=LBClient,type=GAUGE,id=Client_github,unit=MILLISECONDS
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.getMBean(DefaultMBeanServerInterceptor.java:1095) ~[na:1.7.0_72]
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.exclusiveUnregisterMBean(DefaultMBeanServerInterceptor.java:427) ~[na:1.7.0_72]
    at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.unregisterMBean(DefaultMBeanServerInterceptor.java:415) ~[na:1.7.0_72]
    at com.sun.jmx.mbeanserver.JmxMBeanServer.unregisterMBean(JmxMBeanServer.java:546) ~[na:1.7.0_72]
    at com.netflix.servo.jmx.JmxMonitorRegistry.register(JmxMonitorRegistry.java:82) ~[servo-core-0.7.4.jar:na]
    at com.netflix.servo.jmx.JmxMonitorRegistry.register(JmxMonitorRegistry.java:106) ~[servo-core-0.7.4.jar:na]
    at com.netflix.servo.DefaultMonitorRegistry.register(DefaultMonitorRegistry.java:135) [servo-core-0.7.4.jar:na]
    at com.netflix.servo.monitor.Monitors.registerObject(Monitors.java:207) [servo-core-0.7.4.jar:na]
    at com.netflix.loadbalancer.LoadBalancerContext.initWithNiwsConfig(LoadBalancerContext.java:111) [ribbon-loadbalancer-2.0-RC5.jar:na]
    at com.netflix.loadbalancer.LoadBalancerContext.<init>(LoadBalancerContext.java:82) [ribbon-loadbalancer-2.0-RC5.jar:na]
    at com.netflix.loadbalancer.LoadBalancerExecutor.<init>(LoadBalancerExecutor.java:45) [ribbon-loadbalancer-2.0-RC5.jar:na]
    at com.netflix.client.AbstractLoadBalancerAwareClient.<init>(AbstractLoadBalancerAwareClient.java:54) [ribbon-loadbalancer-2.0-RC5.jar:na]
    at feign.ribbon.LBClient.<init>(LBClient.java:47) [feign-ribbon-7.2.0.jar:7.2.0]
    at feign.ribbon.RibbonClient.lbClient(RibbonClient.java:76) [feign-ribbon-7.2.0.jar:7.2.0]
    at feign.ribbon.RibbonClient.execute(RibbonClient.java:64) [feign-ribbon-7.2.0.jar:7.2.0]
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:95) [feign-core-7.2.0.jar:7.2.0]
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:74) [feign-core-7.2.0.jar:7.2.0]
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:101) [feign-core-7.2.0.jar:7.2.0]
    at com.brendan.atlassian.$Proxy6.users(Unknown Source) [na:na]
    at com.brendan.atlassian.RibbonClientTest$1.run(RibbonClientTest.java:56) [test-classes/:na]
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:471) [na:1.7.0_72]

CHANGES.md skips 6.0.1?

Currently, it looks to me like there are entries in CHANGES.md for 6.0 and 6.0.2, but nothing for 6.0.1. From looking around a bit, it looks like 6.0.1 was released, and should include the change that is listed as 6.0.2. Probably the correct fix is to change the existing entry in CHANGES.md from 6.0.2 to 6.0.1?

Recommend Projects

  • React photo React

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

  • Vue.js photo Vue.js

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

  • Typescript photo Typescript

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

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

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

Recommend Topics

  • javascript

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

  • web

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

  • server

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

  • Machine learning

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

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

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

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.