«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. Фильтр

Далее мы напишем filter, который будет выводить некоторые данные о запросе в консоль. Как и в случае со службой, нам нужно будет реализовать метод применения фильтра, который будет принимать запрос и возвращать ответ 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.

«