1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/mirrors-feign

Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Feign упрощает процесс написания Java HTTP-клиентов

Присоединяйтесь к обсуждению на https://gitter.im/OpenFeign/feign CI Maven Central

Feign — это Java-биндер для HTTP-клиентов, вдохновленный Retrofit, JAXRS-2.0 и WebSocket. Первая цель Feign заключалась в уменьшении сложности привязки Denominator к HTTP-интерфейсам независимо от RESTfulness.


Почему Feign, а не X?

Feign использует инструменты, такие как Jersey и CXF, для написания Java-клиентов для ReST или SOAP-сервисов. Кроме того, Feign позволяет вам писать собственный код поверх библиотек HTTP, таких как Apache HC. Feign соединяет ваш код с HTTP-интерфейсами с минимальными затратами и кодом за счет настраиваемых декодеров и обработки ошибок, которые могут быть написаны для любого текстового HTTP-интерфейса.

Как работает Feign?Feign работает путем преобразования аннотаций в шаблонизированный запрос. Аргументы применяются к этим шаблонам в прямом порядке перед выводом. Хотя Feign ограничен поддержкой текстовых API, он значительно упрощает аспекты системы, такие как повторное выполнение запросов. Кроме того, Feign делает тестирование ваших преобразований легким, зная это.### Совместимость с версиями Java

Feign 10.x и выше построены на Java 8 и должны работать на Java 9, 10 и 11. Для тех, кто нуждается в совместимости с JDK 6, пожалуйста, используйте Feign 9.x.

Обзор функций

Это карта с текущими ключевыми функциями, предоставляемыми Feign:

Обзор в виде схемы ума

План действий

Feign 11 и далее

Сделать клиенты API прощеКраткосрочные задачи - что мы делаем сейчас. ⏰

  • Кэширование ответов
    • Поддержка кэширования ответов API. Разрешить пользователям определять условия, при которых ответ может быть кэширован, и тип кэширования, который должен быть использован.
    • Поддержка кэширования в памяти и внешних реализаций кэширования (EhCache, Google, Spring и т.д.).
  • Полная поддержка выражений шаблонов URI
  • Переработка API Logger
    • Переработка API Logger для соответствия фреймворкам, таким как SLF4J, предоставляя общий умственный модель для логирования в Feign. Эта модель будет использоваться Feign самим и предоставит более ясное представление о том, как будет использоваться Logger.
  • Переработка API Retry
    • Переработка API Retry для поддержки пользовательских условий и лучшего контроля над политиками отложенного повторного выполнения. Это может привести к несовместимым изменениям, нарушающим обратную совместимостьСреднесрочные задачи - что будет дальше. ⏲

  • Поддержка асинхронного выполнения через CompletableFuture
    • Разрешить цепочку выполнения Future и управление исполнителями для жизненного цикла запроса/ответа. Реализация потребует несовместимых изменений, нарушающих обратную совместимость. Однако это функциональное требование необходимо для того, чтобы можно было рассмотреть реактивное выполнение.
  • Поддержка реактивного выполнения через Reactive Streams
    • Для JDK 9+ рассмотреть нативную реализацию, использующую java.util.concurrent.Flow.
    • Поддержка Project Reactor и RxJava 2+ на JDK 8.

Долгосрочные задачи - будущее ☁️

  • Дополнительная поддержка прерывателей цепи.
    • Поддержка дополнительных реализаций прерывателей цепи, таких как Resilience4J и прерывателя цепи Spring

Использование

Библиотека feign доступна из Maven Central.

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

Основы

Использование обычно выглядит так, адаптация канонического примера Retrofit.

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");

    // Получаем и выводим список вкладчиков этого репозитория.
    List<Contributor> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

Аннотации интерфейсаАннотации Feign определяют Contract между интерфейсом и тем, как должен работать подлежащий клиент.

Стандартный контракт Feign определяет следующие аннотации:| Аннотация | Целевой интерфейс | Использование | |----------------|------------------|---------------| | @RequestLine | Метод | Определяет HttpMethod и UriTemplate для запроса. Выражения, значения, заключенные в фигурные скобки {expression}, разрешаются с помощью соответствующих параметров, аннотированных @Param. | | @Param | Параметр | Определяет переменную шаблона, значение которой будет использовано для разрешения соответствующего шаблона Выражения, по имени, указанному в качестве значения аннотации. Если значение отсутствует, попытается получить имя из имени параметра метода в байт-коде (если код был скомпилирован с флагом -parameters). | | @Headers | Метод, Тип | Определяет HeaderTemplate; вариация UriTemplate, которая использует значения параметров, аннотированных @Param, для разрешения соответствующих Выражений. При использовании на Типе, шаблон будет применен ко всем запросам. При использовании на Методе, шаблон будет применен только к аннотированному методу. | | @QueryMap | Параметр | Определяет Map пар имя-значение или POJO, для расширения в строку запроса. | | @HeaderMap | Параметр | Определяет Map пар имя-значение, для расширения в Http Headers. || @Body | Метод | Определяет Template, подобный UriTemplate и HeaderTemplate, который использует значения параметров, аннотированных @Param, для разрешения соответствующих выражений. |> Переопределение строки запроса

