Перейти к основному содержимому

Введение в Финагл

· 4 мин. чтения

1. Обзор

В этом уроке мы кратко рассмотрим Finagle, RPC-библиотеку Twitter.

Мы будем использовать его для создания простого клиента и сервера.

2. Строительные блоки

Прежде чем мы углубимся в реализацию, нам нужно познакомиться с основными концепциями, которые мы будем использовать для создания нашего приложения. Они широко известны, но могут иметь немного другое значение в мире Finagle.

2.1. Услуги

Службы — это функции, представленные классами, которые принимают запросы и возвращают Future , содержащую конечный результат операции или информацию о сбое.

2.2. Фильтры

Фильтры также являются функциями. Они берут запрос и сервис, выполняют какие-то операции с запросом, передают его сервису, выполняют какие-то операции с результирующим Future и, наконец, возвращают окончательный Future . Мы можем думать о них как об аспектах , поскольку они могут реализовывать логику, связанную с выполнением функции, и изменять ее ввод и вывод.

2.3. фьючерсы

Фьючерсы представляют собой конечные результаты асинхронных операций. Они могут находиться в одном из трех состояний: ожидание, успешное выполнение или сбой.

3. Сервис

Во-первых, мы реализуем простую службу приветствия HTTP. Он возьмет параметр имени из запроса и ответит и добавит обычное сообщение «Привет».

Для этого нам нужно создать класс, который будет расширять абстрактный класс Service из библиотеки Finagle, реализуя его метод apply .

То, что мы делаем, похоже на реализацию функционального интерфейса . Интересно, однако, что на самом деле мы не можем использовать эту конкретную функцию, потому что Finagle написан на Scala, и мы используем преимущества совместимости Java-Scala:

public class GreetingService extends Service<Request, Response> {
@Override
public Future<Response> apply(Request request) {
String greeting = "Hello " + request.getParam("name");
Reader<Buf> reader = Reader.fromBuf(new Buf.ByteArray(greeting.getBytes(), 0, greeting.length()));
return Future.value(Response.apply(request.version(), Status.Ok(), reader));
}
}

4. Фильтр

Далее напишем фильтр, который будет выводить в консоль некоторые данные о запросе. Как и в случае с Service , нам нужно будет реализовать метод apply фильтра , который будет принимать запрос и возвращать ответ Future , но на этот раз он также будет принимать сервис в качестве второго параметра.

Базовый класс Filter имеет четыре параметра типа, но очень часто нам не нужно менять типы запросов и ответов внутри фильтра.

Для этого мы будем использовать SimpleFilter , который объединяет четыре параметра типа в два. Мы напечатаем некоторую информацию из запроса, а затем просто вызовем метод применения из предоставленного сервиса:

public class LogFilter extends SimpleFilter<Request, Response> {
@Override
public Future apply(Request request, Service<Request, Response> service) {
logger.info("Request host:" + request.host().getOrElse(() -> ""));
logger.info("Request params:");
request.getParams().forEach(entry -> logger.info("\t" + entry.getKey() + " : " + entry.getValue()));
return service.apply(request);
}
}

5. Сервер

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

Мы предоставим этому серверу службу, которая содержит как наш фильтр, так и службу, связанные вместе с методом andThen :

Service serverService = new LogFilter().andThen(new GreetingService()); 
Http.serve(":8080", serverService);

6. Клиент

Наконец, нам нужен клиент для отправки запроса на наш сервер.

Для этого мы создадим службу HTTP, используя удобный метод newService из класса Finagle Http . Он будет нести прямую ответственность за отправку запроса.

Кроме того, мы будем использовать тот же фильтр ведения журнала, который мы реализовали ранее, и свяжем его со службой HTTP. Затем нам просто нужно вызвать метод применения .

Эта последняя операция является асинхронной, и ее конечные результаты сохраняются в экземпляре Future . Мы могли бы подождать, пока это Будущее увенчается успехом или неудачей, но это будет блокирующая операция, и мы можем захотеть ее избежать. Вместо этого мы можем реализовать обратный вызов, который будет запускаться при успешном выполнении Future :

Service<Request, Response> clientService = new LogFilter().andThen(Http.newService(":8080"));
Request request = Request.apply(Method.Get(), "/?name=John");
request.host("localhost");
Future<Response> response = clientService.apply(request);

Await.result(response
.onSuccess(r -> {
assertEquals("Hello John", r.getContentString());
return BoxedUnit.UNIT;
})
.onFailure(r -> {
throw new RuntimeException(r);
})
);

Обратите внимание, что мы возвращаем BoxedUnit.UNIT. Возврат единицы — это способ Scala справиться с методами void , поэтому мы делаем это здесь, чтобы поддерживать совместимость.

7. Резюме

В этом руководстве мы узнали, как создать простой HTTP-сервер и клиент с помощью Finagle, а также как установить связь между ними и обмениваться сообщениями.

Как всегда, исходный код со всеми примерами можно найти на GitHub .