Skip to content

Commit dabf1e6

Browse files
author
ayazychyan
committed
gh-362: internal http server api
1 parent 7fbdef3 commit dabf1e6

7 files changed

Lines changed: 237 additions & 23 deletions

File tree

http/http-server-common/src/main/java/ru/tinkoff/kora/http/server/common/HttpServerModule.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.micrometer.core.instrument.MeterRegistry;
44
import io.opentelemetry.api.trace.Tracer;
5+
import java.util.Optional;
56
import org.jspecify.annotations.Nullable;
67
import ru.tinkoff.kora.application.graph.All;
78
import ru.tinkoff.kora.application.graph.PromiseOf;
@@ -13,6 +14,7 @@
1314
import ru.tinkoff.kora.config.common.Config;
1415
import ru.tinkoff.kora.config.common.extractor.ConfigValueExtractionException;
1516
import ru.tinkoff.kora.config.common.extractor.ConfigValueExtractor;
17+
import ru.tinkoff.kora.http.server.common.annotation.InternalApi;
1618
import ru.tinkoff.kora.http.server.common.annotation.PrivateApi;
1719
import ru.tinkoff.kora.http.server.common.handler.HttpServerRequestHandler;
1820
import ru.tinkoff.kora.http.server.common.privateapi.LivenessHandler;
@@ -23,8 +25,6 @@
2325
import ru.tinkoff.kora.http.server.common.telemetry.impl.DefaultHttpServerTelemetryFactory;
2426
import ru.tinkoff.kora.telemetry.common.MetricsScraper;
2527