Если необходимо направить запрос на другой хост, отличный от того, который был указан при создании клиента Feign, или вы хотите указать целевой хост для каждого запроса, включите параметр java.net.URI, и Feign будет использовать это значение как целевую точку запроса.

@RequestLine("POST /repos/{owner}/{repo}/issues")
void createIssue(URI host, Issue issue, @Param("owner") String owner, @Param("repo") String repo);
```### Шаблоны и выражения

Выражения Feign представляют простые строковые выражения (уровень 1) в соответствии с спецификацией URI Template - RFC 6570. Выражения расширяются с помощью соответствующих параметров метода, аннотированных Param.

Пример

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");

    /* Параметры owner и repo будут использоваться для расширения выражений owner и repo, определенных в RequestLine.
     *
     * Результатом будет URI https://api.github.com/repos/OpenFeign/feign/contributors
     */
    github.contributors("OpenFeign", "feign");
  }
}

Выражения должны быть заключены в фигурные скобки {}, и могут содержать регулярные выражения, разделенные двоеточием :, чтобы ограничить разрешенные значения. Пример owner должен состоять только из букв. {owner:[a-zA-Z]*}

Расширение параметров запроса

Шаблоны RequestLine и QueryMap следуют спецификации URI Template - RFC 6570 для шаблонов уровня 1, которая определяет следующее:

  • Неразрешенные выражения исключаются.

  • Все литералы и значения переменных закодированы в формате pct, если они не были уже закодированы или помечены как encoded с помощью аннотации @Param.У нас также есть ограниченная поддержка для уровня 3, выражений стиля пути, с следующими ограничениями:

  • Массивы и списки расширяются по умолчанию.

  • Поддерживаются только шаблоны с одной переменной.*

Примеры:

{;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;
  }
}

Если owners в приведенном выше примере определен как Matt, Jeff, Susan, URI расширится до /repos;owners=Matt;owners=Jeff;owners=Susan

Для получения дополнительной информации см. RFC Yöntem 6570, раздел 3.2.7

Неопределенные значения vs. Пустые значения

Неопределенные выражения — это выражения, где значение для выражения явно равно null или значение не предоставляется. Согласно URI Template - RFC 6570, возможно предоставить пустое значение для выражения. Когда Feign разрешает выражение, он сначала определяет, определено ли значение, если значение определено, то параметр запроса остается. Если выражение неопределено, параметр запроса удаляется. См. ниже полное разъяснение.

Пустая строка

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

Результат

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

Исправленный текст:

У нас также есть ограниченная поддержка для уровня 3, выражений стиля пути, с следующими ограничениями:

  • Массивы и списки расширяются по умолчанию.
  • Поддерживаются только шаблоны с одной переменной.*

Примеры:

{;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;
  }
}

Если owners в приведенном выше примере определен как Matt, Jeff, Susan, URI расширится до /repos;owners=Matt;owners=Jeff;owners=Susan

Для получения дополнительной информации см. RFC 6570, раздел 3.2.7

Неопределенные значения vs. Пустые значения

Неопределенные выражения — это выражения, где значение для выражения явно равно null или значение не предоставляется. Согласно URI Template - RFC 6570, возможно предоставить пустое значение для выражения. Когда Feign разрешает выражение, он сначала определяет, определено ли значение, если значение определено, то параметр запроса остается. Если выражение неопределено, параметр запроса удаляется. См. ниже полное разъяснение.

Пустая строка

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

Результат

http://localhost:8080/test?param=
```*Отсутствующее значение*
```java
public void test() {
   Map<String, Object> parameters = new LinkedHashMap<>();
   this.demoClient.test(parameters);
}

Результат

http://localhost:8080/test

Неопределенное значение

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

Результат

http://localhost:8080/test

Для получения дополнительных примеров см. раздел Расширенное использование.

А что насчет слешей? /

Шаблоны @RequestLine по умолчанию не кодируют символы слеша /. Чтобы изменить это поведение, установите свойство decodeSlash в false для @RequestLine.> Что насчет плюса? +

Согласно спецификации URI, символ + допустим как в пути, так и в строке запроса URI. Однако обработка этого символа в строке запроса может быть неоднородной. В некоторых устаревших системах символ + эквивалентен пробелу. Feign следует подходу современных систем, где символ + не должен представлять пробел и явно кодируется как %2B при обнаружении в строке запроса.

Если вы хотите использовать + как пробел, используйте символ пробела или закодируйте значение напрямую как %20##### Пользовательское Расширение

Аннотация @Param имеет опциональное свойство expander, которое позволяет полностью контролировать расширение отдельного параметра. Свойство expander должно ссылаться на класс, реализующий интерфейс Expander:```java public interface Expander { String expand(Object value); }

