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 .