Модуль предоставляет тонкий слой абстракции над библиотеками HTTP-сервера для создания обработчиков HTTP-запросов с помощью аннотаций в декларативном стиле, так и в императивном стиле.
???+ tip "Совет"
**Мы советуем** использовать подход когда первичен контракт в формате OpenAPI
и из него создаются контроллеры по средствам генератора.
Такой подход позволяет достигнуть консистентности контракта между потребителем и собственником,
и позволять делиться этим контрактом для создания клиентов для него по средствам такого же подхода.
Подробнее про генератор в [секции про генерации из OpenAPI](openapi-codegen.md).
Реализация основанная на Undertow. Undertow — это легковесный веб-сервер с открытым исходным кодом для Java-приложений. Он построен на асинхронных и неблокирующих I/O-операциях с использованием NIO, что обеспечивает высокую производительность и низкое потребление ресурсов.
===! ":fontawesome-brands-java: Java"
[Зависимость](general.md#_4) `build.gradle`:
```groovy
implementation "ru.tinkoff.kora:http-server-undertow"
```
Модуль:
```java
@KoraApp
public interface Application extends UndertowHttpServerModule { }
```
=== ":simple-kotlin: Kotlin"
[Зависимость](general.md#_4) `build.gradle.kts`:
```groovy
implementation("ru.tinkoff.kora:http-server-undertow")
```
Модуль:
```kotlin
@KoraApp
interface Application : UndertowHttpServerModule
```
Пример полной конфигурации, описанной в классе HttpServerConfig (указаны примеры значений или значения по умолчанию):
===! ":material-code-json: Hocon"
```javascript
httpServer {
publicApiHttpPort = 8080 //(1)!
privateApiHttpPort = 8085 //(2)!
privateApiHttpMetricsPath = "/metrics" //(3)!
privateApiHttpReadinessPath = "/system/readiness" //(4)!
privateApiHttpLivenessPath = "/system/liveness" //(5)!
ignoreTrailingSlash = false //(6)!
ioThreads = 2 //(7)!
blockingThreads = 2 //(8)!
shutdownWait = "30s" //(9)!
threadKeepAliveTimeout = "60s" //(10)!
socketReadTimeout = "0s" //(11)!
socketWriteTimeout = "0s" //(12)!
socketKeepAliveEnabled = false //(13)!
virtualThreadsEnabled = false //(14)!
telemetry {
logging {
enabled = false //(15)!
stacktrace = true //(16)!
mask = "***" //(17)!
maskqueries = [ ] //(18)!
maskheaders = [ "authorization", "cookie", "set-cookie" ] //(19)!
pathtemplate = true //(20)!
}
metrics {
enabled = true //(21)!
slo = [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] //(22)!
}
tracing {
enabled = true //(23)!
}
}
}
```
1. Порт публичного HTTP-сервера
2. Порт служебного HTTP-сервера
3. Путь для получения [метрик](metrics.md) на служебном сервере
4. Путь для получения статуса [проб готовности](probes.md) на служебном сервере
5. Путь для получения статуса [проб жизнеспособности](probes.md) на служебном сервере
6. Игнорировать ли слэш в окончании пути, если включен то `/my/path` и `/my/path/` будут интерпритироваться одинакого, по умолчанию выключен
7. Количество потоков сетевых обработчиков, по умолчанию равен кол-во ядер процессора либо минимум `2`
8. Количество потоков обработчиков запросов, по умолчанию равен кол-во ядер процессора умноженных на 2 либо минимум `2` потока
9. Время ожидания обработки перед выключением сервера в случае [штатного завершения](container.md#_24)
10. Максимальное время жизни потока обработчика запроса
11. Максимальное время ожидания чтения данные из сокета/соединения
12. Максимальное время ожидания записи данных в сокет/соединение
13. Отсылать ли сообщения `keep-alive` во время жизни сокета/соединения TCP
14. Включает поддержку виртуальных потоков для обработки запросов (вместо `blockingThreads`), требует Java 21+
15. Включает логгирование модуля (по умолчанию `false`)
16. Включает логгирование стэка вызовов в случае исключения
17. Маска которая используется для скрытия указанных заголовков и параметров запроса/ответа
18. Список параметров запроса которые следует скрывать
19. Список заголовков запроса/ответа которые следует скрывать
20. Использовать ли всегда шаблон пути запроса при логгировании. По умолчанию используется всегда шаблон пути, за исключением уровня логирования `TRACE` где использует полный путь.
21. Включает метрики модуля (по умолчанию `true`)
22. Настройка [SLO](https://www.atlassian.com/ru/incident-management/kpis/sla-vs-slo-vs-sli) для [DistributionSummary](https://github.com/micrometer-metrics/micrometer-docs/blob/main/src/docs/concepts/distribution-summaries.adoc) метрики
23. Включает трассировку модуля (по умолчанию `true`)
=== ":simple-yaml: YAML"
```yaml
httpServer:
publicApiHttpPort: 8080 #(1)!
privateApiHttpPort: 8085 #(2)!
privateApiHttpMetricsPath: "/metrics" #(3)!
privateApiHttpReadinessPath: "/system/readiness" #(4)!
privateApiHttpLivenessPath: "/system/liveness" #(5)!
ignoreTrailingSlash: false #(6)!
ioThreads: 2 #(7)!
blockingThreads: 2 #(8)!
shutdownWait: "30s" #(9)!
threadKeepAliveTimeout: "60s" #(10)!
socketReadTimeout: "0s" #(11)!
socketWriteTimeout: "0s" #(12)!
socketKeepAliveEnabled: false #(13)!
virtualThreadsEnabled: false #(14)!
telemetry:
logging:
enabled: false #(15)!
stacktrace: true #(16)!
mask: "***" #(17)!
maskQueries: [ ] #(18)!
maskHeaders: [ "authorization", "cookie", "set-cookie" ] #(19)!
pathTemplate: true #(20)!
metrics:
enabled: true #(21)!
slo: [ 1, 10, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 30000, 60000, 90000 ] #(22)!
telemetry:
enabled: true #(23)!
```
1. Порт публичного HTTP-сервера
2. Порт служебного HTTP-сервера
3. Путь для получения [метрик](metrics.md) на служебном сервере
4. Путь для получения статуса [проб готовности](probes.md) на служебном сервере
5. Путь для получения статуса [проб жизнеспособности](probes.md) на служебном сервере
6. Игнорировать ли слэш в окончании пути, если включен то `/my/path` и `/my/path/` будут интерпритироваться одинакого, по умолчанию выключен
7. Количество потоков сетевых обработчиков, по умолчанию равен кол-во ядер процессора либо минимум `2`
8. Количество потоков обработчиков запросов, по умолчанию равен кол-во ядер процессора умноженных на 2 либо минимум `2` потока
9. Время ожидания обработки перед выключением сервера в случае [штатного завершения](container.md#_24)
10. Максимальное время жизни потока обработчика запроса
11. Максимальное время ожидания чтения данные из сокета/соединения
12. Максимальное время ожидания записи данных в сокет/соединение
13. Отсылать ли сообщения `keep-alive` во время жизни сокета/соединения TCP
14. Включает поддержку виртуальных потоков для обработки запросов (вместо `blockingThreads`), требует Java 21+
15. Включает логгирование модуля (по умолчанию `false`)
16. Включает логгирование стэка вызовов в случае исключения
17. Маска которая используется для скрытия указанных заголовков и параметров запроса/ответа
18. Список параметров запроса которые следует скрывать
19. Список заголовков запроса/ответа которые следует скрывать
20. Использовать ли всегда шаблон пути запроса при логгировании. По умолчанию используется всегда шаблон пути, за исключением уровня логирования `TRACE` где использует полный путь.
21. Включает метрики модуля (по умолчанию `true`)
22. Настройка [SLO](https://www.atlassian.com/ru/incident-management/kpis/sla-vs-slo-vs-sli) для [DistributionSummary](https://github.com/micrometer-metrics/micrometer-docs/blob/main/src/docs/concepts/distribution-summaries.adoc) метрики
23. Включает трассировку модуля (по умолчанию `true`)
Для создания контроллера следует использовать @HttpController аннотацию, а для его регистрации как зависимость @Component.
Аннотация @HttpRoute отвечает за указания пути и метода HTTP для конкретного метода обработчика.
===! ":fontawesome-brands-java: Java"
```java
@Component //(1)!
@HttpController //(2)!
public final class SomeController {
//(3)!
@HttpRoute(method = HttpMethod.POST, //(4)!
path = "/hello/world") //(5)!
public String helloWorld() {
return "Hello World";
}
}
```
1. Указывает что класс является компонентом и его требуется зарегистрировать в контейнере приложения
2. Указывает что класс является контроллером и содержит HTTP-обработчики
3. Указывает что метод является обработчиком пути в контроллере
4. Указывает тип HTTP метода обработчика
5. Указывает путь метода обработчика
=== ":simple-kotlin: Kotlin"
```kotlin
@Component //(1)!
@HttpController //(2)!
class SomeController {
//(3)!
@HttpRoute(method = HttpMethod.POST, //(4)!
path = "/hello/world") //(5)!
fun helloWorld(): String {
return "Hello World"
}
}
```
1. Указывает что класс является компонентом и его требуется зарегистрировать в контейнере приложения
2. Указывает что класс является контроллером и содержит HTTP-обработчики
3. Указывает что метод является обработчиком пути в контроллере
4. Указывает тип HTTP метода обработчика
5. Указывает путь метода обработчика
Секция описывает преобразования HTTP-запроса у контроллера. Предлагается использовать специальные аннотации для указания параметров запроса.
@Path — обозначает значение части пути запроса, сам параметр указывается в {кавычках} в пути
и имя параметра указывается в value либо по умолчанию равно имени аргумента метода.
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/{pathName}")
public String helloWorld(@Path("pathName") String pathValue) {
return "Hello World";
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/{pathName}")
fun helloWorld(
@Path("pathName") pathValue: String
): String {
return "Hello World";
}
}
```
@Query — значение параметра запроса, имя параметра указывается в value либо по умолчанию равно имени аргумента метода.
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public String helloWorld(@Query("queryName") String queryValue,
@Query("queryNameList") List<String> queryValues) {
return "Hello World";
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
fun helloWorld(
@Query("queryName") queryValue: String,
@Query("queryNameList") queryValues: List<String>
): String {
return "Hello World";
}
}
```
@Header — значение заголовка запроса, имя параметра указывается в value либо по умолчанию равно имени аргумента метода.
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public String helloWorld(@Header("headerName") String headerValue,
@Header("headerNameList") List<String> headerValues) {
return "Hello World";
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
operator fun helloWorld(
@Header("headerName") headerValue: String,
@Header("headerNameList") headerValues: List<String>
): String {
return "Hello World";
}
}
```
Для указания тела запроса требуется использовать аргумент метода без специальных аннотации,
по умолчанию поддерживаются такие типы как byte[], ByteBuffer, String.
Для указания, что тело является Json и ему требуется автоматически создать такой читатель и внедрить его,
требуется использовать аннотацию @Json:
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
public record Request(String name) {}
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public String helloWorld(@Json Request body) { //(1)!
return "Hello World";
}
}
```
1. Указывает что тело должно быть записано как Json
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
data class Request(val name: String)
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
fun helloWorld(@Json body: Request): String { //(1)!
return "Hello World"
}
}
```
1. Указывает что тело должно быть записано как Json
Требуется подключить модуль Json.
Можно использовать FormUrlEncoded как тип аргумента тела форма данных.
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public String helloWorld(FormUrlEncoded body) {
return "Hello World";
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
fun helloWorld(body: FormUrlEncoded): String {
return "Hello World"
}
}
```
Можно использовать FormMultipart как тип аргумента тела бинарная форма.
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public String helloWorld(FormMultipart body) {
return "Hello World";
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
fun helloWorld(body: FormMultipart): String {
return "Hello World"
}
}
```
@Cookie — значение Cookie, имя параметра указывается в value либо по умолчанию равно имени аргумента метода.
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public String helloWorld(@Cookie("cookieName") String cookieValue) {
return "Hello World";
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
operator fun helloWorld(
@Cookie("cookieName") cookieValue: String
): String {
return "Hello World";
}
}
```
В случае если требуется обрабатывать запрос отличным способом, то можно использовать специальный интерфейс HttpServerRequestMapper:
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
public record UserContext(String userId, String traceId) {}
public static final class RequestMapper implements HttpServerRequestMapper<UserContext> {
@Override
public UserContext apply(HttpServerRequest request) {
return new UserContext(request.headers().getFirst("x-user-id"), request.headers().getFirst("x-trace-id"));
}
}
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public String get(@Mapping(RequestMapper.class) UserContext context) {
return "Hello World";
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class MapperRequestController {
data class UserContext(val userId: String, val traceId: String)
class RequestMapper : HttpServerRequestMapper<UserContext> {
override fun apply(request: HttpServerRequest): UserContext {
return UserContext(
request.headers().getFirst("x-user-id"),
request.headers().getFirst("x-trace-id")
)
}
}
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
@Mapping(RequestMapper::class)
operator fun get(@Mapping(RequestMapper::class) context: UserContext): String {
return "Hello World"
}
}
```
===! ":fontawesome-brands-java: Java"
По умолчанию все аргументы объявленные в методе являются **обязательными** (*NotNull*).
=== ":simple-kotlin: Kotlin"
По умолчанию все аргументы объявленные в методе которые не используют [Kotlin Nullability](https://kotlinlang.ru/docs/null-safety.html) синтаксис считаются **обязательными** (*NotNull*).
===! ":fontawesome-brands-java: Java"
В случае если аргумент метода является необязательным, то есть может отсутствовать то,
можно использовать аннотацию `@Nullable`:
```java
@Component
@HttpController
public final class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public String helloWorld(@Nullable @Query("queryName") String queryValue) { //(1)!
return "Hello World";
}
}
```
1. Подойдет любая аннотация `@Nullable`, такие как `javax.annotation.Nullable` / `jakarta.annotation.Nullable` / `org.jetbrains.annotations.Nullable` / и т.д.
=== ":simple-kotlin: Kotlin"
Предполагается использовать [Kotlin Nullability](https://kotlinlang.ru/docs/null-safety.html) синтаксис и помечать такой параметр как Nullable:
```kotlin
@Component
@HttpController
class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
fun helloWorld(@Query("queryName") queryValue: String?): String {
return "Hello World"
}
}
```
По умолчанию можно использовать стандартные типы возвращаемых значений,
такие как byte[], ByteBuffer, String которые будут обработаны со статус кодом 200 и соответствующим заголовком типа ответа
либо HttpServerResponse где надо будет самостоятельно заполнить всю информацию об HTTP ответе.
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public HttpServerResponse helloWorld() {
return HttpServerResponse.of(
200, //(1)!
HttpHeaders.of("headerName", "headerValue"), //(2)!
HttpBody.plaintext(body) //(3)!
);
}
}
```
1. HTTP статус код ответа
2. Заголовки ответа
3. Тело ответа
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
fun helloWorld(): HttpServerResponse {
return HttpServerResponse.of(
200, //(1)!
HttpHeaders.of("headerName", "headerValue"), //(2)!
HttpBody.plaintext(body) //(3)!
)
}
}
```
1. HTTP статус код ответа
2. Заголовки ответа
3. Тело ответа
В случае если предполагается отвечать в формате Json, то требуется использовать аннотацию @Json над методом:
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
public record Response(String greeting) {}
@Json //(1)!
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public Response helloWorld() {
return new Response("Hello World");
}
}
```
1. Указывает что ответ должен быть в формате Json
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
data class Response(val greeting: String)
@Json //(1)!
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
fun helloWorld(): Response {
return Response("Hello World")
}
}
```
1. Указывает что ответ должен быть в формате Json
Требуется подключить модуль Json.
Если предполагается читать тело и получить также заголовки и статус код ответа,
то предполагается использовать HttpResponseEntity, это обертка над телом ответа.
Ниже показан пример аналогичный примеру Json вместе с оберткой HttpResponseEntity:
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
public record Response(String greeting) {}
@Json
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public HttpResponseEntity<Response> helloWorld() {
return HttpResponseEntity.of(200, HttpHeaders.of("myHeader", "12345"), new Response("Hello World"));
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
data class Response(val greeting: String)
@Json
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
fun helloWorld(): HttpResponseEntity<Response> {
return HttpResponseEntity.of(200, HttpHeaders.of("myHeader", "12345"), Response("Hello World"));
}
}
```
Если требуется отвечать ошибкой, то можно использовать HttpServerResponseException для того чтобы бросать исключение.
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/{pathName}")
public String helloWorld(@Path("pathName") String pathValue) {
if("null".equals(pathValue)) {
throw HttpServerResponseException.of(400, "Bad request");
}
return "OK";
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
@HttpRoute(method = HttpMethod.POST, path = "/hello/{pathName}")
fun helloWorld(@Path("pathName") pathValue: String): String {
if ("null" == pathValue) {
throw HttpServerResponseException.of(400, "Bad request")
}
return "OK"
}
}
```
В случае если требуется чтение ответа отличным способом, то можно использовать специальный интерфейс HttpServerResponseMapper:
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
public record HelloWorldResponse(String greeting, String name) {}
public static final class ResponseMapper implements HttpServerResponseMapper<HelloWorldResponse> {
@Override
public HttpServerResponse apply(Context ctx, HttpServerRequest request, HelloWorldResponse result) {
return HttpServerResponse.of(200, HttpBody.plaintext(result.greeting() + " - " + result.name()));
}
}
@Mapping(ResponseMapper.class)
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
public HelloWorldResponse helloWorld() {
return new HelloWorldResponse("Hello World", "Bob");
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
data class HelloWorldResponse(val greeting: String, val name: String)
class ResponseMapper : HttpServerResponseMapper<HelloWorldResponse> {
fun apply(ctx: Context, request: HttpServerRequest, result: HelloWorldResponse): HttpServerResponse {
return HttpServerResponse.of(200, HttpBody.plaintext(result.greeting + " - " + result.name))
}
}
@Mapping(ResponseMapper::class)
@HttpRoute(method = HttpMethod.POST, path = "/hello/world")
fun helloWorld(): HelloWorldResponse {
return HelloWorldResponse("Hello World", "Bob")
}
}
```
Доступные сигнатуры для методов декларативного HTTP обработчика из коробки:
===! ":fontawesome-brands-java: Java"
Под `T` подразумевается тип возвращаемого значения, либо `Void`.
- `T myMethod()`
- `CompletionStage<T> myMethod()` [CompletionStage](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/CompletionStage.html)
- `Mono<T> myMethod()` [Project Reactor](https://projectreactor.io/docs/core/release/reference/) (надо подключить [зависимость](https://mvnrepository.com/artifact/io.projectreactor/reactor-core))
=== ":simple-kotlin: Kotlin"
Под `T` подразумевается тип возвращаемого значения, либо `Unit`.
- `myMethod(): T`
- `suspend myMethod(): T` [Kotlin Coroutine](https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine) (надо подключить [зависимость](https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core) как `implementation`)
Можно создавать перехватчики для изменения поведения либо создания дополнительного поведения используя класс HttpServerInterceptor.
Перехватчики можно использовать на:
- Конкретных методах контроллера
- Контроллере целиком
- Всех контроллерах сразу (требуется использовать
@Tag(HttpServerModule.class)над классом перехватчиком) (такой перехватчик может быть лишь один)
===! ":fontawesome-brands-java: Java"
```java
@Component
@HttpController
public final class SomeController {
public static final class MethodInterceptor implements HttpServerInterceptor {
@Override
public CompletionStage<HttpServerResponse> intercept(Context context,
HttpServerRequest request,
InterceptChain chain) throws Exception {
return chain.process(context, request);
}
}
@InterceptWith(MethodInterceptor.class)
@HttpRoute(method = HttpMethod.POST, path = "/intercepted")
public String helloWorld() {
return "Hello World";
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Component
@HttpController
class SomeController {
class MethodInterceptor : HttpServerInterceptor {
override fun intercept(
context: Context,
request: HttpServerRequest,
chain: HttpServerInterceptor.InterceptChain
): CompletionStage<HttpServerResponse> {
return chain.process(context, request)
}
}
@InterceptWith(MethodInterceptor::class)
@HttpRoute(method = HttpMethod.POST, path = "/intercepted")
fun helloWorld(): String {
return "Hello World"
}
}
```
Обработка ошибок на уровне всех HTTP ответов может быть реализована также посредствам перехватчика, ниже представлен простой пример такого перехватчика.
===! ":fontawesome-brands-java: Java"
```java
@Tag(HttpServerModule.class)
@Component
public final class ErrorInterceptor implements HttpServerInterceptor {
@Override
public CompletionStage<HttpServerResponse> intercept(Context context,
HttpServerRequest request,
InterceptChain chain) throws Exception {
return chain.process(context, request).exceptionally(e -> {
if(e instanceof CompletionException) {
e = e.getCause();
}
if (e instanceof HttpServerResponseException ex) {
return ex;
}
var body = HttpBody.plaintext(e.getMessage());
if (e instanceof IllegalArgumentException) {
return HttpServerResponse.of(400, body);
} else if (e instanceof TimeoutException) {
return HttpServerResponse.of(408, body);
} else {
return HttpServerResponse.of(500, body);
}
});
}
}
```
=== ":simple-kotlin: Kotlin"
```kotlin
@Tag(HttpServerModule.class)
@Component
class ErrorInterceptor : HttpServerInterceptor {
override fun intercept(
context: Context,
request: HttpServerRequest,
chain: HttpServerInterceptor.InterceptChain
): CompletionStage<HttpServerResponse> {
return chain.process(context, request).exceptionally { e ->
val error = if (e is CompletionException) e.cause!! else e
if (error is HttpServerResponseException) {
return@exceptionally error
}
val body = HttpBody.plaintext(error.message)
when (error) {
is IllegalArgumentException -> HttpServerResponse.of(400, body)
is TimeoutException -> HttpServerResponse.of(408, body)
else -> HttpServerResponse.of(500, body)
}
}
}
}
```
Для создания контроллера следует реализовать HttpServerRequestHandler.HandlerFunction интерфейс,
а затем зарегистрировать его в обработчике HttpServerRequestHandler.
Ниже показан пример по обработке всех описанных декларативных параметров запроса из примеров выше:
===! ":fontawesome-brands-java: Java"
```java
public interface SomeModule {
default HttpServerRequestHandler someHttpHandler() {
return HttpServerRequestHandlerImpl.of(HttpMethod.POST, //(1)!
"/hello/{world}", //(2)!
(context, request) -> {
var path = RequestHandlerUtils.parseStringPathParameter(request, "world");
var query = RequestHandlerUtils.parseOptionalStringQueryParameter(request, "query");
var queries = RequestHandlerUtils.parseOptionalStringListQueryParameter(request, "Queries");
var header = RequestHandlerUtils.parseOptionalStringHeaderParameter(request, "header");
var headers = RequestHandlerUtils.parseOptionalStringListHeaderParameter(request, "Headers");
return CompletableFuture.completedFuture(HttpServerResponse.of(200, HttpBody.plaintext("Hello World")));
});
}
}
```
1. Указывает тип HTTP метода обработчика
2. Указывает путь метода обработчика
=== ":simple-kotlin: Kotlin"
```kotlin
interface SomeModule {
fun someHttpHandler(): HttpServerRequestHandler? {
return HttpServerRequestHandlerImpl.of(
HttpMethod.POST, //(1)!
"/hello/{world}" //(2)!
) { context: Context, request: HttpServerRequest ->
val path = RequestHandlerUtils.parseStringPathParameter(request, "world")
val query = RequestHandlerUtils.parseOptionalStringQueryParameter(request, "query")
val queries = RequestHandlerUtils.parseOptionalStringListQueryParameter(request, "Queries")
val header = RequestHandlerUtils.parseOptionalStringHeaderParameter(request, "header")
val headers = RequestHandlerUtils.parseOptionalStringListHeaderParameter(request, "Headers")
CompletableFuture.completedFuture(HttpServerResponse.of(200, HttpBody.plaintext("Hello World")))
}
}
}
```
1. Указывает тип HTTP метода обработчика
2. Указывает путь метода обработчика