Результат этого метода следует тем же правилам, что и выше. Если результат равен `null` или пустой строке, значение опускается. Если значение не закодировано в percent-encoded, оно будет закодировано. См. [Пользовательское расширение @Param](#custom-param-expansion) для дополнительных примеров.

#### Расширение заголовков запроса

Шаблоны `Headers` и `HeaderMap` следуют тем же правилам, что и [Расширение параметров запроса](#request-parameter-expansion), с некоторыми изменениями:

* Неразрешённые выражения опускаются. Если результат равен пустому значению заголовка, весь заголовок удаляется.
* Не выполняется кодирование в percent-encoded.

См. [Заголовки](#headers) для примеров.

> **Примечание к параметрам @Param и их именам**:
>
> Все выражения с одинаковым именем, независимо от их положения в `@RequestLine`, `@QueryMap`, `@BodyTemplate`, или `@Headers`, будут разрешаться к одному и тому же значению.
> В следующем примере значение `contentType` будет использоваться для разрешения как заголовка, так и выражения пути:
>
> ```java
> public interface ContentService {
>   @RequestLine("GET /api/documents/{contentType}")
>   @Headers("Accept: {contentType}")
>   String getDocumentByType(@Param("contentType") String type);
> }
>```
>
> Имейте это в виду при проектировании ваших интерфейсов.

#### Расширение тела запроса

Шаблоны `Body` следуют тем же правилам, что и [Расширение параметров запроса](#request-parameter-expansion), с некоторыми изменениями:* Неразрешённые выражения опускаются.
* Расширенное значение **не** будет передано через `Encoder` перед тем, как быть добавлено в тело запроса.
* Должен быть указан заголовок `Content-Type`. См. [Шаблоны тела](#body-templates) для примеров.

---
### Пользовательские Настройки

Feign имеет несколько аспектов, которые можно настроить.  
Для простых случаев вы можете использовать `Feign.builder()` для создания интерфейса API с вашими пользовательскими компонентами.<br>
Для настройки запроса вы можете использовать `options(Request.Options options)` на `target()` для установки `connectTimeout`, `connectTimeoutUnit`, `readTimeout`, `readTimeoutUnit`, `followRedirects`.<br>
Для примера:
```java
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");
  }
}

Множественные Интерфейсы

Feign может создавать несколько интерфейсов API. Эти интерфейсы определяются как Target<T> (по умолчанию HardCodedTarget<T>), что позволяет для динамического обнаружения и декорирования запросов перед их выполнением.

Например, следующий шаблон может декорировать каждый запрос текущим URL и токеном аутентификации из службы идентификации.

public class CloudService {
  public static void main(String[] args) {
    CloudDNS cloudDNS = Feign.builder()
      .target(new CloudIdentityTarget<CloudDNS>(user, apiKey));
  }
```  class CloudIdentityTarget extends Target<CloudDNS> {
    /* реализация класса Target */
  }
}

Примеры

Feign включает примеры клиентов для GitHub и Wikipedia. Проект denominator также можно использовать для изучения Feign в действии. В частности, обратите внимание на его пример демона.


Интеграции

Feign предназначен для работы с другими открытыми проектами. Модули приветствуются для интеграции с вашими любимыми проектами!

Кодировщик/Декодер

Gson

Gson включает кодировщик и декодер, которые можно использовать с JSON API.

Добавьте GsonEncoder и/или GsonDecoder к вашему Feign.Builder следующим образом:

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 включает кодировщик и декодер, которые можно использовать с JSON API.

Добавьте JacksonEncoder и/или JacksonDecoder к вашему Feign.Builder следующим образом:

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");
  }
}

Для более легкого использования Jackson Jr используйте JacksonJrEncoder и JacksonJrDecoder из модуля Jackson Jr.

Moshi

Moshi включает кодировщик и декодер, которые можно использовать с JSON API. Добавьте MoshiEncoder и/или MoshiDecoder к вашему Feign.Builder следующим образом:```java GitHub github = Feign.builder() .encoder(new MoshiEncoder()) .decoder(new MoshiDecoder()) .target(GitHub.class, "https://api.github.com");


#### Sax
[SaxDecoder](./sax) позволяет декодировать XML таким образом, чтобы это было совместимо с обычной JVM и также с Android-окружением.

Вот пример конфигурации парсинга ответа Sax:
```java
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 включает в себя кодировщик и декодировщик, которые можно использовать с XML API.

Добавьте JAXBEncoder и/или JAXBDecoder к вашему Feign.Builder следующим образом:

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 включает в себя кодировщик и декодировщик, которые можно использовать с XML API.

Этот модуль добавляет поддержку для кодирования и декодирования объектов SOAP Body через JAXB и SOAPMessage. Он также предоставляет возможности декодирования SOAPFault, упаковывая их в исключение javax.xml.ws.soap.SOAPFaultException, так что вам нужно будет ловить только SOAPFaultException, чтобы обрабатывать SOAPFault.

Добавьте SOAPEncoder и/или SOAPDecoder к вашему Feign.Builder следующим образом:

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");
  }
}
```Примечание: возможно, вам также потребуется добавить `SOAPErrorDecoder`, если SOAP Faults возвращаются в ответе с ошибочными HTTP-кодами (400, 500, ...).#### Fastjson2 

[fastjson2](./fastjson2) включает в себя кодировщик и декодировщик, которые можно использовать с API JSON.

Добавьте `Fastjson2Encoder` и/или `Fastjson2Decoder` к вашему `Feign.Builder` следующим образом:

```java
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");
  }
}

