Избранное сообщение

Фетісов В. С. Комп’ютерні технології в тестуванні. Навчально-методичний посібник. 2-ге видання, перероблене та доповнене / Мои публикации

В 10-х годах я принимал участие в программе Европейского Союза Tempus "Освітні вимірювання, адаптовані до стандартів ЄС". В рамк...

Благодаря Интернету количество писателей и поэтов увеличивается в геометрической прогрессии. Поголовье читателей начинает заметно отставать.

понедельник, 14 января 2019 г.

Маппинг запросов на Netty / Программирование на Java

Давным-давно в одной далёкой-далёкой... проекте, понадобилось мне сделать обработку http-запросов на Netty. К сожалению, стандартных удобных механизмов для маппинга http-запросов в Netty не нашлось (да и этот фреймвёрк совсем не для того), поэтому, было решено реализовать собственный механизм.

Если читатель начал беспокоиться о судьбе проекта, то не стоит, с ним всё хорошо, т.к. в дальнейшем было решено переписать веб-сервис на фреймвёрке более заточенном под RESTful сервисы, без использования собственных велосипедов. Но наработки остались, и они могут быть кому-нибудь полезными, поэтому хотелось бы ими поделиться.
Netty — это фреймвёрк, позволяющий разрабатывать высокопроизводительные сетевые приложения. Подробнее о нём можно прочитать на сайте проекта.
Для создания сокет-серверов Netty предоставляет весьма удобный функционал, но для создание REST-серверов данный функционал, на мой взгляд, является не очень удобным.

Обработка запросов с использованием стандартного механизма Netty


Для обработки запросов в Netty необходимо отнаследоваться от класса ChannelInboundHandlerAdapter и переопределить метод channelRead.
public class HttpMappingHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
    }
}

Для получения необходимой при обработке http-запросов информации объект msg можно привести к HttpRequest.
HttpRequest request = (HttpRequest) msg;

После этого можно получить какую-либо информацию из этого запроса. Например, URL-адрес запроса.
String uri = request.uri();

Тип запроса.
HttpMethod httpMethod = request.method();

И контент.
ByteBuf byteBuf = ((HttpContent) request).content();

Контентом может быть, например, json, переданный в теле POST-запроса. ByteBuf — это класс из библиотеки Netty, поэтому json-парсеры вряд ли смогут с ним работать, но его очень просто можно привести к строке.
String content = byteBuf.toString(StandardCharsets.UTF_8);

Вот, в общем-то, и всё. Используя указанные выше методы, можно обрабатывать http-запросы. Правда, обрабатывать всё придётся в одном месте, а именно в методе channelRead. Даже если разнести логику обработки запросов по разным методам и классам, всё равно придётся сопоставлять URL с этими методами где-то в одном месте.

Маппинг запросов


Что ж, как видим, вполне можно реализовать маппинг http-запросов, используя стандартный функционал Netty. Правда, будет это не очень удобно. Хотелось бы как-то полностью разнести обработку http-запросов по разным методам (например, как это сделано в Spring). С помощью рефлексии была предпринята попытка реализовать подобный подход. Получилась из этого библиотека num. С её исходным кодом можно ознакомиться по ссылке.
Для использования маппинга запросов с помощью библиотеки num достаточно отнаследоваться от класса AbstractHttpMappingHandler, после чего в этом классе можно будет создавать методы-обработчики запросов. Главное требование к данным методам — это чтобы они возвращали FullHttpResponse или его наследников. Показать, по какому http-запросу будет вызываться данный метод, можно с помощью аннотаций:
  • @Get
  • @Post
  • @Put
  • @Delete

Имя аннотации показывает то, какой тип запроса будет вызван. Поддерживается четыре типа запросов: GETPOSTPUT и DELETE. В качестве параметра value в аннотации необходимо указать URL-адрес, при обращении на который будет вызываться нужный метод.
Пример того, как будет выглядеть обработчик GET-запроса, который возвращает строку Hello, world!.
public class HelloHttpHandler extends AbstractHttpMappingHandler {

      @Get("/test/get")
      public DefaultFullHttpResponse test() {
          return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK,
                  Unpooled.copiedBuffer("Hello, world!", StandardCharsets.UTF_8));
      }
}

Параметры запросов


Передача параметров из запроса в метод-обработчик осуществляется также с помощью аннотаций. Для этого можно воспользоваться одной из следующих аннотаций:
  • @PathParam
  • @QueryParam
  • @RequestBody

@PathParam


Для передачи path-параметров используется аннотация @PathParam. При её использовании в качестве параметра value аннотации необходимо указать название параметра. Кроме того, название параметра необходимо указать и в URL запроса.
Пример того, как будет выглядеть обработчик GET-запроса, в который передаётся path-параметр id и который возвращает этот параметр.
public class HelloHttpHandler extends AbstractHttpMappingHandler {

      @Get("/test/get/{id}")
      public DefaultFullHttpResponse test(@PathParam(value = "id") int id) {
          return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK,
                  Unpooled.copiedBuffer(id, StandardCharsets.UTF_8));
      }
}

