Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

REST-клиент: запросы к другим сервисам

Сервисы редко живут в одиночку: блогу может понадобиться профиль автора из сервиса пользователей, курс валют из банка, адрес из сервиса карт. Писать HTTP-запросы вручную долго. REST-клиент Quarkus работает наоборот по сравнению с ресурсом: вы описываете чужой API Java-интерфейсом с теми же аннотациями @Path и @GET, а Quarkus сам создаёт реализацию, которая отправляет запросы и превращает JSON в объекты.

Подключение

./mvnw quarkus:add-extension -Dextensions="rest-client-jackson"

В качестве «сервиса пользователей» возьмём учебный публичный API jsonplaceholder.typicode.com. Его адрес /users/1 отдаёт JSON с полями id, name, email и многими другими.

Интерфейс клиента

// src/main/java/com/example/blog/User.java
package com.example.blog;
 
public record User(long id, String name, String email) {
}
// src/main/java/com/example/blog/UserClient.java
package com.example.blog;
 
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
 
@Path("/users")
@RegisterRestClient(configKey = "users-api")
public interface UserClient {
 
    @GET
    @Path("/{id}")
    User getById(@PathParam("id") long id);
}

@RegisterRestClient говорит Quarkus создать реализацию интерфейса. configKey — имя, по которому клиент ищет свои настройки. Лишние поля ответа Jackson пропускает, в запись попадут только id, name и email.

Адрес сервиса в настройках

# src/main/resources/application.properties
quarkus.rest-client.users-api.url=https://jsonplaceholder.typicode.com
quarkus.rest-client.users-api.connect-timeout=2000
quarkus.rest-client.users-api.read-timeout=5000

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

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

Клиент внедряется с дополнительным квалификатором @RestClient:

// src/main/java/com/example/blog/AuthorResource.java
package com.example.blog;
 
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import org.eclipse.microprofile.rest.client.inject.RestClient;
 
@Path("/authors")
public class AuthorResource {
 
    @Inject
    @RestClient
    UserClient userClient;
 
    @GET
    @Path("/{id}")
    public User get(@PathParam("id") long id) {
        return userClient.getById(id);
    }
}
curl http://localhost:8080/authors/1
{"id":1,"name":"Leanne Graham","email":"Sincere@april.biz"}

Наш сервис сходил в чужой API и вернул сокращённый профиль автора.

Ошибки удалённого сервиса

Если удалённый сервис ответил 404, клиент бросает ClientWebApplicationException. Чтобы превратить такой ответ в своё исключение, в интерфейс добавляют статический метод с @ClientExceptionMapper:

// src/main/java/com/example/blog/UserClient.java
@ClientExceptionMapper
static RuntimeException toException(Response response) {
    if (response.getStatus() == 404) {
        return new AuthorNotFoundException();
    }
    return null;
}

Импорты — io.quarkus.rest.client.reactive.ClientExceptionMapper и jakarta.ws.rs.core.Response. null означает «обработать как обычно». Само исключение и обработчик для сервера пишутся так же, как PostNotFoundException:

// src/main/java/com/example/blog/AuthorNotFoundException.java
package com.example.blog;
 
public class AuthorNotFoundException extends RuntimeException {
 
    public AuthorNotFoundException() {
        super("Автор не найден");
    }
}
// src/main/java/com/example/blog/ExceptionMappers.java
@ServerExceptionMapper
public RestResponse<ErrorMessage> mapAuthorNotFound(AuthorNotFoundException e) {
    return RestResponse.status(Response.Status.NOT_FOUND, new ErrorMessage(e.getMessage()));
}

Теперь curl -i http://localhost:8080/authors/999 возвращает 404 и {"error":"Автор не найден"}.

Частая ошибка

Без @ClientExceptionMapper ошибка чужого сервиса превращается у нас в 500:

HTTP/1.1 500 Internal Server Error

А в логе:

ERROR [io.quarkus.vertx.http.runtime.QuarkusErrorHandler] HTTP Request to /authors/999 failed, error id: ...:
org.jboss.resteasy.reactive.ClientWebApplicationException: Received: 'Not Found, status code 404'
when invoking REST Client method: 'com.example.blog.UserClient#getById'

Quarkus намеренно не передаёт клиенту статус удалённого сервиса как есть: чужой 404 не означает, что не найден адрес нашего API. Решайте сами, что ответить, через @ClientExceptionMapper или try/catch вокруг вызова.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.