Контракт

JAX-RS

JAXRSContract переопределяет обработку аннотаций, чтобы использовать стандартные, предоставляемые спецификацией JAX-RS. Это в настоящее время ориентировано на спецификацию 1.1.

Вот пример выше, переписанный для использования 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");
  }
}

Клиент

OkHttp

OkHttpClient направляет HTTP-запросы Feign к OkHttp, что позволяет использовать SPDY и улучшенное управление сетью.

Чтобы использовать OkHttp с Feign, добавьте модуль OkHttp к вашему classpath. Затем настройте Feign для использования 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](./ribbon) переопределяет разрешение URL клиента Feign, добавляя возможности умного маршрутизирования и устойчивости, предоставляемые [Ribbon](https://github.com/Netflix/ribbon).

Интеграция требует передачи имени клиента Ribbon как части хоста URL, например `myAppProd`.
```java
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 направляет HTTP-запросы Feign к новому HTTP/2 клиенту Java 11 New HTTP/2 Client, который реализует HTTP/2. Чтобы использовать новый HTTP/2 Client с Feign, используйте Java SDK 11. Затем настройте Feign для использования Http2Client:

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

Breaker

Hystrix

HystrixFeign настраивает поддержку размыкания цепи, предоставляемую Hystrix.

Чтобы использовать Hystrix с Feign, добавьте модуль Hystrix в ваш classpath. Затем используйте HystrixFeign builder:

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

Logger

SLF4J

SLF4JModule позволяет направлять логирование Feign в SLF4J, позволяя вам легко использовать backend логирования вашего выбора (Logback, Log4J и т.д.).

Чтобы использовать SLF4J с Feign, добавьте как модуль SLF4J, так и привязку SLF4J вашего выбора в ваш classpath. Затем настройте Feign для использования Slf4jLogger:```java 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"); } }


### Декодеры
`Feign.builder()` позволяет вам указывать дополнительные настройки, такие как способ декодирования ответа.

Если какие-либо методы в вашем интерфейсе возвращают типы, отличные от `Response`, `String`, `byte[]` или `void`, вам потребуется настроить декодер, отличный от стандартного.

Вот как настроить декодирование JSON (используя расширение `feign-gson`):

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

Если вам нужно предварительно обработать ответ перед передачей его декодеру, вы можете использовать метод mapAndDecode builder. Пример использования: работа с API, который предоставляет только jsonp, вам может потребоваться извлечь jsonp перед передачей его в декодер JSON вашего выбора:

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

Если какие-либо методы в вашем интерфейсе возвращают тип Stream, вам потребуется настроить StreamDecoder.

Вот как настроить декодер Stream без делегирования декодера:```java public class Пример { 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"); } }


```java

public class Пример {
  public static void main(String[] args) {
    GitHub github = Feign.builder()
            .decoder(StreamDecoder.create((r, t) -> {
              BufferedReader bufferedReader = new BufferedReader(r.body().asReader(StandardCharsets.UTF_8));
              return bufferedReader.lines().iterator();
            }, (r, t) -> "это делегированный декодер"))
            .target(GitHub.class, "https://api.github.com");
  }
}

Энкодеры

Самый простой способ отправить тело запроса на сервер — это определить метод POST, имеющий параметр типа String или byte[] без каких-либо аннотаций. Вероятно, вам потребуется добавить заголовок Content-Type.

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

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

С настройкой энкодера вы можете отправлять типизированное тело запроса. Вот пример использования расширения feign-gson:

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 Пример {
  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 указывает на шаблон для раскрытия с использованием параметров, аннотированных @Param. Вероятно, вам потребуется добавить заголовок Content-Type.```java 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 должны быть экранированы! @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"} } }


### Заголовки
Feign поддерживает установку заголовков в запросах как часть API или клиента в зависимости от сценария использования.

#### Установка заголовков с помощью API
В случаях, когда определенные интерфейсы или вызовы всегда должны иметь определенные значения заголовков, имеет смысл определять заголовки как часть API.

Статические заголовки могут быть установлены на интерфейсе API или методе с помощью аннотации `@Headers`.

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

Методы могут указывать динамическое содержимое для статических заголовков с использованием расширения переменных в @Headers.

public interface Api {
   @RequestLine("POST /")
   @Headers("X-Ping: {token}")
   void post(@Param("token") String token);
}
```В случаях, когда ключи и значения заголовков динамические, а диапазон возможных ключей неизвестен заранее и может варьироваться между различными вызовами методов в одном и том же API/клиенте (например, пользовательские заголовки метаданных, такие как "x-amz-meta-\*" или "x-goog-meta-\*"), параметр типа `Map` может быть аннотирован `HeaderMap`, чтобы создать запрос, использующий содержимое карты как параметры заголовков.```java
public interface Api {
   @RequestLine("POST /")
   void post(@HeaderMap Map<String, Object> headerMap);
}

Эти подходы определяют заголовочные записи как часть API и не требуют никаких кастомизаций при построении клиента Feign.

Установка заголовков для каждого целевого объекта

Чтобы кастомизировать заголовки для каждого метода запроса на целевом объекте, можно использовать RequestInterceptor. RequestInterceptors могут быть использованы в нескольких экземплярах целевых объектов и должны быть потокобезопасными. RequestInterceptors применяются ко всем методам запроса на целевом объекте.

Если вам требуется кастомизация для каждого метода, необходимо использовать кастомный целевой объект, так как RequestInterceptor не имеет доступа к метаданным текущего метода.

Для примера установки заголовков с помощью RequestInterceptor см. раздел Request Interceptors.

Заголовки могут быть установлены как часть кастомного 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));
    }
  }
```Эти подходы зависят от использования кастомного `RequestInterceptor` или `Target`, который устанавливается на клиенте Feign при его построении и может использоваться как способ установки заголовков для всех вызовов API на основе каждого клиента. Это может быть полезно для таких вещей, как установка токена аутентификации в заголовке всех вызовов API на основе каждого клиента. Методы выполняются при вызове API на потоке, который вызывает API, что позволяет динамически устанавливать заголовки в момент вызова и в контекстно-зависимом режиме  например, можно использовать потокобезопасное хранилище для установки различных значений заголовков в зависимости от вызывающего потока, что может быть полезно для таких вещей, как установка уникальных идентификаторов трассировки для каждого потока.

#### Установка нулевого заголовка Content-Length

Для указания заголовка `Content-Length: 0` при отправке запроса с пустым телом, системная переменная `sun.net.http.allowRestrictedHeaders` должна быть установлена в значение `true`.

Если этого не сделать, заголовок `Content-Length` не будет добавлен.

### Расширенное использование

#### Основные API
В большинстве случаев API для сервиса следуют одному и тому же соглашению. Feign поддерживает это через интерфейсы с одиночным наследованием.

Рассмотрим пример:
```java
interface BaseAPI {
  @RequestLine("GET /health")
  String health();
```  @RequestLine("GET /all")
  List<Entity> all();
}

Вы можете определить и указать конкретный API, наследуя базовые методы.

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

В большинстве случаев представления ресурсов также являются последовательными. По этой причине поддерживаются параметры типа на базовом интерфейсе API.

@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> { }

Логирование

Вы можете логировать HTTP-сообщения, отправляемые и получаемые от целевого узла, настроив Logger. Вот самый простой способ сделать это:

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");
  }
}