@QueryParam


Для передачи query-параметров используется аннотация @QueryParam. При её использовании в качестве параметра value аннотации необходимо указать название параметра. Обязательностью параметра можно управлять с помощью параметра аннотации required.
Пример того, как будет выглядеть обработчик GET-запроса, в который передаётся query-параметр message и который возвращает этот параметр.
public class HelloHttpHandler extends AbstractHttpMappingHandler {

      @Get("/test/get")
      public DefaultFullHttpResponse test(@QueryParam(value = "message") String message) {
          return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK,
                  Unpooled.copiedBuffer(message, StandardCharsets.UTF_8));
      }
}

@RequestBody


Для передачи тела POST-запросов используется аннотация @RequestBody. Поэтому и использовать её разрешается только в POST-запросах. Предполагается, что в качестве тела запроса будут передаваться данные в формате json. Поэтому для использования @RequestBody необходимо в конструктор класса-обработчика передать реализацию интерфейса JsonParser, которая будет заниматься парсингом данных из тела запроса. Также в библиотеке уже имеется реализация по умолчанию JsonParserDefault. В качестве парсера данная реализация использует jackson.
Пример того, как будет выглядеть обработчик POST-запроса, в котором имеется тело запроса.
public class HelloHttpHandler extends AbstractHttpMappingHandler {

      @Post("/test/post")
      public DefaultFullHttpResponse test(@RequestBody Message message) {
          return new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, OK,
                Unpooled.copiedBuffer("{id: '" + message.getId() +"', msg: '" + message.getMessage() + "'}",
                        StandardCharsets.UTF_8));
      }
}

Класс Message выглядит следующим образом.
public class Message {

    private int id;
    private String message;

    public Message() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

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


Если при обработке запросов возникнет какой-либо Exception и он не будет перехвачен в коде методов-обработчиков, то вернётся ответ с 500-ым кодом. Для того чтобы написать какую-то свою логику по обработке ошибок, достаточно переопределить в классе-обработчике метод exceptionCaught.
public class HelloHttpHandler extends AbstractHttpMappingHandler {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR));
    }
}

Заключение


Вот, в общем-то, и всё. Надеюсь, это было интересно и будет кому-нибудь полезным.


Код примера http-сервера на Netty с использованием библиотеки num доступен по ссылке.


Смотри также популярное:

Зачем нужна Java. http://fetisovvs.blogspot.com/2014/07/java.html
Когда Java наконец помрёт, что с этим делать и что будет с JPoint. https://fetisovvs.blogspot.com/2018/11/java-jpoint-java.html
Разбор основных концепций параллелизма. http://fetisovvs.blogspot.com/2018/04/java.html
Первый контакт с «var» в Java 10. http://fetisovvs.blogspot.com/2018/01/var-java-10-java.html
JAVA 9. Что нового? http://fetisovvs.blogspot.com/2017/10/java-9-java.html
Руководство по Java 9 для тех, кому приходится работать с legacy-кодом. http://fetisovvs.blogspot.com/2018/08/java-9-legacy-java.html
Концепции объектно-ориентированного программирования — ООП в Java. http://fetisovvs.blogspot.com/2017/01/java-java.html
Анимации в Android по полочкам (Часть 1. Базовые анимации). http://fetisovvs.blogspot.com/2018/02/android-1-java.html
Двести пятьдесят русскоязычных обучающих видео докладов и лекций о Java. http://fetisovvs.blogspot.com/2015/12/java-5-java-java.html
Абстрактные классы и методы. http://fetisovvs.blogspot.com/2017/02/java.html
Полное руководство по Java Reflection API. Рефлексия на примерах. http://fetisovvs.blogspot.com/2017/02/java-reflection-api-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. http://fetisovvs.blogspot.com/2017/10/java-java.html
Микросервисы для Java программистов. Практическое введение во фреймворки и контейнеры. (Часть 3). http://fetisovvs.blogspot.com/2017/10/java-3-java.html
ТОП-3 способа конвертировать массив в ArrayList. Пример на Java. http://fetisovvs.blogspot.com/2016/09/3-arraylist-java-java.html
Ввод–вывод в Java. http://fetisovvs.blogspot.com/2016/05/java-java_28.html
Java Challengers #2: Сравнение строк. https://fetisovvs.blogspot.com/2018/11/java-challengers-2-java.html
Enum-Всемогущий. http://fetisovvs.blogspot.com/2017/02/enum-java.html
Массивы в Java. Создание и обработка. http://fetisovvs.blogspot.com/2017/10/java-java_18.html
Arrays, Collections: Алгоритмический минимум. http://fetisovvs.blogspot.com/2017/12/arrays-collections.html
Популярные методы для работы с Java массивами. http://fetisovvs.blogspot.com/2016/09/java-java_29.html
Пример использования метода replace в Java. Как заменить символ в строке? http://fetisovvs.blogspot.com/2017/01/replace-java-java.html
Класс Scanner в Java — описание и пример использования. http://fetisovvs.blogspot.com/2017/01/scanner-java-java.html
Пример использования метода trim в Java: как удалить пробелы в начале и конце строки? http://fetisovvs.blogspot.com/2017/01/trim-java-java.html
Spark — Потрясающий веб-микрофреймворк для Java. http://fetisovvs.blogspot.com/2017/10/spark-java-java.html
Чтение и запись CSV файла с помощью SuperCSV. http://fetisovvs.blogspot.com/2017/01/csv-supercsv-java-java.html
Конструкция try/catch/finally (исключения). http://fetisovvs.blogspot.com/2017/01/trycatchfinally-java.html
1000+ часов видео по Java на русском. http://fetisovvs.blogspot.nl/2017/06/1000-java-java.html
Раздача халявы: нетормозящие треды в Java. Project Loom. http://fetisovvs.blogspot.com/2018/09/java-project-loom-java.html
Шпаргалка Java программиста 7.1 Типовые задачи: Оптимальный путь преобразования InputStream в строку. http://fetisovvs.blogspot.com/2016/04/java-71-inputstream-java.html
Шпаргалки Java программиста 10: Lombok. http://fetisovvs.blogspot.nl/2017/12/java-10-lombok-java.html
Шпаргалки Java программиста 9: Java SE — Шпаргалка для собеседований и повторений. http://fetisovvs.blogspot.com/2017/12/java-9-java-se-java.html
Шпаргалка Java программиста 8. Библиотеки для работы с Json (Gson, Fastjson,
LoganSquare, Jackson, JsonPath и другие). http://fetisovvs.blogspot.com/2016/04/java-8-json-gson-fastjson-logansquare.html
Java 8 и паттерн Стратегия. http://fetisovvs.blogspot.com/2018/03/java-8-java.html
Java EE Concurency API. http://fetisovvs.blogspot.com/2018/08/java-ee-concurency-api-java.html
Реализация ООП-наследования в классах, работающих с SQL и MS Entity Framework. http://fetisovvs.blogspot.com/2017/02/sql-ms-entity-framework.html
Как установить соединение с СУБД MySQL в IntelliJ IDEA в редакции Community. http://fetisovvs.blogspot.com/2016/04/mysql-intellij-idea-community-java.html
TDD приложений на Spring Boot: работа с базой данных. https://fetisovvs.blogspot.com/2018/12/tdd-spring-boot-java.html
Руководство: Thymeleaf + Spring. Часть 1. https://fetisovvs.blogspot.com/2019/01/thymeleaf-spring-1-java.html
Введение в Spring Boot: создание простого REST API на Java. https://fetisovvs.blogspot.com/2019/01/spring-boot-rest-api-java-java.html
Максимально простой в поддержке способ интеграции java-клиента с java-сервером. http://fetisovvs.blogspot.com/2018/09/java-java-java.html
AOP vs Функции. https://fetisovvs.blogspot.com/2019/01/aop-vs-java.html
Как с помощью maven работать с библиотеками, которых в maven нет. http://fetisovvs.blogspot.com/2017/03/maven-maven-java.html
Проекты по созданию компиляторов из Java в JavaScript и исполняемые файлы. http://fetisovvs.blogspot.com/2018/01/java-javascript-java.html
Реактивное программирование с JAX-RS. http://fetisovvs.blogspot.com/2018/09/jax-rs-java.html
Компактные строки в Java 9. https://fetisovvs.blogspot.com/2018/10/java-9-java.html
Динамический прокси Java: что это и как им пользоваться? https://fetisovvs.blogspot.com/2018/12/java-java.html
Абстрактный CRUD от репозитория до контроллера: что ещё можно сделать при помощи Spring + Generics. http://fetisovvs.blogspot.com/2018/09/crud-spring-generics-java.html
Диагностика утечек памяти в Java. http://fetisovvs.blogspot.com/2017/03/java-java_18.html
Java, Spring, Kurento и медиасервисы. https://fetisovvs.blogspot.com/2019/01/java-spring-kurento-java.html
Spring AOP и JavaConfig в плагинах для Atlassian Jira. http://fetisovvs.blogspot.com/2018/04/spring-aop-javaconfig-atlassian-jira.html
Блеск и нищета Java для настольных систем. http://fetisovvs.blogspot.com/2018/04/java-haulmont-java.html
Разбор задачек от Одноклассников на JPoint 2018. http://fetisovvs.blogspot.com/2018/04/jpoint-2018-java.html
Программируем… выход из лабиринта. http://fetisovvs.blogspot.com/2015/10/java.html
Основы работы с IntelliJ IDEA. Интерфейс программы. http://fetisovvs.blogspot.com/2016/09/intellij-idea-java.html
Ускоряем время сборки и доставки java web приложения. http://fetisovvs.blogspot.com/2018/03/java-web-java.html
Открытый урок Java Enterprise «CDI in action». http://fetisovvs.blogspot.com/2018/09/java-enterprise-cdi-in-action-java.html
«Мы все стремимся к сложности, а потом с ней боремся»: интервью с Венкатом Субраманиамом. http://fetisovvs.blogspot.com/2018/09/java_16.html

Комментариев нет:

Отправить комментарий