26-
import java.util.Optional;
27-
2828
public interface HttpServerModule extends StringParameterReadersModule, HttpServerRequestMapperModule, HttpServerResponseMapperModule {
2929

3030
default HttpServerConfig httpServerConfig(Config config, ConfigValueExtractor<HttpServerConfig> configValueExtractor) {
@@ -77,4 +77,19 @@ default HttpServerHandler privateApiHandler(@Tag(PrivateApi.class) All<HttpServe
7777
return new HttpServerHandler(handlers, interceptors, config);
7878
}
7979

80+
@InternalApi
81+
default InternalHttpServerConfig internalApiHttpServerConfig(Config config, ConfigValueExtractor<InternalHttpServerConfig> configValueExtractor) {
82+
var value = config.get("internalHttpServer");
83+
var parsed = configValueExtractor.extract(value);
84+
if (parsed == null) {
85+
throw ConfigValueExtractionException.missingValueAfterParse(value);
86+
}
87+
return parsed;
88+
}
89+
90+
@InternalApi
91+
default HttpServerHandler internalApiHandler(@Tag(InternalApi.class) All<HttpServerRequestHandler> handlers, @Tag(InternalApi.class) All<HttpServerInterceptor> interceptors, HttpServerConfig config) {
92+
return new HttpServerHandler(handlers, interceptors, config);
93+
}
94+
8095
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package ru.tinkoff.kora.http.server.common;
2+
3+
import ru.tinkoff.kora.config.common.annotation.ConfigValueExtractor;
4+
5+
@ConfigValueExtractor
6+
public interface InternalHttpServerConfig extends HttpServerConfig {
7+
8+
@Override
9+
default int port() {
10+
return 8090;
11+
}
12+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package ru.tinkoff.kora.http.server.common;
2+
3+
public enum NoopHttpServer implements HttpServer {
4+
5+
INSTANCE;
6+
7+
@Override
8+
public int port() {
9+
return -1;
10+
}
11+
12+
@Override
13+
public void init() {
14+
// no-op
15+
}
16+
17+
@Override
18+
public void release() {
19+
// no-op
20+
}
21+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package ru.tinkoff.kora.http.server.common.annotation;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Target;
5+
import ru.tinkoff.kora.common.Tag;
6+
7+
@Tag(InternalApi.class)
8+
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
9+
public @interface InternalApi {
10+
}

http/http-server-common/src/testFixtures/java/ru/tinkoff/kora/http/server/common/HttpServerTestKit.java

Lines changed: 154 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,45 @@
11
package ru.tinkoff.kora.http.server.common;
22

3+
import static java.time.Instant.now;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
6+
import static org.junit.jupiter.api.Assertions.fail;
7+
import static org.mockito.Mockito.never;
8+
import static org.mockito.Mockito.timeout;
9+
import static org.mockito.Mockito.verify;
10+
import static org.mockito.Mockito.when;
11+
import static ru.tinkoff.kora.http.common.HttpMethod.GET;
12+
import static ru.tinkoff.kora.http.common.HttpMethod.POST;
13+
314
import io.opentelemetry.api.trace.Span;
15+
import java.io.IOException;
16+
import java.io.OutputStream;
17+
import java.nio.ByteBuffer;
18+
import java.nio.charset.StandardCharsets;
19+
import java.time.Duration;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.Optional;
23+
import java.util.concurrent.CompletableFuture;
24+
import java.util.concurrent.ConcurrentLinkedDeque;
25+
import java.util.concurrent.ExecutionException;
26+
import java.util.concurrent.Executors;
27+
import java.util.concurrent.ForkJoinPool;
28+
import java.util.concurrent.ThreadLocalRandom;
29+
import java.util.concurrent.TimeUnit;
30+
import java.util.function.Supplier;
431
import okhttp3.ConnectionPool;
532
import okhttp3.OkHttpClient;
633
import okhttp3.Request;
734
import okhttp3.RequestBody;
835
import okio.BufferedSink;
936
import org.jetbrains.annotations.NotNull;
1037
import org.jspecify.annotations.Nullable;
11-
import org.junit.jupiter.api.*;
38+
import org.junit.jupiter.api.AfterEach;
39+
import org.junit.jupiter.api.Assertions;
40+
import org.junit.jupiter.api.Nested;
41+
import org.junit.jupiter.api.Test;
42+
import org.junit.jupiter.api.TestInstance;
1243
import org.mockito.AdditionalAnswers;
1344
import org.mockito.ArgumentMatchers;
1445
import org.mockito.Mockito;
@@ -32,28 +63,15 @@
3263
import ru.tinkoff.kora.http.server.common.privateapi.MetricsHandler;
3364
import ru.tinkoff.kora.http.server.common.privateapi.ReadinessHandler;
3465
import ru.tinkoff.kora.http.server.common.router.HttpServerHandler;
35-
import ru.tinkoff.kora.http.server.common.telemetry.*;
66+
import ru.tinkoff.kora.http.server.common.telemetry.$HttpServerTelemetryConfig_ConfigValueExtractor;
67+
import ru.tinkoff.kora.http.server.common.telemetry.$HttpServerTelemetryConfig_HttpServerLoggingConfig_ConfigValueExtractor;
68+
import ru.tinkoff.kora.http.server.common.telemetry.$HttpServerTelemetryConfig_HttpServerMetricsConfig_ConfigValueExtractor;
69+
import ru.tinkoff.kora.http.server.common.telemetry.$HttpServerTelemetryConfig_HttpServerTracingConfig_ConfigValueExtractor;
70+
import ru.tinkoff.kora.http.server.common.telemetry.HttpServerObservation;
71+
import ru.tinkoff.kora.http.server.common.telemetry.HttpServerTelemetry;
72+
import ru.tinkoff.kora.http.server.common.telemetry.NoopHttpServerTelemetry;
3673
import ru.tinkoff.kora.telemetry.common.MetricsScraper;
3774

38-
import java.io.IOException;
39-
import java.io.OutputStream;
40-
import java.nio.ByteBuffer;
41-
import java.nio.charset.StandardCharsets;
42-
import java.time.Duration;
43-
import java.util.ArrayList;
44-
import java.util.List;
45-
import java.util.Optional;
46-
import java.util.concurrent.*;
47-
import java.util.function.Supplier;
48-
49-
import static java.time.Instant.now;
50-
import static org.assertj.core.api.Assertions.assertThat;
51-
import static org.assertj.core.api.Assertions.assertThatThrownBy;
52-
import static org.junit.jupiter.api.Assertions.fail;
53-
import static org.mockito.Mockito.*;
54-
import static ru.tinkoff.kora.http.common.HttpMethod.GET;
55-
import static ru.tinkoff.kora.http.common.HttpMethod.POST;
56-
5775
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
5876
public abstract class HttpServerTestKit {
5977
protected static MetricsScraper registry = Mockito.mock(MetricsScraper.class);
@@ -74,6 +92,7 @@ public abstract class HttpServerTestKit {
7492

7593
private volatile HttpServer httpServer = null;
7694
private volatile HttpServer privateHttpServer = null;
95+
private volatile HttpServer internalHttpServer = null;
7796

7897
protected final OkHttpClient client = new OkHttpClient.Builder()
7998
.connectionPool(new ConnectionPool(0, 1, TimeUnit.MICROSECONDS))
@@ -190,6 +209,84 @@ void testReadinessFailureOnUninitializedProbe() throws IOException {
190209
}
191210

192211

212+
@Nested
213+
public class InternalApiTest {
214+
@Test
215+
void testNoopHttpServerWhenNoHandlers() {
216+
var noopServer = NoopHttpServer.INSTANCE;
217+
assertThat(noopServer.port()).isEqualTo(-1);
218+
Assertions.assertDoesNotThrow(noopServer::init);
219+
Assertions.assertDoesNotThrow(noopServer::release);
220+
}
221+
222+
@Test
223+
void testInternalApiHelloWorld() throws IOException {
224+
var httpResponse = HttpServerResponse.of(200, HttpBody.plaintext("internal hello"));
225+
var handler = handler(GET, "/internal", (_) -> httpResponse);
226+
startInternalHttpServer(handler);
227+
228+
var request = internalApiRequest("/internal")
229+
.get()
230+
.build();
231+
232+
try (var response = client.newCall(request).execute()) {
233+
assertThat(response.code()).isEqualTo(200);
234+
assertThat(response.body().string()).isEqualTo("internal hello");
235+
}
236+
}
237+
238+
@Test
239+
void testInternalApiUnknownPath() throws IOException {
240+
var handler = handler(GET, "/internal", (_) -> HttpServerResponse.of(200));
241+
startInternalHttpServer(handler);
242+
243+
var request = internalApiRequest("/unknown")
244+
.get()
245+
.build();
246+
247+
try (var response = client.newCall(request).execute()) {
248+
assertThat(response.code()).isEqualTo(404);
249+
}
250+
}
251+
252+
@Test
253+
void testInternalApiWithInterceptor() throws IOException {
254+
var httpResponse = HttpServerResponse.of(200, HttpBody.plaintext("internal hello"));
255+
var handler = handler(GET, "/internal", (_) -> httpResponse);
256+
var interceptor = new HttpServerInterceptor() {
257+
@Override
258+
public HttpServerResponse intercept(HttpServerRequest request, InterceptChain chain) throws Exception {
259+
var header = request.headers().getFirst("x-internal-block");
260+
if (header != null) {
261+
request.body().close();
262+
return HttpServerResponse.of(403, HttpBody.plaintext("blocked"));
263+
}
264+
return chain.process(request);
265+
}
266+
};
267+
startInternalHttpServer(List.of(interceptor), handler);
268+
269+
var request = internalApiRequest("/internal")
270+
.get()
271+
.build();
272+
273+
try (var response = client.newCall(request).execute()) {
274+
assertThat(response.code()).isEqualTo(200);
275+
assertThat(response.body().string()).isEqualTo("internal hello");
276+
}
277+
278+
var blockedRequest = internalApiRequest("/internal")
279+
.header("x-internal-block", "true")
280+
.get()
281+
.build();
282+
283+
try (var response = client.newCall(blockedRequest).execute()) {
284+
assertThat(response.code()).isEqualTo(403);
285+
assertThat(response.body().string()).isEqualTo("blocked");
286+
}
287+
}
288+
}
289+
193290
@Nested
194291
public class PublicApiTest {
195292
@Test
@@ -990,6 +1087,34 @@ protected void startPrivateHttpServer() {
9901087
}
9911088
}
9921089

1090+
protected void startInternalHttpServer(HttpServerRequestHandler... handlers) {
1091+
startInternalHttpServer(List.of(), handlers);
1092+
}
1093+
1094+
protected void startInternalHttpServer(List<HttpServerInterceptor> interceptors, HttpServerRequestHandler... handlers) {
1095+
var config = new HttpServerConfig_Impl(
1096+
0,
1097+
false,
1098+
Duration.ofSeconds(1),
1099+
Duration.ofSeconds(1),
1100+
false,
1101+
Duration.ofMillis(1),
1102+
new $HttpServerTelemetryConfig_ConfigValueExtractor.HttpServerTelemetryConfig_Impl(
1103+
new $HttpServerTelemetryConfig_HttpServerLoggingConfig_ConfigValueExtractor.HttpServerLoggingConfig_Defaults(),
1104+
new $HttpServerTelemetryConfig_HttpServerMetricsConfig_ConfigValueExtractor.HttpServerMetricsConfig_Defaults(),
1105+
new $HttpServerTelemetryConfig_HttpServerTracingConfig_ConfigValueExtractor.HttpServerTracingConfig_Defaults()
1106+
),
1107+
Size.of(1, Size.Type.GiB)
1108+
);
1109+
var internalApiHandler = new HttpServerHandler(List.of(handlers), interceptors, config);
1110+
this.internalHttpServer = this.httpServer(valueOf(config), internalApiHandler, this.telemetry);
1111+
try {
1112+
this.internalHttpServer.init();
1113+
} catch (Exception e) {
1114+
throw new RuntimeException(e);
1115+
}
1116+
}
1117+
9931118
@AfterEach
9941119
void tearDown() throws Exception {
9951120
if (this.httpServer != null) {
@@ -1000,6 +1125,10 @@ void tearDown() throws Exception {
10001125
this.privateHttpServer.release();
10011126
this.privateHttpServer = null;
10021127
}
1128+
if (this.internalHttpServer != null) {
1129+
this.internalHttpServer.release();
1130+
this.internalHttpServer = null;
1131+
}
10031132
this.readinessProbePromise.setValue(readinessProbe);
10041133
this.livenessProbePromise.setValue(livenessProbe);
10051134
}
@@ -1021,6 +1150,10 @@ protected Request.Builder privateApiRequest(String path) {
10211150
return request(this.privateHttpServer.port(), path);
10221151
}
10231152

1153+
protected Request.Builder internalApiRequest(String path) {
1154+
return request(this.internalHttpServer.port(), path);
1155+
}
1156+
10241157
protected Request.Builder request(String path) {
10251158
return request(this.httpServer.port(), path);
10261159
}

http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowHttpServerModule.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,17 @@
33
import io.undertow.Undertow;
44
import org.jspecify.annotations.Nullable;
55
import org.xnio.XnioWorker;
6+
import ru.tinkoff.kora.application.graph.All;
67
import ru.tinkoff.kora.application.graph.ValueOf;
8+
import ru.tinkoff.kora.common.Tag;
79
import ru.tinkoff.kora.common.annotation.Root;
810
import ru.tinkoff.kora.common.util.Configurer;
11+
import ru.tinkoff.kora.http.server.common.HttpServer;
912
import ru.tinkoff.kora.http.server.common.HttpServerConfig;
13+
import ru.tinkoff.kora.http.server.common.InternalHttpServerConfig;
14+
import ru.tinkoff.kora.http.server.common.NoopHttpServer;
15+
import ru.tinkoff.kora.http.server.common.annotation.InternalApi;
16+
import ru.tinkoff.kora.http.server.common.handler.HttpServerRequestHandler;
1017
import ru.tinkoff.kora.http.server.common.router.HttpServerHandler;
1118
import ru.tinkoff.kora.http.server.common.telemetry.HttpServerTelemetryFactory;
1219

@@ -20,4 +27,19 @@ default UndertowHttpServer undertowHttpServer(ValueOf<HttpServerConfig> config,
2027
var telemetry = telemetryFactory.get(config.get().telemetry());
2128
return new UndertowHttpServer(config, handler, "kora-undertow", telemetry, worker, configurer);
2229
}
30+
31+
@Root
32+
@InternalApi
33+
default HttpServer internalApiUndertowHttpServer(@Tag(InternalApi.class) All<HttpServerRequestHandler> handlers,
34+
@InternalApi ValueOf<InternalHttpServerConfig> config,
35+
@InternalApi ValueOf<HttpServerHandler> handler,
36+
HttpServerTelemetryFactory telemetryFactory,
37+
XnioWorker worker,
38+
@Nullable @InternalApi Configurer<Undertow.Builder> configurer) {
39+
if (handlers.isEmpty()) {
40+
return NoopHttpServer.INSTANCE;
41+
}
42+
var telemetry = telemetryFactory.get(config.get().telemetry());
43+
return new UndertowHttpServer(config, handler, "kora-undertow-internal", telemetry, worker, configurer);
44+
}
2345
}

http/http-server-undertow/src/main/java/ru/tinkoff/kora/http/server/undertow/UndertowModule.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ default UndertowConfig undertowHttpServerConfig(Config config, ConfigValueExtrac
3838
}
3939
return parsed;
4040
}
41+
4142
}

0 commit comments

Comments
 (0)