Примечание о JavaLogger: Избегайте использования конструктора по умолчанию JavaLogger(), он помечен как устаревший и будет удален в ближайшее время.

SLF4JLogger (см. выше) также может быть полезен.

Чтобы фильтровать чувствительную информацию, такую как авторизация или токены, переопределите методы shouldLogRequestHeader или shouldLogResponseHeader.#### Запросные интерцепторы Когда вам нужно изменить все запросы, независимо от их целевого узла, вам следует настроить RequestInterceptor. Например, если вы выступаете в роли промежуточного узла, вы можете хотеть передать заголовок X-Forwarded-For.```java static class ForwardedForInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { template.header("X-Forwarded-For", "origin.host.com"); } }``````java 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"); } }


Еще один распространенный пример интерцептора — это аутентификация, например, использование встроенного `BasicAuthRequestInterceptor`.

```java
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");
  }
}

Пользовательское расширение @Param

Параметры, аннотированные с Param, расширяются на основе их toString. Указав пользовательский Param.Expander, пользователи могут контролировать это поведение, например, форматировать даты.

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

Динамические параметры запроса

Параметр типа Map может быть аннотирован с QueryMap, чтобы создать запрос, использующий содержимое карты как параметры запроса.

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

Это также может использоваться для генерации параметров запроса из объекта POJO с помощью QueryMapEncoder.

public interface Api {
  @RequestLine("GET /find")
  V find(@QueryMap CustomPojo customPojo);
}
```Когда используется таким образом, без указания пользовательского `QueryMapEncoder`, карта запросов будет генерироваться с использованием имен членов переменных как имен параметров запроса. Вы можете аннотировать конкретное поле `CustomPojo` аннотацией `@Param`, чтобы указать другое имя для параметра запроса. Следующий объект POJO сгенерирует параметры запроса вида "/find?name={name}&number={number}&region_id={regionId}" (порядок включенных параметров запроса не гарантируется, и как обычно, если любое значение равно null, оно будет опущено).

```java
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;
  }
}

Чтобы настроить QueryMapEncoder:

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

Когда объекты аннотируются с помощью @QueryMap, стандартный кодировщик использует рефлексию для анализа предоставленных объектов и их полей для расширения значений объектов в строку запроса. Если вы предпочитаете, чтобы строка запроса создавалась с использованием методов доступа и изменения, как определено в Java Beans API, пожалуйста, используйте BeanQueryMapEncoder.

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

Обработка ошибок

Если вам требуется более тонкое управление обработкой неожиданных ответов, экземпляры Feign могут зарегистрировать пользовательский ErrorDecoder через конструктор.

public class Example {
  public static void main(String[] args) {
    MyApi myApi = Feign.builder()
                 .errorDecoder(new MyErrorDecoder())
                 .target(MyApi.class, "https://api.hostname.com");
  }
}
```Все ответы, которые приводят к HTTP-статусу, не входящему в диапазон 2xx, будут запускать метод `decode` класса `ErrorDecoder`, что позволит вам обрабатывать ответ, обёртывать ошибку в пользовательское исключение или выполнять любую дополнительную обработку. Если вы хотите повторить запрос, выбросьте исключение `RetryableException`. Это вызовет зарегистрированный `Retryer`.### Повторная отправка
По умолчанию Feign автоматически повторяет `IOException`-и, независимо от HTTP-метода, рассматривая их как временные сетевые исключения, а также любые `RetryableException`, выброшенные из `ErrorDecoder`. Чтобы настроить это поведение, зарегистрируйте пользовательский экземпляр `Retryer` через конструктор.

В следующем примере показано, как обновлять токен и повторять запрос с использованием `ErrorDecoder` и `Retryer` при получении ответа с кодом 401.

```java
public class Пример {
    public static void main(String[] args) {
        var github = Feign.builder()
                .decoder(new GsonDecoder())
                .retryer(new МойПовторитель(100, 3))
                .errorDecoder(new МойОбработчикОшибок())
                .target(ГитХаб.class, "https://api.github.com");

        var вкладчики = github.вкладчики("foo", "bar", "invalid_token");
        for (var вкладчик : вкладчики) {
            System.out.println(вкладчик.login + " " + вкладчик.вклад);
        }
    }

    static class МойОбработчикОшибок implements ErrorDecoder {

        private final ErrorDecoder стандартныйОбработчикОшибок = new Default();

        @Override
        public Exception decode(String methodKey, Response response) {
            // обертка 401 в RetryableException для повторной попытки
            if (response.status() == 401) {
                return new RetryableException(response.status(), response.reason(), response.request().httpMethod(), null, response.request());
            }
            return стандартныйОбработчикОшибок.decode(methodKey, response);
        }
    }

    static class МойПовторитель implements Retryer {

        private final long период;
        private final int максимальноеКоличествоПопыток;
        private int попытка = 1;

        public МойПовторитель(long период, int максимальноеКоличествоПопыток) {
            this.период = период;
            this.максимальноеКоличествоПопыток = максимальноеКоличествоПопыток;
        }

        @Override
        public Retryer clone() {
            return new МойПовторитель(период, максимальноеКоличествоПопыток);
        }

        @Override
        public void attempt(Retryer.Context context) {
            if (попытка >= максимальноеКоличествоПопыток) {
                throw new RetryException("Превышено максимальное количество попыток");
            }
            попытка++;
            try {
                Thread.sleep(период);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RetryException("Повторная попытка прервана", e);
            }
        }
    }
}
``````markdown
```java
    public class МойПовторитель {
        public МойПовторитель(long период, int максимальное_количество_попыток) {
            this.период = период;
            this.максимальное_количество_попыток = максимальное_количество_попыток;
        }

        @Override
        public void продолжитьИлиПередать(RetryableException e) {
            if (++попытка > максимальное_количество_попыток) {
                throw e;
            }
            if (e.status() == 401) {
                // удалить Authorization, чтобы Feign не добавлял новый заголовок Authorization
                // поскольку GitHub отвечает 400 bad request
                e.request().requestTemplate().removeHeader("Authorization");
                e.request().requestTemplate().header("Authorization", "Bearer " + получитьНовыйТокен());
                try {
                    Thread.sleep(период);
                } catch (InterruptedException ex) {
                    throw e;
                }
            } else {
                throw e;
            }
        }

        // Получение нового токена из внешнего API
        // В этом примере мы можем просто вернуть фиксированный токен, чтобы продемонстрировать работу Retryer
        private String получитьНовыйТокен() {
            return "newToken";
        }
    }
}
``````java
    @Override
    public Retryer клонировать() {
        return new МойПовторитель(период, максимальное_количество_попыток);
    }
}

Retryerы отвечают за определение, следует ли повторить запрос, возвращая либо true, либо false из метода continueOrPropagate(RetryableException e). Экземпляр Retryer создается для каждого выполнения Client, что позволяет поддерживать состояние между каждым запросом, если это необходимо. ```Если повторная попытка определяется как неудачная, последнее исключение RetryException будет выброшено. Чтобы выбросить первоначальную причину, приведшую к неудачной повторной попытке, настройте свой Feign клиент с помощью опции `exceptionPropagationPolicy()`.

Интерцептор Ответа

Если вам нужно рассматривать то, что обычно было бы ошибкой, как успешное выполнение и возвращать результат вместо выбрасывания исключения, вы можете использовать ResponseInterceptor.

В качестве примера Feign включает простой RedirectionInterceptor, который можно использовать для извлечения заголовка Location из ответов перенаправления.

public interface Api {
  // возвращает ответ  Yöntem 302
  @RequestLine("GET /location")
  String location();
}

public class MyApp {
  public static void main(String[] args) {
    // Настройте HTTP-клиент для игнорирования перенаправлений
    Api api = Feign.builder()
                   .options(new Options(10, TimeUnit.SECONDS, 60, TimeUnit.SECONDS, false))
                   .responseInterceptor(new RedirectionInterceptor())
                   .target(Api.class, "https://redirect.example.com");
  }
}

Метрики

По умолчанию, Feign не собирает никаких метрик.

Однако, возможно добавить возможности сбора метрик для любого клиента Feign.

Возможности метрик предоставляют первоклассный API метрик, который пользователи могут использовать для получения информации о жизненном цикле запросов/ответов.

Примечание о модулях метрик:

Все модули интеграции метрик построены в отдельных модулях и не доступны в модуле feign-core. Вам потребуется добавить их в зависимости.#### 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");
    // метрики будут доступны с этого момента
  }
}

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");
    // метрики будут доступны с этого момента
  }
}

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");
    // метрики будут доступны с этого момента
  }
}

Статические и методы по умолчанию

Интерфейсы, на которые ориентирован Feign, могут содержать статические методы или методы по умолчанию (если используется Java 8+). Это позволяет клиентам Feign содержать логику, которая не явно определена в подлежащем API. Например, статические методы делают легким указание общих конфигураций сборки клиентов; методы по умолчанию могут использоваться для составления запросов или определения параметров по умолчанию.

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);
}
``````markdown
### Асинхронное выполнение через `CompletableFuture`

Feign 10.8 вводит новый построитель `AsyncFeign`, который позволяет методам возвращать экземпляры `CompletableFuture`.

```java
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");

    // Получение и вывод списка вкладчиков этого библиотеки.
    CompletableFuture<List<Contributor>> contributors = github.contributors("OpenFeign", "feign");
    for (Contributor contributor : contributors.get(1, TimeUnit.SECONDS)) {
      System.out.println(contributor.login + " (" + contributor.contributions + ")");
    }
  }
}

Maven’s Bill of Material (BOM)

Поддержание всех библиотек Feign на одной версии необходимо для избежания несовместимых бинарников. При использовании внешних зависимостей может быть сложно убедиться, что только одна версия присутствует.

С этой целью, сборка Feign генерирует модуль под названием feign-bom, который блокирует версии для всех модулей feign-*.



[Здесь](https://repo1.maven.org/maven2/io/github/openfeign/feign-bom/11.9/feign-bom-11.9.pom) можно посмотреть пример того, как выглядит файл BOM Feign.

#### Использование

```xml
<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>

Форм-энкодер

Статус сборки Maven Central Лицензия

Этот модуль добавляет поддержку кодирования форм application/x-www-form-urlencoded и multipart/form-data.

Добавление зависимости

Добавьте зависимость в ваш проект:

Maven:

<dependencies>
  ...
  <dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form</artifactId>
    <version>4.0.0</version>
  </dependency>
  ...
</dependencies>

Gradle:

compile 'io.github.openfeign.form:feign-form:4.0.0'

Требования

Расширение feign-form зависит от OpenFeign и его конкретных версий:- все выпуски feign-form до 3.5.0 работают с OpenFeign 9.* версиями;

  • начиная с версии feign-form 3.5.0, модуль работает с OpenFeign 10.1.0 версиями и выше.

ВАЖНО: обратная совместимость отсутствует, и нет никаких гарантий, что версии feign-form после 3.5.0 будут работать с OpenFeign до 10.*. OpenFeign был переработан в 10-й версии, поэтому лучший подход — использовать самую свежую версию OpenFeign и feign-form.

Примечания:

  • spring-cloud-openfeign использует OpenFeign 9.* до v2.0.3.RELEASE и 10.* после. В любом случае, зависимость уже имеет подходящую версию feign-form, см. dependency pom, поэтому вам не нужно указывать её отдельно;

  • spring-cloud-starter-feign — это устаревшая зависимость, и она всегда использует версии OpenFeign 9.*.

Использование

Добавьте FormEncoder в ваш Feign.Builder следующим образом:

SomeApi github = Feign.builder()
                       .encoder(new FormEncoder())
                       .target(SomeApi.class, "http://api.some.org");

Кроме того, вы можете декорировать существующий encoder, например, JsonEncoder, следующим образом:

SomeApi github = Feign.builder()
                       .encoder(new FormEncoder(new JacksonEncoder()))
                       .target(SomeApi.class, "http://api.some.org");

И используйте их вместе:

interface SomeApi {

  @RequestLine("POST /json")
  @Headers("Content-Type: application/json")
  void json(Dto dto);
}
```  @RequestLine("POST /form")
  @Headers("Content-Type: application/x-www-form-urlencoded")
  void from (@Param("field1") String field1, @Param("field2") String[] values);
}
```Вы можете указать два типа формата кодирования с помощью заголовка `Content-Type`.### application/x-www-form-urlencoded

```java
interface SomeApi {

  @RequestLine("POST /authorization")
  @Headers("Content-Type: application/x-www-form-urlencoded")
  void authorization (@Param("email") String email, @Param("password") String password);

  // Группировка всех параметров в POJO
  @RequestLine("POST /user")
  @Headers("Content-Type: application/x-www-form-urlencoded")
  void addUser (User user);

  class User {

    Integer id;

    String name;
  }
}

multipart/form-data

interface SomeApi {

  // Параметр файла
  @RequestLine("POST /send_photo")
  @Headers("Content-Type: multipart/form-data")
  void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") File photo);

  // Параметр byte[]
  @RequestLine("POST /send_photo")
  @Headers("Content-Type: multipart/form-data")
  void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") byte[] photo);

  // Параметр FormData
  @RequestLine("POST /send_photo")
  @Headers("Content-Type: multipart/form-data")
  void sendPhoto (@Param("is_public") Boolean isPublic, @Param("photo") FormData photo);

  // Группировка всех параметров в POJO
  @RequestLine("POST /send_photo")
  @Headers("Content-Type: multipart/form-data")
  void sendPhoto (MyPojo pojo);

  class MyPojo {

    @FormProperty("is_public")
    Boolean isPublic;

    File photo;
  }
}

В приведенном выше примере метод sendPhoto использует параметр photo с использованием трех различных поддерживаемых типов.

  • File будет использовать расширение файла для определения Content-Type;
  • byte[] будет использовать application/octet-stream как Content-Type;
  • FormData будет использовать Content-Type и fileName объекта FormData;
  • Пользовательский POJO клиента для группировки параметров (включая типы выше).

FormData — это пользовательский объект, который обертывает byte[] и определяет Content-Type и fileName следующим образом:```java FormData formData = new FormData("image/png", "filename.png", myDataAsByteArray); someApi.sendPhoto(true, formData);


### Поддержка Spring MultipartFile и Spring Cloud Netflix @FeignClient

Вы также можете использовать Form Encoder с Spring `MultipartFile` и `@FeignClient`.

Добавьте зависимости в файл `pom.xml` вашего проекта:
```xml
<dependencies>
  <dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form</artifactId>
    <version>4.0.0</version>
  </dependency>
  <dependency>
    <groupId>io.github.openfeign.form</groupId>
    <artifactId>feign-form-spring</artifactId>
    <version>4.0.0</version>
  </dependency>
</dependencies>
@FeignClient(
    name = "file-upload-service",
    configuration = FileUploadServiceClient.MultipartSupportConfig.class
)
public interface FileUploadServiceClient extends IFileUploadServiceClient {

  public class MultipartSupportConfig {

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Bean
    public Encoder feignFormEncoder () {
      return new SpringFormEncoder(new SpringEncoder(messageConverters));
    }
  }
}

Или, если вам не требуется стандартный кодировщик Spring:

@FeignClient(
    name = "file-upload-service",
    configuration = FileUploadServiceClient.MultipartSupportConfig.class
)
public interface FileUploadServiceClient extends IFileUploadServiceClient {

  public class MultipartSupportConfig {

    @Bean
    public Encoder feignFormEncoder () {
      return new SpringFormEncoder();
    }
  }
}

Благодарим tf-haotri-pham за его функцию, которая использует библиотеку Apache commons-fileupload для обработки разбора multipart-ответа. Части данных тела сохраняются в виде массивов байтов в памяти.

Чтобы использовать эту функцию, включите SpringManyMultipartFilesReader в список конвертеров сообщений для декодера и сделайте так, чтобы клиент Feign возвращал массив MultipartFile:

@FeignClient(
    name = "${feign.name}",
    url = "${feign.url}",
    configuration = DownloadClient.ClientConfiguration.class
)
public interface DownloadClient {

  @RequestMapping("/multipart/download/{fileId}")
  MultipartFile[] download(@PathVariable("fileId") String fileId);

  class ClientConfiguration {

    @Autowired
    private ObjectFactory<HttpMessageConverters> messageConverters;

    @Bean
    public Decoder feignDecoder () {
      List<HttpMessageConverter<?>> springConverters =
            messageConverters.getObject().getConverters();

      List<HttpMessageConverter<?>> decoderConverters =
            new ArrayList<HttpMessageConverter<?>>(springConverters.size() + 1);
      
      decoderConverters.addAll(springConverters);
      decoderConverters.add(new SpringManyMultipartFilesReader(4096));

      HttpMessageConverters httpMessageConverters = new HttpMessageConverters(decoderConverters);

      return new SpringDecoder(new ObjectFactory<HttpMessageConverters>() {

        @Override
        public HttpMessageConverters getObject() {
          return httpMessageConverters;
        }
      });
    }
  }
}
```

Комментарии ( 0 )

Вы можете оставить комментарий после Вход в систему

Введение

Описание недоступно Развернуть Свернуть
Apache-2.0
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/mirrors-feign.git
git@api.gitlife.ru:oschina-mirror/mirrors-feign.git
oschina-mirror
mirrors-feign
mirrors-feign
master