diff --git a/benchmarks/jmh/src/jmh/kotlin/com/linecorp/armeria/internal/common/kotlin/AnnotatedServiceFlowBenchmark.kt b/benchmarks/jmh/src/jmh/kotlin/com/linecorp/armeria/internal/common/kotlin/AnnotatedServiceFlowBenchmark.kt index 6baf919793e..49972eb8fd2 100644 --- a/benchmarks/jmh/src/jmh/kotlin/com/linecorp/armeria/internal/common/kotlin/AnnotatedServiceFlowBenchmark.kt +++ b/benchmarks/jmh/src/jmh/kotlin/com/linecorp/armeria/internal/common/kotlin/AnnotatedServiceFlowBenchmark.kt @@ -37,32 +37,32 @@ import java.util.stream.IntStream @Warmup(iterations = 1) @Suppress("unused") open class AnnotatedServiceFlowBenchmark { - lateinit var server: Server lateinit var client: WebClient @Setup open fun setup() { - server = Server.builder() - .annotatedService( - "/benchmark", - object { - @Get("/flow") - @ProducesJsonSequences - fun flowBm(): Flow = flow { - (0 until 1000).forEach { - emit("$it") - } - } + server = + Server.builder() + .annotatedService( + "/benchmark", + object { + @Get("/flow") + @ProducesJsonSequences + fun flowBm(): Flow = + flow { + (0 until 1000).forEach { + emit("$it") + } + } - @Get("/publisher") - @ProducesJsonSequences - fun publisherBm(): Publisher = - Flux.fromStream(IntStream.range(0, 1000).mapToObj { it.toString() }) - } - ) - .build() - .also { it.start().join() } + @Get("/publisher") + @ProducesJsonSequences + fun publisherBm(): Publisher = Flux.fromStream(IntStream.range(0, 1000).mapToObj { it.toString() }) + }, + ) + .build() + .also { it.start().join() } client = WebClient.of("h2c://127.0.0.1:${server.activeLocalPort()}") } diff --git a/core/src/test/java/com/linecorp/armeria/client/EndpointTest.java b/core/src/test/java/com/linecorp/armeria/client/EndpointTest.java index 8f22effdb53..727dfd213f0 100644 --- a/core/src/test/java/com/linecorp/armeria/client/EndpointTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/EndpointTest.java @@ -32,6 +32,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; +import com.google.common.testing.EqualsTester; import com.linecorp.armeria.client.Endpoint.Type; import com.linecorp.armeria.common.Attributes; @@ -454,9 +455,10 @@ void equals() { final Endpoint a1 = Endpoint.of("a"); final Endpoint a2 = Endpoint.of("a"); - assertThat(a1).isNotEqualTo(new Object()); - assertThat(a1).isEqualTo(a1); - assertThat(a1).isEqualTo(a2); + new EqualsTester() + .addEqualityGroup(a1, a2) + .addEqualityGroup(new Object()) + .testEquals(); } @Test @@ -468,6 +470,7 @@ void portEquals() { final Endpoint e = Endpoint.of("a", 80).withIpAddr("::1"); final Endpoint f = Endpoint.of("a", 80).withWeight(500); // Weight not part of comparison final Endpoint g = Endpoint.of("g", 80); + assertThat(a).isNotEqualTo(b); assertThat(b).isEqualTo(c); assertThat(b).isNotEqualTo(d); diff --git a/core/src/test/java/com/linecorp/armeria/client/circuitbreaker/EventCountTest.java b/core/src/test/java/com/linecorp/armeria/client/circuitbreaker/EventCountTest.java index c0d178f9337..b525d82540c 100644 --- a/core/src/test/java/com/linecorp/armeria/client/circuitbreaker/EventCountTest.java +++ b/core/src/test/java/com/linecorp/armeria/client/circuitbreaker/EventCountTest.java @@ -21,6 +21,8 @@ import org.junit.jupiter.api.Test; +import com.google.common.testing.EqualsTester; + class EventCountTest { @Test @@ -56,10 +58,12 @@ void testInvalidArguments() { @Test void testEquals() { - final EventCount ec = EventCount.of(1, 1); - assertThat(ec).isEqualTo(ec); - assertThat(EventCount.of(0, 0)).isEqualTo(EventCount.of(0, 0)); - assertThat(EventCount.of(1, 0)).isNotEqualTo(EventCount.of(0, 0)); - assertThat(EventCount.of(1, 0)).isNotEqualTo(new Object()); + new EqualsTester() + .addEqualityGroup(EventCount.of(0, 0), EventCount.of(0, 0)) + .addEqualityGroup(EventCount.of(1, 0), EventCount.of(1, 0)) + .addEqualityGroup(EventCount.of(0, 1), EventCount.of(0, 1)) + .addEqualityGroup(EventCount.of(1, 1), EventCount.of(1, 1)) + .addEqualityGroup(new Object()) + .testEquals(); } } diff --git a/core/src/test/java/com/linecorp/armeria/common/HttpHeadersBaseTest.java b/core/src/test/java/com/linecorp/armeria/common/HttpHeadersBaseTest.java index 19af15a2057..dfc5e4801d1 100644 --- a/core/src/test/java/com/linecorp/armeria/common/HttpHeadersBaseTest.java +++ b/core/src/test/java/com/linecorp/armeria/common/HttpHeadersBaseTest.java @@ -42,6 +42,7 @@ import org.junit.jupiter.api.Test; import com.google.common.collect.ImmutableList; +import com.google.common.testing.EqualsTester; import io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName; import io.netty.util.AsciiString; @@ -410,13 +411,9 @@ void headersWithSameNamesAndValuesShouldBeEquivalent() { headers2.add("name2", "value2"); headers2.add("name2", "value3"); - assertThat(headers2).isEqualTo(headers1); - assertThat(headers1).isEqualTo(headers2); - assertThat(headers1).isEqualTo(headers1); - assertThat(headers2).isEqualTo(headers2); - assertThat(headers2.hashCode()).isEqualTo(headers1.hashCode()); - assertThat(headers1.hashCode()).isEqualTo(headers1.hashCode()); - assertThat(headers2.hashCode()).isEqualTo(headers2.hashCode()); + new EqualsTester() + .addEqualityGroup(headers1, headers2) + .testEquals(); } @Test @@ -452,10 +449,10 @@ void headersWithDifferentNamesAndValuesShouldNotBeEquivalent() { h1.set("name1", "value1"); final HttpHeadersBase h2 = newEmptyHeaders(); h2.set("name2", "value2"); - assertThat(h1).isNotEqualTo(h2); - assertThat(h2).isNotEqualTo(h1); - assertThat(h1).isEqualTo(h1); - assertThat(h2).isEqualTo(h2); + new EqualsTester() + .addEqualityGroup(h1) + .addEqualityGroup(h2) + .testEquals(); } @Test diff --git a/core/src/test/java/com/linecorp/armeria/common/QueryParamsBaseTest.java b/core/src/test/java/com/linecorp/armeria/common/QueryParamsBaseTest.java index e165e734154..6ce5c25a05b 100644 --- a/core/src/test/java/com/linecorp/armeria/common/QueryParamsBaseTest.java +++ b/core/src/test/java/com/linecorp/armeria/common/QueryParamsBaseTest.java @@ -41,6 +41,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; +import com.google.common.testing.EqualsTester; class QueryParamsBaseTest { @@ -406,13 +407,9 @@ void paramsWithSameNamesAndValuesShouldBeEquivalent() { params2.add("name2", "value2"); params2.add("name2", "value3"); - assertThat(params2).isEqualTo(params1); - assertThat(params1).isEqualTo(params2); - assertThat(params1).isEqualTo(params1); - assertThat(params2).isEqualTo(params2); - assertThat(params2.hashCode()).isEqualTo(params1.hashCode()); - assertThat(params1.hashCode()).isEqualTo(params1.hashCode()); - assertThat(params2.hashCode()).isEqualTo(params2.hashCode()); + new EqualsTester() + .addEqualityGroup(params1, params2) + .testEquals(); } @Test @@ -448,10 +445,11 @@ void paramsWithDifferentNamesAndValuesShouldNotBeEquivalent() { p1.set("name1", "value1"); final QueryParamsBase p2 = newEmptyParams(); p2.set("name2", "value2"); - assertThat(p1).isNotEqualTo(p2); - assertThat(p2).isNotEqualTo(p1); - assertThat(p1).isEqualTo(p1); - assertThat(p2).isEqualTo(p2); + + new EqualsTester() + .addEqualityGroup(p1) + .addEqualityGroup(p2) + .testEquals(); } @Test diff --git a/core/src/test/java/com/linecorp/armeria/internal/common/ArmeriaHttp2HeadersTest.java b/core/src/test/java/com/linecorp/armeria/internal/common/ArmeriaHttp2HeadersTest.java index a0996bf74bc..58c2f6f1395 100644 --- a/core/src/test/java/com/linecorp/armeria/internal/common/ArmeriaHttp2HeadersTest.java +++ b/core/src/test/java/com/linecorp/armeria/internal/common/ArmeriaHttp2HeadersTest.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import com.google.common.collect.ImmutableList; +import com.google.common.testing.EqualsTester; import com.linecorp.armeria.common.ContentDisposition; import com.linecorp.armeria.common.HttpHeaderNames; @@ -367,13 +368,9 @@ void headersWithSameNamesAndValuesShouldBeEquivalent() { headers2.add("name2", "value2"); headers2.add("name2", "value3"); - assertThat(headers2).isEqualTo(headers1); - assertThat(headers1).isEqualTo(headers2); - assertThat(headers1).isEqualTo(headers1); - assertThat(headers2).isEqualTo(headers2); - assertThat(headers2.hashCode()).isEqualTo(headers1.hashCode()); - assertThat(headers1.hashCode()).isEqualTo(headers1.hashCode()); - assertThat(headers2.hashCode()).isEqualTo(headers2.hashCode()); + new EqualsTester() + .addEqualityGroup(headers1, headers2) + .testEquals(); } @Test @@ -411,8 +408,8 @@ void headersWithDifferentNamesAndValuesShouldNotBeEquivalent() { h2.set("name2", "value2"); assertThat(h1).isNotEqualTo(h2); assertThat(h2).isNotEqualTo(h1); - assertThat(h1).isEqualTo(h1); - assertThat(h2).isEqualTo(h2); + assertThat(h1.equals(h1)).isTrue(); + assertThat(h2.equals(h2)).isTrue(); } @Test diff --git a/core/src/test/java/com/linecorp/armeria/internal/common/ByteArrayBytesTest.java b/core/src/test/java/com/linecorp/armeria/internal/common/ByteArrayBytesTest.java index 76b6379117c..840429d5512 100644 --- a/core/src/test/java/com/linecorp/armeria/internal/common/ByteArrayBytesTest.java +++ b/core/src/test/java/com/linecorp/armeria/internal/common/ByteArrayBytesTest.java @@ -21,6 +21,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import com.google.common.testing.EqualsTester; + import com.linecorp.armeria.common.ByteBufAccessMode; import io.netty.buffer.ByteBuf; @@ -100,12 +102,13 @@ void equals() { final ByteBufBytes bufData = new ByteBufBytes(Unpooled.directBuffer().writeInt(0x01020304), true); - assertThat(a).isEqualTo(a); - assertThat(a).isEqualTo(b); + new EqualsTester() + .addEqualityGroup(a, b) + .addEqualityGroup(c) + .addEqualityGroup(d) + .addEqualityGroup(new Object()) + .testEquals(); assertThat(a.array()).isEqualTo(bufData.array()); - assertThat(a).isNotEqualTo(c); - assertThat(a).isNotEqualTo(d); - assertThat(a).isNotEqualTo(new Object()); bufData.close(); } diff --git a/core/src/test/java/com/linecorp/armeria/internal/common/ByteBufBytesTest.java b/core/src/test/java/com/linecorp/armeria/internal/common/ByteBufBytesTest.java index 02da9236c9d..3ba39e92b9c 100644 --- a/core/src/test/java/com/linecorp/armeria/internal/common/ByteBufBytesTest.java +++ b/core/src/test/java/com/linecorp/armeria/internal/common/ByteBufBytesTest.java @@ -21,6 +21,8 @@ import org.junit.jupiter.api.Test; +import com.google.common.testing.EqualsTester; + import com.linecorp.armeria.common.ByteBufAccessMode; import io.netty.buffer.ByteBuf; @@ -185,12 +187,13 @@ void equals() { final ByteBufBytes d = new ByteBufBytes(Unpooled.directBuffer().writeInt(0x04050607), true); final ByteArrayBytes arrayData = new ByteArrayBytes(new byte[] { 1, 2, 3, 4 }); - assertThat(a).isEqualTo(a); - assertThat(a).isEqualTo(b); + new EqualsTester() + .addEqualityGroup(a, b) + .addEqualityGroup(c) + .addEqualityGroup(d) + .addEqualityGroup(new Object()) + .testEquals(); assertThat(a.array()).isEqualTo(arrayData.array()); - assertThat(a).isNotEqualTo(c); - assertThat(a).isNotEqualTo(d); - assertThat(a).isNotEqualTo(new Object()); a.close(); b.close(); diff --git a/dependencies.toml b/dependencies.toml index 2d3a0021316..68c5e5961ac 100644 --- a/dependencies.toml +++ b/dependencies.toml @@ -2,114 +2,125 @@ akka = "2.6.20" akka-http-cors = "1.0.0" akka-grpc-runtime = "1.0.3" -apache-httpclient5 = "5.2.1" +apache-httpclient5 = "5.3" apache-httpclient4 = "4.5.14" asm = "9.6" -assertj = "3.24.2" +assertj = "3.25.2" awaitility = "4.2.0" blockhound = "1.0.8.RELEASE" bouncycastle = "1.70" brave = "6.0.0" -brotli4j = "1.12.0" +brotli4j = "1.15.0" bucket4j = "7.6.0" +# Don"t upgrade Caffeine to 3.x that requires Java 11. caffeine = "2.9.3" cglib = "3.3.0" -checkerframework = "2.5.5" +checkerframework = "2.5.6" checkstyle = "10.3.2" -controlplane = "1.0.41" -curator = "5.5.0" -dagger = "2.48.1" -dgs = "7.6.0" +controlplane = "1.0.42" +curator = "5.6.0" +dagger = "2.50" +dgs = "8.2.2" dropwizard1 = "1.3.29" -dropwizard2 = "2.1.8" -dropwizard-metrics = "4.2.21" -errorprone = "2.22.0" +dropwizard2 = "2.1.10" +dropwizard-metrics = "4.2.24" +errorprone = "2.24.1" errorprone-gradle-plugin = "3.1.0" eureka = "2.0.1" fastutil = "8.5.12" -finagle = "22.12.0" +finagle = "23.11.0" findbugs = "3.0.2" -futures-completable = "0.3.5" +futures-completable = "0.3.6" futures-extra = "4.3.3" -gax-grpc = "2.35.0" +gax-grpc = "2.40.0" +# Don"t upgrade graphql-java to 21.0 that requires Java 11 graphql-java = "20.4" -graphql-kotlin = "6.5.6" -grpc-java = "1.58.0" -grpc-kotlin = "1.4.0" -guava = "32.1.3-jre" +graphql-kotlin = "7.0.2" +grpc-java = "1.61.0" +grpc-kotlin = "1.4.1" +guava = "33.0.0-jre" hamcrest = "2.2" hbase = "1.2.6" hibernate-validator6 = "6.2.5.Final" hibernate-validator8 = "8.0.1.Final" j2objc = "2.8" -jackson = "2.15.3" +jackson = "2.16.1" jakarta-inject = "2.0.1" jakarta-validation = "3.0.2" jakarta-websocket = "2.1.1" -java-websocket = "1.5.4" +java-websocket = "1.5.5" javax-annotation = "1.3.2" javax-inject = "1" javax-jsr311 = "1.1.1" javax-validation = "2.0.1.Final" -jctools = "4.0.1" +jctools = "4.0.2" # Find the latest version of the major 10 https://central.sonatype.com/artifact/org.eclipse.jetty/jetty-server/ -jetty10 = "10.0.17" +jetty10 = "10.0.19" # Find the latest version of the major 10 https://central.sonatype.com/artifact/org.eclipse.jetty/apache-jstl/ -jetty10-jstl = "10.0.17" -jetty11 = "11.0.17" +jetty10-jstl = "10.0.19" +jetty11 = "11.0.19" jetty11-jstl = "11.0.0" jetty12 = "12.0.5" jetty93 = "9.3.30.v20211001" -jetty94 = "9.4.51.v20230217" +jetty94 = "9.4.52.v20230823" jetty-alpn-api = "1.1.3.v20160715" jkube = "1.15.0" jmh-core = "1.37" -jmh-gradle-plugin = "0.7.1" +jmh-gradle-plugin = "0.7.2" joor = "0.9.15" +# Don't upgrade json-unit to 3.0.0 that requires Java 17 json-unit = "2.38.0" -jsoup = "1.16.1" +jsoup = "1.17.2" junit4 = "4.13.2" -junit5 = "5.10.0" +junit5 = "5.10.1" +# Don't upgrade junit-pioneer to 2.x.x that requires Java 11 junit-pioneer = "1.9.1" jwt = "4.4.0" -kafka = "3.6.0" -kotlin = "1.9.0" +kafka = "3.6.1" +kotlin = "1.9.22" kotlin-coroutine = "1.7.3" -krotodc = "1.0.5" -ktlint-gradle-plugin = "11.5.1" +krotodc = "1.0.6" +ktlint-gradle-plugin = "12.1.0" kubernetes-client = "6.9.2" -logback12 = "1.2.11" -logback13 = "1.3.11" -logback14 = "1.4.11" -micrometer = "1.11.5" -micrometer-tracing = "1.1.6" +logback12 = "1.2.13" +logback13 = "1.3.14" +logback14 = "1.4.14" +micrometer = "1.12.2" +micrometer-tracing = "1.2.2" micrometer-docs-generator = "1.0.2" micrometer13 = "1.3.20" +# Don't uprade mockito to 5.x.x that requires Java 11 mockito = "4.11.0" monix = "3.4.1" munit = "0.7.29" -netty = "4.1.100.Final" -netty-incubator-transport-native-io_uring = "0.0.23.Final" +netty = "4.1.106.Final" +netty-incubator-transport-native-io_uring = "0.0.24.Final" nexus-publish = "1.3.0" -node-gradle-plugin = "5.0.0" +node-gradle-plugin = "7.0.1" okhttp2 = "2.7.5" # For testing okhttp3 = { strictly = "3.14.9" } # Not just for testing. Used in the Retrofit mudule. okhttp4 = "4.12.0" # For testing +# Don"t upgrade OpenSAML to 4.x that requires Java 11. opensaml = "3.4.6" osdetector = "1.7.3" # Used for kubernetes-chaos-tests picocli = "4.7.5" proguard = "7.3.1" prometheus = "0.16.0" -protobuf = "3.24.0" +# Ensure that we use the same Protobuf version as what gRPC depends on. +# See: https://github.com/grpc/grpc-java/blob/master/build.gradle +# (Switch to the right tag and look for "protobuf".) +# e.g. https://github.com/grpc/grpc-java/blob/v1.48.0/gradle/libs.versions.toml +protobuf = "3.25.1" protobuf-gradle-plugin = "0.8.19" protobuf-jackson = "2.2.0" reactive-grpc = "1.2.4" reactive-streams = "1.0.4" -reactor = "3.5.11" +reactor = "3.6.2" reactor-kotlin = "1.2.2" +# Upgrade once https://github.com/ronmamo/reflections/issues/279 is fixed. reflections = "0.9.11" -resilience4j = "2.1.0" +resilience4j = "2.2.0" resteasy = "5.0.7.Final" resteasy-jboss-logging = "3.4.3.Final" resteasy-jboss-logging-annotations = "2.2.1.Final" @@ -124,17 +135,17 @@ scala212 = "2.12.18" scala213 = "2.13.12" scala3 = "3.3.0" scalafmt-gradle-plugin = "1.16.2" -scalapb = "0.11.13" +scalapb = "0.11.15" scalapb-json = "0.12.1" shadow-gradle-plugin = "7.1.2" shibboleth-utilities = "7.5.2" snappy = "1.1.10.5" slf4j = "1.7.36" -slf4j2 = "2.0.7" +slf4j2 = "2.0.11" spring6 = "6.1.3" spring-boot2 = "2.7.18" spring-boot3 = "3.2.2" -testcontainers = "1.19.1" +testcontainers = "1.19.3" thrift09 = { strictly = "0.9.3-1" } thrift012 = { strictly = "0.12.0" } thrift013 = { strictly = "0.13.0" } @@ -143,10 +154,13 @@ thrift015 = { strictly = "0.15.0" } thrift016 = { strictly = "0.16.0" } thrift017 = { strictly = "0.17.0" } thrift018 = { strictly = "0.18.1" } -tomcat8 = "8.5.94" -tomcat9 = "9.0.82" -tomcat10 = "10.1.15" +tomcat8 = "8.5.98" +tomcat9 = "9.0.85" +tomcat10 = "10.1.18" xml-apis = "1.4.01" +# Ensure that we use the same ZooKeeper version as what Curator depends on. +# See: https://github.com/apache/curator/blob/master/pom.xml +# (Switch to the right tag to find out the right version.) zookeeper = "3.9.1" zookeeper-junit = "1.2" @@ -262,7 +276,6 @@ module = "com.github.vladimir-bukhtoyarov:bucket4j-core" version.ref = "bucket4j" javadocs = "https://javadoc.io/doc/com.github.vladimir-bukhtoyarov/bucket4j-core/7.6.0/" -# Don"t upgrade Caffeine to 3.x that requires Java 11. [libraries.caffeine] module = "com.github.ben-manes.caffeine:caffeine" version.ref = "caffeine" @@ -319,7 +332,6 @@ module = "com.google.dagger:dagger-producers" version.ref = "dagger" # DGS is used only for testing in it:dgs module. -# A minimum of java 17 is required for 6.x.x [libraries.dgs] module = "com.netflix.graphql.dgs:graphql-dgs-client" version.ref = "dgs" @@ -421,12 +433,10 @@ version.ref = "futures-extra" module = "com.google.api:gax-grpc" version.ref = "gax-grpc" -# graphql-java requires java11 from 21.0 [libraries.graphql-java] module = "com.graphql-java:graphql-java" version.ref = "graphql-java" -# graphql-kotlin 7.x.x requires java 11 [libraries.graphql-kotlin-client] module = "com.expediagroup:graphql-kotlin-client" version.ref = "graphql-kotlin" @@ -711,7 +721,6 @@ version.ref = "jetty-alpn-api" module = "org.jooq:joor" version.ref = "joor" -# json-unit 3.0.0 requires java17 [libraries.json-unit] module = "net.javacrumbs.json-unit:json-unit" version.ref = "json-unit" @@ -744,7 +753,6 @@ module = "org.junit.platform:junit-platform-commons" [libraries.junit5-platform-launcher] module = "org.junit.platform:junit-platform-launcher" -# requires a minimum of java 11 for 2.x.x [libraries.junit-pioneer] module = "org.junit-pioneer:junit-pioneer" version.ref = "junit-pioneer" @@ -810,7 +818,6 @@ module = "io.fabric8:kubernetes-junit-jupiter" version.ref = "kubernetes-client" exclusions = ["io.fabric8:kubernetes-httpclient-okhttp", "org.slf4j:slf4j-api"] -# Don't upgrade Logback 1.4.0 which requires Java 11 # TODO(ikhoon): Upgrade Logback to 1.3.0 when Spring Boot 2 supports it. [libraries.logback12] module = "ch.qos.logback:logback-classic" @@ -869,7 +876,6 @@ module = "io.micrometer:micrometer-spring-legacy" version.ref = "micrometer13" exclusions = ["org.springframework:spring-web", "org.springframework:spring-webmvc"] -# mockito 5.x.x requires java 11 [libraries.mockito] module = "org.mockito:mockito-core" version.ref = "mockito" @@ -933,10 +939,6 @@ module = "io.prometheus:simpleclient_common" version.ref = "prometheus" javadocs = "https://prometheus.github.io/client_java/" -# Ensure that we use the same Protobuf version as what gRPC depends on. -# See: https://github.com/grpc/grpc-java/blob/master/build.gradle -# (Switch to the right tag and look for "protobuf".) -# e.g. https://github.com/grpc/grpc-java/blob/v1.48.0/gradle/libs.versions.toml [libraries.protobuf-java] module = "com.google.protobuf:protobuf-java" version.ref = "protobuf" @@ -957,7 +959,6 @@ version.ref = "protobuf-jackson" exclusions = "javax.annotation:javax.annotation-api" javadocs = "https://developers.curioswitch.org/apidocs/java/" -# Reactor 3.5 should be updated with Spring Boot 3 [libraries.reactor-core] module = "io.projectreactor:reactor-core" version.ref = "reactor" @@ -1018,7 +1019,6 @@ version.ref = "shibboleth-utilities" module = "org.xerial.snappy:snappy-java" version.ref = "snappy" -# Don"t upgrade OpenSAML to 4.x that requires Java 11. [libraries.opensaml-core] module = "org.opensaml:opensaml-core" version.ref = "opensaml" @@ -1074,7 +1074,6 @@ module = "org.reactivestreams:reactive-streams-tck" version.ref = "reactive-streams" exclusions = "org.yaml:snakeyaml" -# Upgrade once https://github.com/ronmamo/reflections/issues/279 is fixed. [libraries.reflections] module = "org.reflections:reflections" version.ref = "reflections" @@ -1349,9 +1348,6 @@ version.ref = "java-websocket" module = "xml-apis:xml-apis" version.ref = "xml-apis" -# Ensure that we use the same ZooKeeper version as what Curator depends on. -# See: https://github.com/apache/curator/blob/master/pom.xml -# (Switch to the right tag to find out the right version.) [libraries.zookeeper] module = "org.apache.zookeeper:zookeeper" version.ref = "zookeeper" diff --git a/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/ContextAwareService.kt b/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/ContextAwareService.kt index add42393085..8f2310c0d33 100644 --- a/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/ContextAwareService.kt +++ b/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/ContextAwareService.kt @@ -10,10 +10,12 @@ import org.slf4j.LoggerFactory import java.util.concurrent.Executors class ContextAwareService { - @Get("/foo") @ProducesJson - suspend fun foo(@Param name: String, @Param id: Int): FooResponse { + suspend fun foo( + @Param name: String, + @Param id: Int, + ): FooResponse { log.info("Hello $name") // Make sure that current thread is request context aware ServiceRequestContext.current() diff --git a/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/DecoratingService.kt b/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/DecoratingService.kt index df657b2b630..cedd6fa1c42 100644 --- a/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/DecoratingService.kt +++ b/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/DecoratingService.kt @@ -14,7 +14,6 @@ import kotlin.coroutines.coroutineContext @CoroutineNameDecorator(name = "default") class DecoratingService { - @Get("/foo") suspend fun foo(): HttpResponse { log.info("My name is ${coroutineContext[CoroutineName]?.name}") diff --git a/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/Main.kt b/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/Main.kt index b25f1c4f944..c347545eb15 100644 --- a/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/Main.kt +++ b/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/Main.kt @@ -34,7 +34,7 @@ fun configureServices(sb: ServerBuilder) { .decorator( CoroutineContextService.newDecorator { ctx -> CoroutineName(ctx.config().defaultServiceNaming().serviceName(ctx) ?: "name") - } + }, ) .applyCommonDecorator() .build(ContextAwareService()) @@ -59,9 +59,9 @@ private fun AnnotatedServiceBindingBuilder.applyCommonDecorator(): AnnotatedServ LogWriter.builder() .requestLogLevel(LogLevel.INFO) .successfulResponseLogLevel(LogLevel.INFO) - .build() + .build(), ) - .newDecorator() + .newDecorator(), ) } diff --git a/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/MarkdownDescriptionService.kt b/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/MarkdownDescriptionService.kt index 72f9cdeb98e..2301505a4a9 100644 --- a/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/MarkdownDescriptionService.kt +++ b/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/MarkdownDescriptionService.kt @@ -52,7 +52,7 @@ class MarkdownDescriptionService { .start(); ``` """, - markup = Markup.MARKDOWN + markup = Markup.MARKDOWN, ) @Get("/markdown") fun markdown( @@ -62,12 +62,12 @@ class MarkdownDescriptionService { @Param param2: String, @Description("param3 description") @Param - param3: MarkdownEnumParam + param3: MarkdownEnumParam, ): MarkdownDescriptionResult { return MarkdownDescriptionResult( result1 = param1, result2 = param2, - result3 = param3.name + result3 = param3.name, ) } @@ -77,7 +77,7 @@ class MarkdownDescriptionService { ### Structs description subtitle > Support blockquotes """, - markup = Markup.MARKDOWN + markup = Markup.MARKDOWN, ) data class MarkdownDescriptionResult( @Description(value = "result1 description (default)", markup = Markup.MARKDOWN) @@ -85,7 +85,7 @@ class MarkdownDescriptionService { @Description(value = "`result2` **description** (use markdown)", markup = Markup.MARKDOWN) val result2: String, @Description(value = "`result3` see https://armeria.dev/ (add links)", markup = Markup.MARKDOWN) - val result3: String + val result3: String, ) @Description("MarkdownEnumParam") @@ -93,6 +93,6 @@ class MarkdownDescriptionService { @Description(value = "Description for `ENUM_1`", markup = Markup.MARKDOWN) ENUM_1, ENUM_2, - ENUM_3 + ENUM_3, } } diff --git a/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/MermaidDescriptionService.kt b/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/MermaidDescriptionService.kt index d47c66313e3..61305b2662c 100644 --- a/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/MermaidDescriptionService.kt +++ b/examples/annotated-http-service-kotlin/src/main/kotlin/example/armeria/server/annotated/kotlin/MermaidDescriptionService.kt @@ -35,11 +35,11 @@ class MermaidDescriptionService { Task in sec :2014-01-12 , 12d another task : 24d """, - markup = Markup.MERMAID + markup = Markup.MERMAID, ) @Get("/mermaid") fun mermaid( - @Param param1: String + @Param param1: String, ): HttpResponse { return HttpResponse.of(200) } diff --git a/examples/annotated-http-service-kotlin/src/test/kotlin/example/armeria/server/annotated/kotlin/KotlinAnnotatedServiceTest.kt b/examples/annotated-http-service-kotlin/src/test/kotlin/example/armeria/server/annotated/kotlin/KotlinAnnotatedServiceTest.kt index 8ec6ca143e4..145ebdd690b 100644 --- a/examples/annotated-http-service-kotlin/src/test/kotlin/example/armeria/server/annotated/kotlin/KotlinAnnotatedServiceTest.kt +++ b/examples/annotated-http-service-kotlin/src/test/kotlin/example/armeria/server/annotated/kotlin/KotlinAnnotatedServiceTest.kt @@ -10,16 +10,15 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.RegisterExtension class KotlinAnnotatedServiceTest { - companion object { - @JvmField @RegisterExtension - val server: ServerExtension = object : ServerExtension() { - override fun configure(sb: ServerBuilder) { - configureServices(sb) + val server: ServerExtension = + object : ServerExtension() { + override fun configure(sb: ServerBuilder) { + configureServices(sb) + } } - } fun client(): WebClient { return WebClient.of(server.httpUri()) diff --git a/examples/context-propagation/kotlin/src/main/kotlin/example/armeria/contextpropagation/kotlin/Main.kt b/examples/context-propagation/kotlin/src/main/kotlin/example/armeria/contextpropagation/kotlin/Main.kt index e07bc20d865..d76072cc405 100644 --- a/examples/context-propagation/kotlin/src/main/kotlin/example/armeria/contextpropagation/kotlin/Main.kt +++ b/examples/context-propagation/kotlin/src/main/kotlin/example/armeria/contextpropagation/kotlin/Main.kt @@ -22,24 +22,26 @@ import com.linecorp.armeria.common.HttpStatus import com.linecorp.armeria.server.Server fun main() { - val backend = Server.builder() - .service("/square/{num}") { ctx, _ -> - val num = ctx.pathParam("num")?.toLong() - if (num != null) { - HttpResponse.of((num * num).toString()) - } else { - HttpResponse.of(HttpStatus.BAD_REQUEST) + val backend = + Server.builder() + .service("/square/{num}") { ctx, _ -> + val num = ctx.pathParam("num")?.toLong() + if (num != null) { + HttpResponse.of((num * num).toString()) + } else { + HttpResponse.of(HttpStatus.BAD_REQUEST) + } } - } - .http(8081) - .build() + .http(8081) + .build() val backendClient = WebClient.of("http://localhost:8081") - val frontend = Server.builder() - .http(8080) - .serviceUnder("/", MainService(backendClient)) - .build() + val frontend = + Server.builder() + .http(8080) + .serviceUnder("/", MainService(backendClient)) + .build() backend.closeOnJvmShutdown() frontend.closeOnJvmShutdown() diff --git a/examples/context-propagation/kotlin/src/main/kotlin/example/armeria/contextpropagation/kotlin/MainService.kt b/examples/context-propagation/kotlin/src/main/kotlin/example/armeria/contextpropagation/kotlin/MainService.kt index af8139c354b..a3fb3b496c7 100644 --- a/examples/context-propagation/kotlin/src/main/kotlin/example/armeria/contextpropagation/kotlin/MainService.kt +++ b/examples/context-propagation/kotlin/src/main/kotlin/example/armeria/contextpropagation/kotlin/MainService.kt @@ -25,10 +25,6 @@ import com.linecorp.armeria.common.HttpRequest import com.linecorp.armeria.common.HttpResponse import com.linecorp.armeria.server.HttpService import com.linecorp.armeria.server.ServiceRequestContext -import java.time.Duration -import java.util.concurrent.CompletableFuture -import java.util.function.Supplier -import java.util.stream.Collectors import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.asCoroutineDispatcher @@ -38,68 +34,80 @@ import kotlinx.coroutines.future.asDeferred import kotlinx.coroutines.future.await import kotlinx.coroutines.future.future import kotlinx.coroutines.withContext +import java.time.Duration +import java.util.concurrent.CompletableFuture +import java.util.function.Supplier +import java.util.stream.Collectors class MainService(private val backendClient: WebClient) : HttpService { - override fun serve(ctx: ServiceRequestContext, req: HttpRequest): HttpResponse { - val response = GlobalScope.future(ctx.eventLoop().asCoroutineDispatcher()) { - val numsFromRequest = async { fetchFromRequest(ctx, req) } - val numsFromDb = async { fetchFromFakeDb(ctx) } - val nums = awaitAll(numsFromRequest, numsFromDb).flatten() - - // The context is kept after resume. - require(ServiceRequestContext.current() === ctx) - require(ctx.eventLoop().inEventLoop()) - - val backendResponses = - awaitAll( - *nums.map { num -> - // The context is mounted in a thread-local, meaning it is available to all logic such - // as tracing. - require(ServiceRequestContext.current() === ctx) - require(ctx.eventLoop().inEventLoop()) - - backendClient.get("/square/$num").aggregate().asDeferred() - }.toTypedArray() - ).toList() - - // The context is mounted in a thread-local, meaning it is available to all logic such as tracing. - require(ServiceRequestContext.current() === ctx) - require(ctx.eventLoop().inEventLoop()) - - HttpResponse.of( - backendResponses.stream() - .map(AggregatedHttpResponse::contentUtf8) - .collect(Collectors.joining("\n")) - ) - } + override fun serve( + ctx: ServiceRequestContext, + req: HttpRequest, + ): HttpResponse { + val response = + GlobalScope.future(ctx.eventLoop().asCoroutineDispatcher()) { + val numsFromRequest = async { fetchFromRequest(ctx, req) } + val numsFromDb = async { fetchFromFakeDb(ctx) } + val nums = awaitAll(numsFromRequest, numsFromDb).flatten() + + // The context is kept after resume. + require(ServiceRequestContext.current() === ctx) + require(ctx.eventLoop().inEventLoop()) + + val backendResponses = + awaitAll( + *nums.map { num -> + // The context is mounted in a thread-local, meaning it is available to all logic such + // as tracing. + require(ServiceRequestContext.current() === ctx) + require(ctx.eventLoop().inEventLoop()) + + backendClient.get("/square/$num").aggregate().asDeferred() + }.toTypedArray(), + ).toList() + + // The context is mounted in a thread-local, meaning it is available to all logic such as tracing. + require(ServiceRequestContext.current() === ctx) + require(ctx.eventLoop().inEventLoop()) + + HttpResponse.of( + backendResponses.stream() + .map(AggregatedHttpResponse::contentUtf8) + .collect(Collectors.joining("\n")), + ) + } return HttpResponse.of(response) } - private suspend fun fetchFromRequest(ctx: ServiceRequestContext, req: HttpRequest): List { + private suspend fun fetchFromRequest( + ctx: ServiceRequestContext, + req: HttpRequest, + ): List { // Switch to the default dispatcher. - val nums = withContext(Dispatchers.Default) { - // The thread is switched. - require(!ctx.eventLoop().inEventLoop()) - // The context is still mounted in a thread-local. - require(ServiceRequestContext.current() === ctx) - - val aggregatedHttpRequest = req.aggregate().await() - - // The context is kept after resume. - require(ServiceRequestContext.current() === ctx) - require(!ctx.eventLoop().inEventLoop()) - - val nums = mutableListOf() - for ( - token in Iterables.concat( - NUM_SPLITTER.split(aggregatedHttpRequest.path().substring(1)), - NUM_SPLITTER.split(aggregatedHttpRequest.contentUtf8()) - ) - ) { - nums.add(token.toLong()) + val nums = + withContext(Dispatchers.Default) { + // The thread is switched. + require(!ctx.eventLoop().inEventLoop()) + // The context is still mounted in a thread-local. + require(ServiceRequestContext.current() === ctx) + + val aggregatedHttpRequest = req.aggregate().await() + + // The context is kept after resume. + require(ServiceRequestContext.current() === ctx) + require(!ctx.eventLoop().inEventLoop()) + + val nums = mutableListOf() + for ( + token in Iterables.concat( + NUM_SPLITTER.split(aggregatedHttpRequest.path().substring(1)), + NUM_SPLITTER.split(aggregatedHttpRequest.contentUtf8()), + ) + ) { + nums.add(token.toLong()) + } + nums } - nums - } return nums } @@ -123,7 +131,7 @@ class MainService(private val backendClient: WebClient) : HttpService { // Always run blocking logic on the blocking task executor. By using // ServiceRequestContext.blockingTaskExecutor, you also ensure the context is mounted inside the // logic (e.g., your DB call will be traced!). - ctx.blockingTaskExecutor() + ctx.blockingTaskExecutor(), ).await() } diff --git a/examples/graphql-kotlin/src/main/kotlin/example/armeria/server/graphql/kotlin/Main.kt b/examples/graphql-kotlin/src/main/kotlin/example/armeria/server/graphql/kotlin/Main.kt index cd487c31e84..6d64430cd76 100644 --- a/examples/graphql-kotlin/src/main/kotlin/example/armeria/server/graphql/kotlin/Main.kt +++ b/examples/graphql-kotlin/src/main/kotlin/example/armeria/server/graphql/kotlin/Main.kt @@ -32,21 +32,21 @@ fun configureService(sb: ServerBuilder) { GraphqlService.builder().schema( toSchema( config = SchemaGeneratorConfig(listOf("example.armeria.server.graphql.kotlin")), - queries = listOf(TopLevelObject(UserQuery())) - ) - ).build() + queries = listOf(TopLevelObject(UserQuery())), + ), + ).build(), ) } data class User(val id: Int, val name: String) class UserQuery { - - private val data = mapOf( - 1 to User(1, "hero"), - 2 to User(2, "human"), - 3 to User(3, "droid") - ) + private val data = + mapOf( + 1 to User(1, "hero"), + 2 to User(2, "human"), + 3 to User(3, "droid"), + ) /** * Retrieves a [User] associated with the specified ID. This method is automatically mapped by diff --git a/examples/graphql-kotlin/src/test/kotlin/example/armeria/server/graphql/kotlin/GraphqlArmeriaClient.kt b/examples/graphql-kotlin/src/test/kotlin/example/armeria/server/graphql/kotlin/GraphqlArmeriaClient.kt index 7362c371cbd..934f98ce86d 100644 --- a/examples/graphql-kotlin/src/test/kotlin/example/armeria/server/graphql/kotlin/GraphqlArmeriaClient.kt +++ b/examples/graphql-kotlin/src/test/kotlin/example/armeria/server/graphql/kotlin/GraphqlArmeriaClient.kt @@ -3,6 +3,7 @@ package example.armeria.server.graphql.kotlin import com.expediagroup.graphql.client.GraphQLClient import com.expediagroup.graphql.client.serializer.GraphQLClientSerializer import com.expediagroup.graphql.client.serializer.defaultGraphQLSerializer +import com.expediagroup.graphql.client.types.AutomaticPersistedQueriesSettings import com.expediagroup.graphql.client.types.GraphQLClientRequest import com.expediagroup.graphql.client.types.GraphQLClientResponse import com.linecorp.armeria.client.WebClient @@ -16,35 +17,39 @@ import java.net.URI class GraphqlArmeriaClient( private val uri: URI, private val client: WebClient = WebClient.of(), - private val serializer: GraphQLClientSerializer = defaultGraphQLSerializer() + private val serializer: GraphQLClientSerializer = defaultGraphQLSerializer(), + // TODO(ikhoon): support Automatic Persisted Queries + // See: https://github.com/ExpediaGroup/graphql-kotlin/issues/1640 + override val automaticPersistedQueriesSettings: AutomaticPersistedQueriesSettings = AutomaticPersistedQueriesSettings(), ) : GraphQLClient { - override suspend fun execute( request: GraphQLClientRequest, - requestCustomizer: HttpRequestBuilder.() -> Unit + requestCustomizer: HttpRequestBuilder.() -> Unit, ): GraphQLClientResponse { - val response = client.execute( - HttpRequest.builder() - .apply(requestCustomizer) - .post(uri.toString()) - .content(MediaType.JSON_UTF_8, serializer.serialize(request)) - .build() - ).aggregate().await() + val response = + client.execute( + HttpRequest.builder() + .apply(requestCustomizer) + .post(uri.toString()) + .content(MediaType.JSON_UTF_8, serializer.serialize(request)) + .build(), + ).aggregate().await() return serializer.deserialize(response.contentUtf8(), request.responseType()) } override suspend fun execute( requests: List>, - requestCustomizer: HttpRequestBuilder.() -> Unit + requestCustomizer: HttpRequestBuilder.() -> Unit, ): List> { - val response = client.execute( - HttpRequest.builder() - .apply(requestCustomizer) - .path(uri.toString()) - .method(HttpMethod.POST) - .content(MediaType.JSON_UTF_8, serializer.serialize(requests)) - .build() - ).aggregate().await() + val response = + client.execute( + HttpRequest.builder() + .apply(requestCustomizer) + .path(uri.toString()) + .method(HttpMethod.POST) + .content(MediaType.JSON_UTF_8, serializer.serialize(requests)) + .build(), + ).aggregate().await() return serializer.deserialize(response.contentUtf8(), requests.map { it.responseType() }) } } diff --git a/examples/graphql-kotlin/src/test/kotlin/example/armeria/server/graphql/kotlin/GraphqlServiceTest.kt b/examples/graphql-kotlin/src/test/kotlin/example/armeria/server/graphql/kotlin/GraphqlServiceTest.kt index f474be4a6d2..cae9a2b8b95 100644 --- a/examples/graphql-kotlin/src/test/kotlin/example/armeria/server/graphql/kotlin/GraphqlServiceTest.kt +++ b/examples/graphql-kotlin/src/test/kotlin/example/armeria/server/graphql/kotlin/GraphqlServiceTest.kt @@ -12,22 +12,21 @@ import org.junit.jupiter.params.provider.CsvSource import kotlin.reflect.KClass class GraphqlServiceTest { - companion object { - @JvmField @RegisterExtension - val server: ServerExtension = object : ServerExtension() { - @Throws(Exception::class) - override fun configure(sb: ServerBuilder) { - configureService(sb) + val server: ServerExtension = + object : ServerExtension() { + @Throws(Exception::class) + override fun configure(sb: ServerBuilder) { + configureService(sb) + } } - } private fun client(): GraphqlArmeriaClient { return GraphqlArmeriaClient( uri = server.httpUri().resolve("/graphql"), - serializer = GraphQLClientJacksonSerializer() + serializer = GraphQLClientJacksonSerializer(), ) } } @@ -36,9 +35,12 @@ class GraphqlServiceTest { @CsvSource( "1,hero", "2,human", - "3,droid" + "3,droid", ) - fun testUserDataFetch(id: String, expected: String) { + fun testUserDataFetch( + id: String, + expected: String, + ) { runBlocking { val response = client().execute(UserNameQuery(id)) assertThat(response.data?.userById?.name).isEqualTo(expected) @@ -50,6 +52,7 @@ class GraphqlServiceTest { class UserNameQuery(id: String) : GraphQLClientRequest { override val query: String = "query {userById(id: $id) {name}}" override val operationName: String? = null + override fun responseType(): KClass = UserNameResult::class } } diff --git a/examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/HelloServiceImpl.kt b/examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/HelloServiceImpl.kt index aa59e494062..109b558535e 100644 --- a/examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/HelloServiceImpl.kt +++ b/examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/HelloServiceImpl.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.toList import kotlinx.coroutines.withContext class HelloServiceImpl : HelloServiceGrpcKt.HelloServiceCoroutineImplBase() { - /** * Sends a [HelloReply] immediately when receiving a request. */ @@ -38,15 +37,18 @@ class HelloServiceImpl : HelloServiceGrpcKt.HelloServiceCoroutineImplBase() { * * @see [Blocking service implementation](https://armeria.dev/docs/server-grpc#blocking-service-implementation) */ - override suspend fun blockingHello(request: HelloRequest): HelloReply = withArmeriaBlockingContext { - try { // Simulate a blocking API call. - Thread.sleep(3000) - } catch (ignored: Exception) { // Do nothing. + override suspend fun blockingHello(request: HelloRequest): HelloReply = + withArmeriaBlockingContext { + try { + // Simulate a blocking API call. + Thread.sleep(3000) + } catch (ignored: Exception) { + // Do nothing. + } + // Make sure that current thread is request context aware + ServiceRequestContext.current() + buildReply(toMessage(request.name)) } - // Make sure that current thread is request context aware - ServiceRequestContext.current() - buildReply(toMessage(request.name)) - } /** * Sends 5 [HelloReply] responses when receiving a request. @@ -97,8 +99,7 @@ class HelloServiceImpl : HelloServiceGrpcKt.HelloServiceCoroutineImplBase() { suspend fun withArmeriaBlockingContext(block: suspend CoroutineScope.() -> T): T = withContext(ServiceRequestContext.current().blockingTaskExecutor().asCoroutineDispatcher(), block) - private fun buildReply(message: String): HelloReply = - HelloReply.newBuilder().setMessage(message).build() + private fun buildReply(message: String): HelloReply = HelloReply.newBuilder().setMessage(message).build() private fun toMessage(message: String): String = "Hello, $message!" } diff --git a/examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/Main.kt b/examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/Main.kt index 519f2d39f9e..add471edf2e 100644 --- a/examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/Main.kt +++ b/examples/grpc-kotlin/src/main/kotlin/example/armeria/grpc/kotlin/Main.kt @@ -11,7 +11,6 @@ import io.grpc.reflection.v1alpha.ServerReflectionGrpc import org.slf4j.LoggerFactory object Main { - @JvmStatic fun main(args: Array) { val server = newServer(8080, 8443) @@ -21,30 +20,36 @@ object Main { server.start().join() server.activePort()?.let { val localAddress = it.localAddress() - val isLocalAddress = localAddress.address.isAnyLocalAddress || - localAddress.address.isLoopbackAddress + val isLocalAddress = + localAddress.address.isAnyLocalAddress || + localAddress.address.isLoopbackAddress logger.info( "Server has been started. Serving DocService at http://{}:{}/docs", if (isLocalAddress) "127.0.0.1" else localAddress.hostString, - localAddress.port + localAddress.port, ) } } private val logger = LoggerFactory.getLogger(Main::class.java) - fun newServer(httpPort: Int, httpsPort: Int, useBlockingTaskExecutor: Boolean = false): Server { + fun newServer( + httpPort: Int, + httpsPort: Int, + useBlockingTaskExecutor: Boolean = false, + ): Server { val exampleRequest: HelloRequest = HelloRequest.newBuilder().setName("Armeria").build() - val grpcService = GrpcService.builder() - .addService(HelloServiceImpl()) - // See https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md - .addService(ProtoReflectionService.newInstance()) - .supportedSerializationFormats(GrpcSerializationFormats.values()) - .enableUnframedRequests(true) - // You can set useBlockingTaskExecutor(true) in order to execute all gRPC - // methods in the blockingTaskExecutor thread pool. - .useBlockingTaskExecutor(useBlockingTaskExecutor) - .build() + val grpcService = + GrpcService.builder() + .addService(HelloServiceImpl()) + // See https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md + .addService(ProtoReflectionService.newInstance()) + .supportedSerializationFormats(GrpcSerializationFormats.values()) + .enableUnframedRequests(true) + // You can set useBlockingTaskExecutor(true) in order to execute all gRPC + // methods in the blockingTaskExecutor thread pool. + .useBlockingTaskExecutor(useBlockingTaskExecutor) + .build() return Server.builder() .http(httpPort) .https(httpsPort) @@ -57,24 +62,24 @@ object Main { .exampleRequests( HelloServiceGrpc.SERVICE_NAME, "Hello", - exampleRequest + exampleRequest, ) .exampleRequests( HelloServiceGrpc.SERVICE_NAME, "LazyHello", - exampleRequest + exampleRequest, ) .exampleRequests( HelloServiceGrpc.SERVICE_NAME, "BlockingHello", - exampleRequest + exampleRequest, ) .exclude( DocServiceFilter.ofServiceName( - ServerReflectionGrpc.SERVICE_NAME - ) + ServerReflectionGrpc.SERVICE_NAME, + ), ) - .build() + .build(), ) .build() } diff --git a/examples/grpc-kotlin/src/test/kotlin/example/armeria/grpc/kotlin/HelloServiceTest.kt b/examples/grpc-kotlin/src/test/kotlin/example/armeria/grpc/kotlin/HelloServiceTest.kt index f109ce0cf51..b0a9cd54605 100644 --- a/examples/grpc-kotlin/src/test/kotlin/example/armeria/grpc/kotlin/HelloServiceTest.kt +++ b/examples/grpc-kotlin/src/test/kotlin/example/armeria/grpc/kotlin/HelloServiceTest.kt @@ -19,7 +19,6 @@ import org.junit.jupiter.params.provider.MethodSource import java.util.concurrent.TimeUnit class HelloServiceTest { - @ParameterizedTest @MethodSource("uris") fun reply(uri: String) { @@ -102,7 +101,6 @@ class HelloServiceTest { } companion object { - private lateinit var server: Server private lateinit var blockingServer: Server private lateinit var helloService: HelloServiceCoroutineStub @@ -126,8 +124,9 @@ class HelloServiceTest { } @JvmStatic - fun uris() = listOf(protoUri(), jsonUri(), blockingProtoUri(), blockingJsonUri()) - .map { Arguments.of(it) } + fun uris() = + listOf(protoUri(), jsonUri(), blockingProtoUri(), blockingJsonUri()) + .map { Arguments.of(it) } private fun protoUri(): String { return "gproto+http://127.0.0.1:" + server.activeLocalPort() + '/' diff --git a/examples/grpc-krotodc/src/main/kotlin/example/armeria/grpc/krotodc/HelloServiceImpl.kt b/examples/grpc-krotodc/src/main/kotlin/example/armeria/grpc/krotodc/HelloServiceImpl.kt index 4cf0441d9e4..20fa3918e0e 100644 --- a/examples/grpc-krotodc/src/main/kotlin/example/armeria/grpc/krotodc/HelloServiceImpl.kt +++ b/examples/grpc-krotodc/src/main/kotlin/example/armeria/grpc/krotodc/HelloServiceImpl.kt @@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.toList import kotlinx.coroutines.withContext class HelloServiceImpl : HelloServiceGrpcKroto.HelloServiceCoroutineImplBase() { - /** * Sends a [HelloReply] immediately when receiving a request. */ @@ -38,15 +37,18 @@ class HelloServiceImpl : HelloServiceGrpcKroto.HelloServiceCoroutineImplBase() { * * @see [Blocking service implementation](https://armeria.dev/docs/server-grpc#blocking-service-implementation) */ - override suspend fun blockingHello(request: HelloRequest): HelloReply = withArmeriaBlockingContext { - try { // Simulate a blocking API call. - Thread.sleep(3000) - } catch (ignored: Exception) { // Do nothing. + override suspend fun blockingHello(request: HelloRequest): HelloReply = + withArmeriaBlockingContext { + try { + // Simulate a blocking API call. + Thread.sleep(3000) + } catch (ignored: Exception) { + // Do nothing. + } + // Make sure that current thread is request context aware + ServiceRequestContext.current() + buildReply(toMessage(request.name)) } - // Make sure that current thread is request context aware - ServiceRequestContext.current() - buildReply(toMessage(request.name)) - } /** * Sends 5 [HelloReply] responses when receiving a request. diff --git a/examples/grpc-krotodc/src/main/kotlin/example/armeria/grpc/krotodc/Main.kt b/examples/grpc-krotodc/src/main/kotlin/example/armeria/grpc/krotodc/Main.kt index b131d948f4e..c6c64bfe1b2 100644 --- a/examples/grpc-krotodc/src/main/kotlin/example/armeria/grpc/krotodc/Main.kt +++ b/examples/grpc-krotodc/src/main/kotlin/example/armeria/grpc/krotodc/Main.kt @@ -11,7 +11,6 @@ import io.grpc.reflection.v1alpha.ServerReflectionGrpc import org.slf4j.LoggerFactory object Main { - @JvmStatic fun main(args: Array) { val server = newServer(8080, 8443) @@ -21,30 +20,36 @@ object Main { server.start().join() server.activePort()?.let { val localAddress = it.localAddress() - val isLocalAddress = localAddress.address.isAnyLocalAddress || - localAddress.address.isLoopbackAddress + val isLocalAddress = + localAddress.address.isAnyLocalAddress || + localAddress.address.isLoopbackAddress logger.info( "Server has been started. Serving DocService at http://{}:{}/docs", if (isLocalAddress) "127.0.0.1" else localAddress.hostString, - localAddress.port + localAddress.port, ) } } private val logger = LoggerFactory.getLogger(Main::class.java) - fun newServer(httpPort: Int, httpsPort: Int, useBlockingTaskExecutor: Boolean = false): Server { + fun newServer( + httpPort: Int, + httpsPort: Int, + useBlockingTaskExecutor: Boolean = false, + ): Server { val exampleRequest: HelloRequest = HelloRequest.newBuilder().setName("Armeria").build() - val grpcService = GrpcService.builder() - .addService(HelloServiceImpl()) - // See https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md - .addService(ProtoReflectionService.newInstance()) - .supportedSerializationFormats(GrpcSerializationFormats.values()) - .enableUnframedRequests(true) - // You can set useBlockingTaskExecutor(true) in order to execute all gRPC - // methods in the blockingTaskExecutor thread pool. - .useBlockingTaskExecutor(useBlockingTaskExecutor) - .build() + val grpcService = + GrpcService.builder() + .addService(HelloServiceImpl()) + // See https://github.com/grpc/grpc-java/blob/master/documentation/server-reflection-tutorial.md + .addService(ProtoReflectionService.newInstance()) + .supportedSerializationFormats(GrpcSerializationFormats.values()) + .enableUnframedRequests(true) + // You can set useBlockingTaskExecutor(true) in order to execute all gRPC + // methods in the blockingTaskExecutor thread pool. + .useBlockingTaskExecutor(useBlockingTaskExecutor) + .build() return Server.builder() .http(httpPort) .https(httpsPort) @@ -57,24 +62,24 @@ object Main { .exampleRequests( HelloServiceGrpc.SERVICE_NAME, "Hello", - exampleRequest + exampleRequest, ) .exampleRequests( HelloServiceGrpc.SERVICE_NAME, "LazyHello", - exampleRequest + exampleRequest, ) .exampleRequests( HelloServiceGrpc.SERVICE_NAME, "BlockingHello", - exampleRequest + exampleRequest, ) .exclude( DocServiceFilter.ofServiceName( - ServerReflectionGrpc.SERVICE_NAME - ) + ServerReflectionGrpc.SERVICE_NAME, + ), ) - .build() + .build(), ) .build() } diff --git a/examples/grpc-krotodc/src/test/kotlin/HelloServiceTest.kt b/examples/grpc-krotodc/src/test/kotlin/HelloServiceTest.kt index bd1e00bfeae..2464273e27b 100644 --- a/examples/grpc-krotodc/src/test/kotlin/HelloServiceTest.kt +++ b/examples/grpc-krotodc/src/test/kotlin/HelloServiceTest.kt @@ -17,7 +17,6 @@ import org.junit.jupiter.params.provider.MethodSource import java.util.concurrent.TimeUnit class HelloServiceTest { - @ParameterizedTest @MethodSource("uris") fun reply(uri: String) { @@ -100,7 +99,6 @@ class HelloServiceTest { } companion object { - private lateinit var server: Server private lateinit var blockingServer: Server private lateinit var helloService: HelloServiceCoroutineStub @@ -124,8 +122,9 @@ class HelloServiceTest { } @JvmStatic - fun uris() = listOf(protoUri(), jsonUri(), blockingProtoUri(), blockingJsonUri()) - .map { Arguments.of(it) } + fun uris() = + listOf(protoUri(), jsonUri(), blockingProtoUri(), blockingJsonUri()) + .map { Arguments.of(it) } private fun protoUri(): String { return "gproto+http://127.0.0.1:" + server.activeLocalPort() + '/' diff --git a/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/HelloAnnotatedService.kt b/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/HelloAnnotatedService.kt index 6ccde2c6a2a..b7616d74f50 100644 --- a/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/HelloAnnotatedService.kt +++ b/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/HelloAnnotatedService.kt @@ -17,7 +17,6 @@ import org.springframework.validation.annotation.Validated @Validated @ExceptionHandler(ValidationExceptionHandler::class) class HelloAnnotatedService { - @Get("/") fun defaultHello(): String = "Hello, world! Try sending a GET request to /hello/armeria" @@ -29,6 +28,6 @@ class HelloAnnotatedService { fun hello( @Param @Size(min = 3, max = 10, message = "name should have between 3 and 10 characters") - name: String + name: String, ): String = "Hello, $name! This message is from Armeria annotated service!" } diff --git a/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/HelloConfiguration.kt b/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/HelloConfiguration.kt index 980a86e64ee..ff5574c2a2f 100644 --- a/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/HelloConfiguration.kt +++ b/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/HelloConfiguration.kt @@ -12,7 +12,6 @@ import org.springframework.context.annotation.Configuration */ @Configuration class HelloConfiguration { - /** * A user can configure a [Server] by providing an [ArmeriaServerConfigurator] bean. */ diff --git a/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/ValidationExceptionHandler.kt b/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/ValidationExceptionHandler.kt index b5d205ced74..7e1a71496e4 100644 --- a/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/ValidationExceptionHandler.kt +++ b/examples/spring-boot-minimal-kotlin/src/main/kotlin/example/springframework/boot/minimal/kotlin/ValidationExceptionHandler.kt @@ -12,8 +12,11 @@ import java.time.Instant * A sample exception handler which handles a [ValidationException]. */ class ValidationExceptionHandler : ExceptionHandlerFunction { - - override fun handleException(ctx: ServiceRequestContext, req: HttpRequest, cause: Throwable): HttpResponse { + override fun handleException( + ctx: ServiceRequestContext, + req: HttpRequest, + cause: Throwable, + ): HttpResponse { return if (cause is ValidationException) { val status = HttpStatus.BAD_REQUEST HttpResponse.ofJson( @@ -23,8 +26,8 @@ class ValidationExceptionHandler : ExceptionHandlerFunction { cause.message ?: "empty message", req.path(), status.code(), - Instant.now().toString() - ) + Instant.now().toString(), + ), ) } else { ExceptionHandlerFunction.fallthrough() @@ -40,5 +43,5 @@ data class ErrorResponse( val message: String, val path: String, val status: Int, - val timestamp: String + val timestamp: String, ) diff --git a/examples/spring-boot-minimal-kotlin/src/test/kotlin/example/springframework/boot/minimal/kotlin/HelloApplicationIntegrationTest.kt b/examples/spring-boot-minimal-kotlin/src/test/kotlin/example/springframework/boot/minimal/kotlin/HelloApplicationIntegrationTest.kt index e0075bdce68..5fe510b8c0e 100644 --- a/examples/spring-boot-minimal-kotlin/src/test/kotlin/example/springframework/boot/minimal/kotlin/HelloApplicationIntegrationTest.kt +++ b/examples/spring-boot-minimal-kotlin/src/test/kotlin/example/springframework/boot/minimal/kotlin/HelloApplicationIntegrationTest.kt @@ -12,8 +12,9 @@ import org.springframework.test.context.ActiveProfiles @ActiveProfiles("testbed") @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) -class HelloApplicationIntegrationTest(@Autowired server: Server) { - +class HelloApplicationIntegrationTest( + @Autowired server: Server, +) { private val client: WebClient = WebClient.of("http://localhost:" + server.activeLocalPort()) @Test diff --git a/gradle/scripts/lib/scala.gradle b/gradle/scripts/lib/scala.gradle index 527bd14d1aa..48b019bee38 100644 --- a/gradle/scripts/lib/scala.gradle +++ b/gradle/scripts/lib/scala.gradle @@ -81,7 +81,7 @@ configure(scala212) { configure(scala213) { dependencies { - implementation 'org.scala-lang:scala-library:2.13.11' + implementation 'org.scala-lang:scala-library:2.13.12' if (managedVersions.containsKey('org.scalameta:munit_2.13')) { testImplementation "org.scalameta:munit_2.13:${managedVersions['org.scalameta:munit_2.13']}" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d11cdd907dd..e6aba2515d5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/grpc-kotlin/src/main/kotlin/com/linecorp/armeria/server/grpc/kotlin/CoroutineServerInterceptor.kt b/grpc-kotlin/src/main/kotlin/com/linecorp/armeria/server/grpc/kotlin/CoroutineServerInterceptor.kt index 4e2496d6731..2a644a38ce0 100644 --- a/grpc-kotlin/src/main/kotlin/com/linecorp/armeria/server/grpc/kotlin/CoroutineServerInterceptor.kt +++ b/grpc-kotlin/src/main/kotlin/com/linecorp/armeria/server/grpc/kotlin/CoroutineServerInterceptor.kt @@ -56,11 +56,10 @@ import kotlin.reflect.full.memberProperties */ @UnstableApi interface CoroutineServerInterceptor : AsyncServerInterceptor { - override fun asyncInterceptCall( call: ServerCall, headers: Metadata, - next: ServerCallHandler + next: ServerCallHandler, ): CompletableFuture> { // COROUTINE_CONTEXT_KEY.get(): // It is necessary to propagate the CoroutineContext set by the previous CoroutineContextServerInterceptor. @@ -68,7 +67,7 @@ interface CoroutineServerInterceptor : AsyncServerInterceptor { // GrpcContextElement.current(): // In gRPC-kotlin, the Coroutine Context is propagated using the gRPC Context. return CoroutineScope( - COROUTINE_CONTEXT_KEY.get() + GrpcContextElement.current() + COROUTINE_CONTEXT_KEY.get() + GrpcContextElement.current(), ).future { suspendedInterceptCall(call, headers, next) } @@ -87,7 +86,7 @@ interface CoroutineServerInterceptor : AsyncServerInterceptor { suspend fun suspendedInterceptCall( call: ServerCall, headers: Metadata, - next: ServerCallHandler + next: ServerCallHandler, ): ServerCall.Listener companion object { diff --git a/grpc-kotlin/src/test/kotlin/com/linecorp/armeria/server/grpc/kotlin/CoroutineServerInterceptorTest.kt b/grpc-kotlin/src/test/kotlin/com/linecorp/armeria/server/grpc/kotlin/CoroutineServerInterceptorTest.kt index a6ee9876f2a..ba1cc7c3f72 100644 --- a/grpc-kotlin/src/test/kotlin/com/linecorp/armeria/server/grpc/kotlin/CoroutineServerInterceptorTest.kt +++ b/grpc-kotlin/src/test/kotlin/com/linecorp/armeria/server/grpc/kotlin/CoroutineServerInterceptorTest.kt @@ -74,19 +74,19 @@ import kotlin.coroutines.CoroutineContext @GenerateNativeImageTrace internal class CoroutineServerInterceptorTest { - @OptIn(ExperimentalCoroutinesApi::class) @ValueSource(strings = ["/non-blocking", "/blocking"]) @ParameterizedTest fun authorizedUnaryRequest(path: String) { runTest { - val client = GrpcClients.builder(server.httpUri()) - .auth(AuthToken.ofOAuth2(token)) - .pathPrefix(path) - .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) + val client = + GrpcClients.builder(server.httpUri()) + .auth(AuthToken.ofOAuth2(TOKEN)) + .pathPrefix(path) + .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) assertThat(client.unaryCall(SimpleRequest.newBuilder().setFillUsername(true).build()).username) - .isEqualTo(username) + .isEqualTo(USER_NAME) } } @@ -95,9 +95,10 @@ internal class CoroutineServerInterceptorTest { @ParameterizedTest fun unauthorizedUnaryRequest(path: String) { runTest { - val client = GrpcClients.builder(server.httpUri()) - .pathPrefix(path) - .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) + val client = + GrpcClients.builder(server.httpUri()) + .pathPrefix(path) + .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) assertThatThrownBy { runBlocking { client.unaryCall(SimpleRequest.newBuilder().setFillUsername(true).build()) } @@ -112,13 +113,14 @@ internal class CoroutineServerInterceptorTest { @ParameterizedTest fun authorizedStreamingOutputCall(path: String) { runTest { - val client = GrpcClients.builder(server.httpUri()) - .auth(AuthToken.ofOAuth2(token)) - .pathPrefix(path) - .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) + val client = + GrpcClients.builder(server.httpUri()) + .auth(AuthToken.ofOAuth2(TOKEN)) + .pathPrefix(path) + .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) client.streamingOutputCall(StreamingOutputCallRequest.getDefaultInstance()).collect { - assertThat(it.payload.body.toStringUtf8()).isEqualTo(username) + assertThat(it.payload.body.toStringUtf8()).isEqualTo(USER_NAME) } } } @@ -128,9 +130,10 @@ internal class CoroutineServerInterceptorTest { @ParameterizedTest fun unauthorizedStreamingOutputCall(path: String) { runTest { - val client = GrpcClients.builder(server.httpUri()) - .pathPrefix(path) - .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) + val client = + GrpcClients.builder(server.httpUri()) + .pathPrefix(path) + .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) assertThatThrownBy { runBlocking { @@ -147,15 +150,16 @@ internal class CoroutineServerInterceptorTest { @ParameterizedTest fun authorizedStreamingInputCall(path: String) { runTest { - val client = GrpcClients.builder(server.httpUri()) - .auth(AuthToken.ofOAuth2(token)) - .pathPrefix(path) - .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) + val client = + GrpcClients.builder(server.httpUri()) + .auth(AuthToken.ofOAuth2(TOKEN)) + .pathPrefix(path) + .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) assertThat( client.streamingInputCall( - listOf(StreamingInputCallRequest.getDefaultInstance()).asFlow() - ).aggregatedPayloadSize + listOf(StreamingInputCallRequest.getDefaultInstance()).asFlow(), + ).aggregatedPayloadSize, ).isEqualTo(1) } } @@ -165,9 +169,10 @@ internal class CoroutineServerInterceptorTest { @ParameterizedTest fun unauthorizedStreamingInputCall(path: String) { runTest { - val client = GrpcClients.builder(server.httpUri()) - .pathPrefix(path) - .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) + val client = + GrpcClients.builder(server.httpUri()) + .pathPrefix(path) + .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) assertThatThrownBy { runBlocking { @@ -184,13 +189,14 @@ internal class CoroutineServerInterceptorTest { @ParameterizedTest fun authorizedFullDuplexCall(path: String) { runTest { - val client = GrpcClients.builder(server.httpUri()) - .auth(AuthToken.ofOAuth2(token)) - .pathPrefix(path) - .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) + val client = + GrpcClients.builder(server.httpUri()) + .auth(AuthToken.ofOAuth2(TOKEN)) + .pathPrefix(path) + .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) client.fullDuplexCall(listOf(StreamingOutputCallRequest.getDefaultInstance()).asFlow()).collect { - assertThat(it.payload.body.toStringUtf8()).isEqualTo(username) + assertThat(it.payload.body.toStringUtf8()).isEqualTo(USER_NAME) } } } @@ -200,9 +206,10 @@ internal class CoroutineServerInterceptorTest { @ParameterizedTest fun unauthorizedFullDuplexCall(path: String) { runTest { - val client = GrpcClients.builder(server.httpUri()) - .pathPrefix(path) - .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) + val client = + GrpcClients.builder(server.httpUri()) + .pathPrefix(path) + .build(TestServiceGrpcKt.TestServiceCoroutineStub::class.java) assertThatThrownBy { runBlocking { @@ -217,79 +224,82 @@ internal class CoroutineServerInterceptorTest { companion object { @RegisterExtension - val server: ServerExtension = object : ServerExtension() { - override fun configure(sb: ServerBuilder) { - val exceptionHandler = - GrpcExceptionHandlerFunction { _: RequestContext, throwable: Throwable, _: Metadata -> - if (throwable is AnticipatedException && throwable.message == "Invalid access") { - return@GrpcExceptionHandlerFunction Status.UNAUTHENTICATED + val server: ServerExtension = + object : ServerExtension() { + override fun configure(sb: ServerBuilder) { + val exceptionHandler = + GrpcExceptionHandlerFunction { _: RequestContext, throwable: Throwable, _: Metadata -> + if (throwable is AnticipatedException && throwable.message == "Invalid access") { + return@GrpcExceptionHandlerFunction Status.UNAUTHENTICATED + } + // Fallback to the default. + null } - // Fallback to the default. - null - } - val threadLocalInterceptor = ThreadLocalInterceptor() - val authInterceptor = AuthInterceptor() - val coroutineNameInterceptor = CoroutineNameInterceptor() - sb.serviceUnder( - "/non-blocking", - GrpcService.builder() - .exceptionHandler(exceptionHandler) - // applying order is "MyAsyncInterceptor -> coroutineNameInterceptor -> - // authInterceptor -> threadLocalInterceptor -> MyAsyncInterceptor" - .intercept( - MyAsyncInterceptor(), - threadLocalInterceptor, - authInterceptor, - coroutineNameInterceptor, - MyAsyncInterceptor() - ) - .addService(TestService()) - .build() - ) - sb.serviceUnder( - "/blocking", - GrpcService.builder() - .addService(TestService()) - .exceptionHandler(exceptionHandler) - // applying order is "MyAsyncInterceptor -> coroutineNameInterceptor -> - // authInterceptor -> threadLocalInterceptor -> MyAsyncInterceptor" - .intercept( - MyAsyncInterceptor(), - threadLocalInterceptor, - authInterceptor, - coroutineNameInterceptor, - MyAsyncInterceptor() - ) - .useBlockingTaskExecutor(true) - .build() - ) + val threadLocalInterceptor = ThreadLocalInterceptor() + val authInterceptor = AuthInterceptor() + val coroutineNameInterceptor = CoroutineNameInterceptor() + sb.serviceUnder( + "/non-blocking", + GrpcService.builder() + .exceptionHandler(exceptionHandler) + // applying order is "MyAsyncInterceptor -> coroutineNameInterceptor -> + // authInterceptor -> threadLocalInterceptor -> MyAsyncInterceptor" + .intercept( + MyAsyncInterceptor(), + threadLocalInterceptor, + authInterceptor, + coroutineNameInterceptor, + MyAsyncInterceptor(), + ) + .addService(TestService()) + .build(), + ) + sb.serviceUnder( + "/blocking", + GrpcService.builder() + .addService(TestService()) + .exceptionHandler(exceptionHandler) + // applying order is "MyAsyncInterceptor -> coroutineNameInterceptor -> + // authInterceptor -> threadLocalInterceptor -> MyAsyncInterceptor" + .intercept( + MyAsyncInterceptor(), + threadLocalInterceptor, + authInterceptor, + coroutineNameInterceptor, + MyAsyncInterceptor(), + ) + .useBlockingTaskExecutor(true) + .build(), + ) + } } - } - private const val username = "Armeria" - private const val token = "token-1234" + private const val USER_NAME = "Armeria" + private const val TOKEN = "token-1234" - private val executorDispatcher = Executors.newSingleThreadExecutor( - ThreadFactoryBuilder().setNameFormat("my-executor").build() - ).asCoroutineDispatcher() + private val executorDispatcher = + Executors.newSingleThreadExecutor( + ThreadFactoryBuilder().setNameFormat("my-executor").build(), + ).asCoroutineDispatcher() private class AuthInterceptor : CoroutineServerInterceptor { - private val authorizer = Authorizer { ctx: ServiceRequestContext, _: Metadata -> - val future = CompletableFuture() - ctx.eventLoop().schedule({ - if (ctx.request().headers().contains("Authorization", "Bearer $token")) { - future.complete(true) - } else { - future.complete(false) - } - }, 100, TimeUnit.MILLISECONDS) - return@Authorizer future - } + private val authorizer = + Authorizer { ctx: ServiceRequestContext, _: Metadata -> + val future = CompletableFuture() + ctx.eventLoop().schedule({ + if (ctx.request().headers().contains("Authorization", "Bearer $TOKEN")) { + future.complete(true) + } else { + future.complete(false) + } + }, 100, TimeUnit.MILLISECONDS) + return@Authorizer future + } override suspend fun suspendedInterceptCall( call: ServerCall, headers: Metadata, - next: ServerCallHandler + next: ServerCallHandler, ): ServerCall.Listener { assertContextPropagation() @@ -324,13 +334,19 @@ internal class CoroutineServerInterceptorTest { } private class CoroutineNameInterceptor : CoroutineContextServerInterceptor() { - override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext { + override fun coroutineContext( + call: ServerCall<*, *>, + headers: Metadata, + ): CoroutineContext { return CoroutineName("my-coroutine-name") } } private class ThreadLocalInterceptor : CoroutineContextServerInterceptor() { - override fun coroutineContext(call: ServerCall<*, *>, headers: Metadata): CoroutineContext { + override fun coroutineContext( + call: ServerCall<*, *>, + headers: Metadata, + ): CoroutineContext { return THREAD_LOCAL.asContextElement(value = "thread-local-value") } @@ -343,7 +359,7 @@ internal class CoroutineServerInterceptorTest { override fun asyncInterceptCall( call: ServerCall, headers: Metadata, - next: ServerCallHandler + next: ServerCallHandler, ): CompletableFuture> { val context = Context.current() return CompletableFuture.supplyAsync({ @@ -373,7 +389,7 @@ internal class CoroutineServerInterceptorTest { } if (request.fillUsername) { - return SimpleResponse.newBuilder().setUsername(username).build() + return SimpleResponse.newBuilder().setUsername(USER_NAME).build() } return SimpleResponse.getDefaultInstance() } @@ -383,7 +399,7 @@ internal class CoroutineServerInterceptorTest { for (i in 1..5) { delay(500) assertContextPropagation() - emit(buildReply(username)) + emit(buildReply(USER_NAME)) } } } @@ -401,7 +417,7 @@ internal class CoroutineServerInterceptorTest { requests.collect { delay(500) assertContextPropagation() - emit(buildReply(username)) + emit(buildReply(USER_NAME)) } } } @@ -418,7 +434,7 @@ internal class CoroutineServerInterceptorTest { StreamingOutputCallResponse.newBuilder() .setPayload( Payload.newBuilder() - .setBody(ByteString.copyFrom(message.toByteArray())) + .setBody(ByteString.copyFrom(message.toByteArray())), ) .build() diff --git a/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/CustomCoroutineContextProvider.kt b/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/CustomCoroutineContextProvider.kt index 25ff05014ba..faefa4b1850 100644 --- a/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/CustomCoroutineContextProvider.kt +++ b/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/CustomCoroutineContextProvider.kt @@ -27,11 +27,13 @@ import kotlin.coroutines.CoroutineContext class CustomCoroutineContextProvider : CoroutineContextProvider { companion object { val dispatcher: ExecutorCoroutineDispatcher + init { - val executor: BlockingTaskExecutor = BlockingTaskExecutor.builder() - .threadNamePrefix("custom-kotlin-grpc-worker") - .numThreads(1) - .build() + val executor: BlockingTaskExecutor = + BlockingTaskExecutor.builder() + .threadNamePrefix("custom-kotlin-grpc-worker") + .numThreads(1) + .build() dispatcher = executor.asCoroutineDispatcher() ShutdownHooks.addClosingTask { executor.shutdown() } } diff --git a/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/CustomCoroutineContextProviderTest.kt b/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/CustomCoroutineContextProviderTest.kt index 6cb07bffd13..d9607b14f2d 100644 --- a/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/CustomCoroutineContextProviderTest.kt +++ b/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/CustomCoroutineContextProviderTest.kt @@ -28,19 +28,19 @@ import testing.grpc.Hello import testing.grpc.TestServiceGrpcKt class CustomCoroutineContextProviderTest { - companion object { @JvmField @RegisterExtension - val server = object : ServerExtension() { - override fun configure(sb: ServerBuilder) { - sb.service( - GrpcService.builder() - .addService(TestServiceImpl()) - .build() - ) + val server = + object : ServerExtension() { + override fun configure(sb: ServerBuilder) { + sb.service( + GrpcService.builder() + .addService(TestServiceImpl()) + .build(), + ) + } } - } } @Test diff --git a/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceImpl.kt b/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceImpl.kt index 1af09e0d282..e3cf04affbe 100644 --- a/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceImpl.kt +++ b/it/grpc/kotlin-coroutine-context-provider/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceImpl.kt @@ -21,7 +21,6 @@ import testing.grpc.Hello import testing.grpc.TestServiceGrpcKt class TestServiceImpl : TestServiceGrpcKt.TestServiceCoroutineImplBase() { - override suspend fun hello(request: Hello.HelloRequest): Hello.HelloReply { val threadName = Thread.currentThread().name assertThat(threadName).startsWith("custom-kotlin-grpc-worker") diff --git a/it/grpc/kotlin/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceImpl.kt b/it/grpc/kotlin/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceImpl.kt index 7edbcd08cc5..3c6b6a2b5ab 100644 --- a/it/grpc/kotlin/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceImpl.kt +++ b/it/grpc/kotlin/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceImpl.kt @@ -32,28 +32,30 @@ import testing.grpc.TestServiceGrpcKt import java.io.Serial class TestServiceImpl : TestServiceGrpcKt.TestServiceCoroutineImplBase() { - /** * Sends a [HelloReply] with a small amount of blocking time using `ArmeriaBlockingContext`. * * @see [Blocking service implementation](https://armeria.dev/docs/server-grpc#blocking-service-implementation) */ - override suspend fun shortBlockingHello(request: HelloRequest): HelloReply = withArmeriaBlockingContext { - try { // Simulate a blocking API call. - Thread.sleep(10) - } catch (ignored: Exception) { // Do nothing. - } + override suspend fun shortBlockingHello(request: HelloRequest): HelloReply = + withArmeriaBlockingContext { + try { + // Simulate a blocking API call. + Thread.sleep(10) + } catch (ignored: Exception) { + // Do nothing. + } - withContext(blockingDispatcher()) { - // A request context is propagated by ArmeriaRequestCoroutineContext. - Thread.sleep(10) + withContext(blockingDispatcher()) { + // A request context is propagated by ArmeriaRequestCoroutineContext. + Thread.sleep(10) + // Make sure that current thread is request context aware + ServiceRequestContext.current() + } // Make sure that current thread is request context aware - ServiceRequestContext.current() + ServiceRequestContext.current().addAdditionalResponseHeader("foo", "bar") + buildReply(toMessage(request.name)) } - // Make sure that current thread is request context aware - ServiceRequestContext.current().addAdditionalResponseHeader("foo", "bar") - buildReply(toMessage(request.name)) - } /** * Sends 5 [HelloReply] responses using [armeriaBlockingDispatcher] when receiving a request. @@ -103,14 +105,12 @@ class TestServiceImpl : TestServiceGrpcKt.TestServiceCoroutineImplBase() { ServiceRequestContext.current().blockingTaskExecutor().asCoroutineDispatcher() // A blocking dispatcher that does not propagate a request context - fun blockingDispatcher(): CoroutineDispatcher = - CommonPools.blockingTaskExecutor().asCoroutineDispatcher() + fun blockingDispatcher(): CoroutineDispatcher = CommonPools.blockingTaskExecutor().asCoroutineDispatcher() suspend fun withArmeriaBlockingContext(block: suspend CoroutineScope.() -> T): T = withContext(ServiceRequestContext.current().blockingTaskExecutor().asCoroutineDispatcher(), block) - private fun buildReply(message: String): HelloReply = - HelloReply.newBuilder().setMessage(message).build() + private fun buildReply(message: String): HelloReply = HelloReply.newBuilder().setMessage(message).build() private fun toMessage(message: String): String = "Hello, $message!" } diff --git a/it/grpc/kotlin/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceTest.kt b/it/grpc/kotlin/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceTest.kt index 64c62b32bec..c93c2f553fd 100644 --- a/it/grpc/kotlin/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceTest.kt +++ b/it/grpc/kotlin/src/test/kotlin/com/linecorp/armeria/grpc/kotlin/TestServiceTest.kt @@ -46,7 +46,6 @@ import testing.grpc.TestServiceGrpcKt.TestServiceCoroutineStub import java.util.concurrent.atomic.AtomicInteger class TestServiceTest { - @ParameterizedTest @MethodSource("uris") fun parallelReplyFromServerSideBlockingCall(uri: String) { @@ -54,9 +53,10 @@ class TestServiceTest { val helloService = GrpcClients.newClient(uri, TestServiceCoroutineStub::class.java) repeat(30) { launch { - val message = helloService.shortBlockingHello( - HelloRequest.newBuilder().setName("$it Armeria").build() - ).message + val message = + helloService.shortBlockingHello( + HelloRequest.newBuilder().setName("$it Armeria").build(), + ).message assertThat(message).isEqualTo("Hello, $it Armeria!") } } @@ -86,7 +86,7 @@ class TestServiceTest { launch { var sequence = 0 service.shortBlockingLotsOfReplies( - HelloRequest.newBuilder().setName("Armeria").build() + HelloRequest.newBuilder().setName("Armeria").build(), ) .collect { assertThat(it.message).isEqualTo("Hello, Armeria! (sequence: ${++sequence})") @@ -126,28 +126,37 @@ class TestServiceTest { @MethodSource("uris") fun shouldReportCloseExactlyOnceWithNonOK(uri: String) { val closeCalled = AtomicInteger() - val helloService = GrpcClients.newClient(uri, TestServiceCoroutineStub::class.java) - .withInterceptors(object : ClientInterceptor { - override fun interceptCall( - method: MethodDescriptor, - options: CallOptions, - next: Channel - ): ClientCall { - return object : SimpleForwardingClientCall(next.newCall(method, options)) { - override fun start(responseListener: Listener, headers: Metadata) { - super.start( - object : SimpleForwardingClientCallListener(responseListener) { - override fun onClose(status: Status, trailers: Metadata) { - closeCalled.incrementAndGet() - super.onClose(status, trailers) - } - }, - headers - ) + val helloService = + GrpcClients.newClient(uri, TestServiceCoroutineStub::class.java) + .withInterceptors( + object : ClientInterceptor { + override fun interceptCall( + method: MethodDescriptor, + options: CallOptions, + next: Channel, + ): ClientCall { + return object : SimpleForwardingClientCall(next.newCall(method, options)) { + override fun start( + responseListener: Listener, + headers: Metadata, + ) { + super.start( + object : SimpleForwardingClientCallListener(responseListener) { + override fun onClose( + status: Status, + trailers: Metadata, + ) { + closeCalled.incrementAndGet() + super.onClose(status, trailers) + } + }, + headers, + ) + } + } } - } - } - }) + }, + ) assertThatThrownBy { runBlocking { helloService.helloError(HelloRequest.newBuilder().setName("Armeria").build()) } @@ -161,7 +170,6 @@ class TestServiceTest { } companion object { - private lateinit var server: Server private lateinit var blockingServer: Server private lateinit var service: TestServiceCoroutineStub @@ -185,10 +193,14 @@ class TestServiceTest { } @JvmStatic - fun uris() = listOf(protoUri(), jsonUri(), blockingProtoUri(), blockingJsonUri()) - .map { Arguments.of(it) } - - private fun newServer(httpPort: Int, useBlockingTaskExecutor: Boolean = false): Server { + fun uris() = + listOf(protoUri(), jsonUri(), blockingProtoUri(), blockingJsonUri()) + .map { Arguments.of(it) } + + private fun newServer( + httpPort: Int, + useBlockingTaskExecutor: Boolean = false, + ): Server { return Server.builder() .http(httpPort) .service( @@ -205,7 +217,7 @@ class TestServiceTest { } } .useBlockingTaskExecutor(useBlockingTaskExecutor) - .build() + .build(), ) .build() } diff --git a/it/kotlin/src/test/kotlin/com/linecorp/armeria/it/AnnotatedServiceErrorMessageTest.kt b/it/kotlin/src/test/kotlin/com/linecorp/armeria/it/AnnotatedServiceErrorMessageTest.kt index 8646bd39f31..9e78b9ab095 100644 --- a/it/kotlin/src/test/kotlin/com/linecorp/armeria/it/AnnotatedServiceErrorMessageTest.kt +++ b/it/kotlin/src/test/kotlin/com/linecorp/armeria/it/AnnotatedServiceErrorMessageTest.kt @@ -25,12 +25,13 @@ import org.junit.jupiter.api.Test class AnnotatedServiceErrorMessageTest { @Test fun test() { - val serverBuilder: ServerBuilder = Server.builder() - .annotatedService(MyAnnotatedService()) + val serverBuilder: ServerBuilder = + Server.builder() + .annotatedService(MyAnnotatedService()) assertThatThrownBy { serverBuilder.build() } .hasMessageContaining( - "Kotlin suspending functions are supported only when you added 'armeria-kotlin' as a dependency." + "Kotlin suspending functions are supported only when you added 'armeria-kotlin' as a dependency.", ) } diff --git a/it/spring/boot3-kotlin/src/test/kotlin/com/linecorp/armeria/spring/kotlin/AbnormalController.kt b/it/spring/boot3-kotlin/src/test/kotlin/com/linecorp/armeria/spring/kotlin/AbnormalController.kt index 37681536fbf..8f95a27e092 100644 --- a/it/spring/boot3-kotlin/src/test/kotlin/com/linecorp/armeria/spring/kotlin/AbnormalController.kt +++ b/it/spring/boot3-kotlin/src/test/kotlin/com/linecorp/armeria/spring/kotlin/AbnormalController.kt @@ -26,7 +26,6 @@ import org.springframework.web.bind.annotation.ResponseBody */ @Controller class AbnormalController(private val objectMapper: ObjectMapper) { - @GetMapping(value = ["/abnormal"], produces = ["text/plain;charset=utf-8"]) @ResponseBody fun abnormal(): ResponseEntity { diff --git a/it/spring/boot3-kotlin/src/test/kotlin/com/linecorp/armeria/spring/kotlin/ClientCoroutineTest.kt b/it/spring/boot3-kotlin/src/test/kotlin/com/linecorp/armeria/spring/kotlin/ClientCoroutineTest.kt index 47dcd737c0e..55dff70946e 100644 --- a/it/spring/boot3-kotlin/src/test/kotlin/com/linecorp/armeria/spring/kotlin/ClientCoroutineTest.kt +++ b/it/spring/boot3-kotlin/src/test/kotlin/com/linecorp/armeria/spring/kotlin/ClientCoroutineTest.kt @@ -30,7 +30,6 @@ import org.springframework.web.reactive.function.client.awaitBody @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class ClientCoroutineTest { - @LocalServerPort private var port = 0 @@ -41,10 +40,11 @@ class ClientCoroutineTest { @PostConstruct fun setUp() { - client = WebClient.builder() - .baseUrl("http://127.0.0.1:$port") - .clientConnector(connector) - .build() + client = + WebClient.builder() + .baseUrl("http://127.0.0.1:$port") + .clientConnector(connector) + .build() } @Test @@ -60,7 +60,7 @@ class ClientCoroutineTest { assertThat(ex.cause).isInstanceOf(UnsupportedMediaTypeException::class.java) .hasMessageContaining( "Content type 'text/plain;charset=utf-8' not supported for " + - "bodyType=com.linecorp.armeria.spring.kotlin.Abnormal" + "bodyType=com.linecorp.armeria.spring.kotlin.Abnormal", ) } } diff --git a/kotlin/src/main/kotlin/com/linecorp/armeria/common/kotlin/CoroutineContextAwareExecutor.kt b/kotlin/src/main/kotlin/com/linecorp/armeria/common/kotlin/CoroutineContextAwareExecutor.kt index 09fb83d9f1e..dafa582a332 100644 --- a/kotlin/src/main/kotlin/com/linecorp/armeria/common/kotlin/CoroutineContextAwareExecutor.kt +++ b/kotlin/src/main/kotlin/com/linecorp/armeria/common/kotlin/CoroutineContextAwareExecutor.kt @@ -17,9 +17,9 @@ package com.linecorp.armeria.common.kotlin import com.linecorp.armeria.common.ContextAwareExecutor -import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher +import kotlin.coroutines.CoroutineContext /** * Converts an instance of [ContextAwareExecutor] to an implementation of [CoroutineDispatcher]. diff --git a/kotlin/src/main/kotlin/com/linecorp/armeria/common/kotlin/CoroutineRequestContext.kt b/kotlin/src/main/kotlin/com/linecorp/armeria/common/kotlin/CoroutineRequestContext.kt index 3600bbd49c0..cb1f7d62790 100644 --- a/kotlin/src/main/kotlin/com/linecorp/armeria/common/kotlin/CoroutineRequestContext.kt +++ b/kotlin/src/main/kotlin/com/linecorp/armeria/common/kotlin/CoroutineRequestContext.kt @@ -18,9 +18,9 @@ package com.linecorp.armeria.common.kotlin import com.linecorp.armeria.common.RequestContext import com.linecorp.armeria.common.util.SafeCloseable +import kotlinx.coroutines.ThreadContextElement import kotlin.coroutines.AbstractCoroutineContextElement import kotlin.coroutines.CoroutineContext -import kotlinx.coroutines.ThreadContextElement /** * Converts an instance of [RequestContext] to an implementation of [CoroutineContext] that automatically @@ -35,16 +35,18 @@ fun RequestContext.asCoroutineContext(): ArmeriaRequestCoroutineContext { * Propagates [RequestContext] over coroutines. */ class ArmeriaRequestCoroutineContext internal constructor( - private val requestContext: RequestContext + private val requestContext: RequestContext, ) : ThreadContextElement, AbstractCoroutineContextElement(Key) { - companion object Key : CoroutineContext.Key override fun updateThreadContext(context: CoroutineContext): SafeCloseable { return requestContext.push() } - override fun restoreThreadContext(context: CoroutineContext, oldState: SafeCloseable) { + override fun restoreThreadContext( + context: CoroutineContext, + oldState: SafeCloseable, + ) { oldState.close() } } diff --git a/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/ArmeriaCoroutineUtil.kt b/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/ArmeriaCoroutineUtil.kt index 991989e273d..20fcb7e2dfa 100644 --- a/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/ArmeriaCoroutineUtil.kt +++ b/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/ArmeriaCoroutineUtil.kt @@ -49,20 +49,22 @@ internal fun callKotlinSuspendingMethod( obj: Any, args: Array, executorService: ExecutorService, - ctx: ServiceRequestContext + ctx: ServiceRequestContext, ): CompletableFuture { val kFunction = checkNotNull(method.kotlinFunction) { "method is not a kotlin function" } - val future = GlobalScope.future(newCoroutineCtx(executorService, ctx)) { - val response = kFunction - .callSuspend(obj, *args) - .let { if (it == Unit) null else it } + val future = + GlobalScope.future(newCoroutineCtx(executorService, ctx)) { + val response = + kFunction + .callSuspend(obj, *args) + .let { if (it == Unit) null else it } - if (response != null && ctx.isCancelled) { - // A request has been canceled. Release the response resources to prevent leaks. - StreamMessageUtil.closeOrAbort(response) + if (response != null && ctx.isCancelled) { + // A request has been canceled. Release the response resources to prevent leaks. + StreamMessageUtil.closeOrAbort(response) + } + response } - response - } // Propagate cancellation to upstream. ctx.whenRequestCancelled().thenAccept { cause -> @@ -80,10 +82,13 @@ internal fun callKotlinSuspendingMethod( */ internal fun Flow.asPublisher( executor: EventExecutor, - ctx: ServiceRequestContext + ctx: ServiceRequestContext, ): Publisher = FlowCollectingPublisher(this, executor, newCoroutineCtx(executor, ctx)) -private fun newCoroutineCtx(executorService: ExecutorService, ctx: ServiceRequestContext): CoroutineContext { +private fun newCoroutineCtx( + executorService: ExecutorService, + ctx: ServiceRequestContext, +): CoroutineContext { val userContext = CoroutineContexts.get(ctx) ?: EmptyCoroutineContext if (executorService is ContextAwareExecutor) { return (executorService as ContextAwareExecutor).asCoroutineDispatcher() + userContext diff --git a/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/ArmeriaRequestCoroutineContext.kt b/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/ArmeriaRequestCoroutineContext.kt index 6fce52c4eb9..a35e948589a 100644 --- a/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/ArmeriaRequestCoroutineContext.kt +++ b/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/ArmeriaRequestCoroutineContext.kt @@ -27,16 +27,18 @@ import kotlin.coroutines.CoroutineContext */ @Deprecated("Use RequestContext.asCoroutineContext() instead.", ReplaceWith("RequestContext.asCoroutineContext()")) class ArmeriaRequestCoroutineContext( - private val requestContext: ServiceRequestContext + private val requestContext: ServiceRequestContext, ) : ThreadContextElement, AbstractCoroutineContextElement(Key) { - companion object Key : CoroutineContext.Key override fun updateThreadContext(context: CoroutineContext): SafeCloseable { return requestContext.push() } - override fun restoreThreadContext(context: CoroutineContext, oldState: SafeCloseable) { + override fun restoreThreadContext( + context: CoroutineContext, + oldState: SafeCloseable, + ) { oldState.close() } } diff --git a/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowCollectingPublisher.kt b/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowCollectingPublisher.kt index e6fcb4a3920..d648b06807b 100644 --- a/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowCollectingPublisher.kt +++ b/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowCollectingPublisher.kt @@ -35,23 +35,24 @@ import kotlin.coroutines.EmptyCoroutineContext internal class FlowCollectingPublisher( private val flow: Flow, private val executor: EventExecutor, - private val context: CoroutineContext = EmptyCoroutineContext + private val context: CoroutineContext = EmptyCoroutineContext, ) : Publisher { @OptIn(DelicateCoroutinesApi::class) override fun subscribe(s: Subscriber) { val delegate = StreamMessage.streaming() - val job = GlobalScope.launch(context) { - try { - flow.collect { - delegate.write(it!!) - delegate.whenConsumed().await() + val job = + GlobalScope.launch(context) { + try { + flow.collect { + delegate.write(it!!) + delegate.whenConsumed().await() + } + } catch (e: Throwable) { + delegate.close(e) + return@launch } - } catch (e: Throwable) { - delegate.close(e) - return@launch + delegate.close() } - delegate.close() - } delegate.whenComplete().handle { _, _ -> if (job.isActive) { job.cancel() diff --git a/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowResponseConverterFunction.kt b/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowResponseConverterFunction.kt index 1a8e13fcc8f..78b2c9e04a5 100644 --- a/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowResponseConverterFunction.kt +++ b/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowResponseConverterFunction.kt @@ -38,10 +38,12 @@ import java.lang.reflect.Type * neither ends nor gets cancelled, which makes the converted [HttpResponse] an infinite stream. */ class FlowResponseConverterFunction( - private val responseConverter: ResponseConverterFunction + private val responseConverter: ResponseConverterFunction, ) : ResponseConverterFunction { - - override fun isResponseStreaming(returnType: Type, produceType: MediaType?): Boolean { + override fun isResponseStreaming( + returnType: Type, + produceType: MediaType?, + ): Boolean { // This convert is used only when the return type of an annotated service is `Flow`. return true } @@ -50,7 +52,7 @@ class FlowResponseConverterFunction( ctx: ServiceRequestContext, headers: ResponseHeaders, result: Any?, - trailers: HttpHeaders + trailers: HttpHeaders, ): HttpResponse { if (result is Flow<*>) { // Reactive Streams doesn't allow emitting null value. diff --git a/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowResponseConverterFunctionProvider.kt b/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowResponseConverterFunctionProvider.kt index 7ccbfa10659..995b49ae798 100644 --- a/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowResponseConverterFunctionProvider.kt +++ b/kotlin/src/main/kotlin/com/linecorp/armeria/internal/common/kotlin/FlowResponseConverterFunctionProvider.kt @@ -16,8 +16,8 @@ package com.linecorp.armeria.internal.common.kotlin -import com.linecorp.armeria.server.annotation.ResponseConverterFunction import com.linecorp.armeria.server.annotation.DelegatingResponseConverterFunctionProvider +import com.linecorp.armeria.server.annotation.ResponseConverterFunction import kotlinx.coroutines.flow.Flow import java.lang.reflect.ParameterizedType import java.lang.reflect.Type @@ -29,7 +29,7 @@ import java.lang.reflect.Type class FlowResponseConverterFunctionProvider : DelegatingResponseConverterFunctionProvider { override fun createResponseConverterFunction( returnType: Type, - responseConverter: ResponseConverterFunction + responseConverter: ResponseConverterFunction, ): ResponseConverterFunction? = returnType .toClassOrNull() diff --git a/kotlin/src/test/kotlin/com/linecorp/armeria/client/kotlin/CoroutineRestClientTest.kt b/kotlin/src/test/kotlin/com/linecorp/armeria/client/kotlin/CoroutineRestClientTest.kt index f2a85a7500f..82f91e1a5ea 100644 --- a/kotlin/src/test/kotlin/com/linecorp/armeria/client/kotlin/CoroutineRestClientTest.kt +++ b/kotlin/src/test/kotlin/com/linecorp/armeria/client/kotlin/CoroutineRestClientTest.kt @@ -30,7 +30,6 @@ import org.junit.jupiter.api.extension.RegisterExtension @GenerateNativeImageTrace class CoroutineRestClientTest { - @Test fun get() { runBlocking { @@ -66,23 +65,24 @@ class CoroutineRestClientTest { companion object { @RegisterExtension - var server: ServerExtension = object : ServerExtension() { - override fun configure(sb: ServerBuilder) { - sb.service("/rest/{id}") { ctx: ServiceRequestContext, req: HttpRequest -> - HttpResponse.of( - req.aggregate().thenApply { agg: AggregatedHttpRequest -> - val restResponse = - RestResponse( - ctx.pathParam("id")!!, - req.method().toString(), - agg.contentUtf8() - ) - HttpResponse.ofJson(restResponse) - } - ) + var server: ServerExtension = + object : ServerExtension() { + override fun configure(sb: ServerBuilder) { + sb.service("/rest/{id}") { ctx: ServiceRequestContext, req: HttpRequest -> + HttpResponse.of( + req.aggregate().thenApply { agg: AggregatedHttpRequest -> + val restResponse = + RestResponse( + ctx.pathParam("id")!!, + req.method().toString(), + agg.contentUtf8(), + ) + HttpResponse.ofJson(restResponse) + }, + ) + } } } - } } data class RestResponse(val id: String, val method: String, val content: String) diff --git a/kotlin/src/test/kotlin/com/linecorp/armeria/common/kotlin/CoroutineContextAwareExecutorTest.kt b/kotlin/src/test/kotlin/com/linecorp/armeria/common/kotlin/CoroutineContextAwareExecutorTest.kt index 4141bcab430..8f84b0ffcd1 100644 --- a/kotlin/src/test/kotlin/com/linecorp/armeria/common/kotlin/CoroutineContextAwareExecutorTest.kt +++ b/kotlin/src/test/kotlin/com/linecorp/armeria/common/kotlin/CoroutineContextAwareExecutorTest.kt @@ -29,7 +29,6 @@ import org.junit.jupiter.api.Test @GenerateNativeImageTrace class CoroutineContextAwareExecutorTest { - @Test fun serviceRequestContext() { val ctx = ServiceRequestContext.builder(HttpRequest.of(HttpMethod.GET, "/")).build() diff --git a/kotlin/src/test/kotlin/com/linecorp/armeria/common/kotlin/CoroutineRequestContextTest.kt b/kotlin/src/test/kotlin/com/linecorp/armeria/common/kotlin/CoroutineRequestContextTest.kt index e26873af2d5..3a1442f8231 100644 --- a/kotlin/src/test/kotlin/com/linecorp/armeria/common/kotlin/CoroutineRequestContextTest.kt +++ b/kotlin/src/test/kotlin/com/linecorp/armeria/common/kotlin/CoroutineRequestContextTest.kt @@ -29,7 +29,6 @@ import org.junit.jupiter.api.Test @GenerateNativeImageTrace class CoroutineRequestContextTest { - @Test fun serviceRequestContext() { val ctx = ServiceRequestContext.builder(HttpRequest.of(HttpMethod.GET, "/")).build() diff --git a/kotlin/src/test/kotlin/com/linecorp/armeria/internal/server/annotation/DataClassDefaultNameTypeInfoProviderTest.kt b/kotlin/src/test/kotlin/com/linecorp/armeria/internal/server/annotation/DataClassDefaultNameTypeInfoProviderTest.kt index 74a4b452612..a39ee699476 100644 --- a/kotlin/src/test/kotlin/com/linecorp/armeria/internal/server/annotation/DataClassDefaultNameTypeInfoProviderTest.kt +++ b/kotlin/src/test/kotlin/com/linecorp/armeria/internal/server/annotation/DataClassDefaultNameTypeInfoProviderTest.kt @@ -30,7 +30,6 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource class DataClassDefaultNameTypeInfoProviderTest { - @CsvSource(value = ["true", "false"]) @ParameterizedTest fun dataClass(request: Boolean) { @@ -64,7 +63,7 @@ class DataClassDefaultNameTypeInfoProviderTest { FieldInfo.builder("renamedNullable", STRING) .requirement(FieldRequirement.OPTIONAL) .descriptionInfo(DescriptionInfo.of("renamed nullable description")) - .build() + .build(), ) } @@ -78,7 +77,7 @@ class DataClassDefaultNameTypeInfoProviderTest { assertThat(enumInfo.descriptionInfo()).isEqualTo(DescriptionInfo.of("Enum description")) assertThat(enumInfo.values()).containsExactlyInAnyOrder( EnumValueInfo("ENUM_1", null, DescriptionInfo.of("ENUM_1 description")), - EnumValueInfo("ENUM_2", null, DescriptionInfo.of("ENUM_2 description")) + EnumValueInfo("ENUM_2", null, DescriptionInfo.of("ENUM_2 description")), ) } @@ -97,7 +96,7 @@ class DataClassDefaultNameTypeInfoProviderTest { val nonnullName: String, @JsonProperty("renamedNullable") @Description("renamed nullable description") - val nullableName: String? + val nullableName: String?, ) @Description("Enum description") @@ -106,6 +105,6 @@ class DataClassDefaultNameTypeInfoProviderTest { ENUM_1, @Description(value = "ENUM_2 description") - ENUM_2 + ENUM_2, } } diff --git a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/DataClassDocServiceTest.kt b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/DataClassDocServiceTest.kt index 167104b77b1..1c683796388 100644 --- a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/DataClassDocServiceTest.kt +++ b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/DataClassDocServiceTest.kt @@ -32,15 +32,15 @@ import org.junit.jupiter.api.extension.RegisterExtension @GenerateNativeImageTrace class DataClassDocServiceTest { - @Test fun dataClassParamSpecification() { val client = WebClient.of(server.httpUri()).blocking() - val jsonNode = client.prepare() - .get("/docs/specification.json") - .asJson(JsonNode::class.java) - .execute() - .content() + val jsonNode = + client.prepare() + .get("/docs/specification.json") + .asJson(JsonNode::class.java) + .execute() + .content() assertThat(jsonNode.get("services")[0]["methods"][0]["parameters"][0]["typeSignature"].asText()) .isEqualTo("com.linecorp.armeria.server.kotlin.DataClassDocServiceTest\$ExampleQueries1") @@ -65,24 +65,29 @@ class DataClassDocServiceTest { companion object { @JvmField @RegisterExtension - val server = object : ServerExtension() { - override fun configure(sb: ServerBuilder) { - sb.annotatedService() - .requestConverters() - sb.annotatedService(MyKotlinService()) - sb.serviceUnder("/docs", DocService()) + val server = + object : ServerExtension() { + override fun configure(sb: ServerBuilder) { + sb.annotatedService() + .requestConverters() + sb.annotatedService(MyKotlinService()) + sb.serviceUnder("/docs", DocService()) + } } - } } class MyKotlinService { @Get("/example1") - fun getIdV1(@Suppress("UNUSED_PARAMETER") queries: ExampleQueries1): String { + fun getIdV1( + @Suppress("UNUSED_PARAMETER") queries: ExampleQueries1, + ): String { return "example" } @Get("/example2") - fun getIdV2(@Suppress("UNUSED_PARAMETER") queries: ExampleQueries2): String { + fun getIdV2( + @Suppress("UNUSED_PARAMETER") queries: ExampleQueries2, + ): String { return "example" } } @@ -91,7 +96,7 @@ class DataClassDocServiceTest { @Param val name: String, @Param @Default - val limit: Int? + val limit: Int?, ) data class ExampleQueries2( @@ -100,6 +105,6 @@ class DataClassDocServiceTest { @Param val topic: String, @Param - val group: String + val group: String, ) } diff --git a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/FlowAnnotatedServiceTest.kt b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/FlowAnnotatedServiceTest.kt index bb3ef7c3bd1..800ef367bb0 100644 --- a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/FlowAnnotatedServiceTest.kt +++ b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/FlowAnnotatedServiceTest.kt @@ -54,59 +54,69 @@ import kotlin.coroutines.coroutineContext @GenerateNativeImageTrace internal class FlowAnnotatedServiceTest { @Test - fun test_byteStreaming() = runBlocking { - client.get("/flow/byte-streaming") shouldProduce listOf("hello", "world", "") - } + fun test_byteStreaming() = + runBlocking { + client.get("/flow/byte-streaming") shouldProduce listOf("hello", "world", "") + } @Test - fun test_jsonStreaming_string() = runBlocking { - client.get("/flow/json-string-streaming") shouldProduce listOf( - "\u001E\"hello\"\n", - "\u001E\"world\"\n", - "" - ) - } + fun test_jsonStreaming_string() = + runBlocking { + client.get("/flow/json-string-streaming") shouldProduce + listOf( + "\u001E\"hello\"\n", + "\u001E\"world\"\n", + "", + ) + } @Test - fun test_jsonStreaming_obj() = runBlocking { - client.get("/flow/json-obj-streaming") shouldProduce listOf( - "\u001E{\"name\":\"foo\",\"age\":10}\n", - "\u001E{\"name\":\"bar\",\"age\":20}\n", - "\u001E{\"name\":\"baz\",\"age\":30}\n", - "" - ) - } + fun test_jsonStreaming_obj() = + runBlocking { + client.get("/flow/json-obj-streaming") shouldProduce + listOf( + "\u001E{\"name\":\"foo\",\"age\":10}\n", + "\u001E{\"name\":\"bar\",\"age\":20}\n", + "\u001E{\"name\":\"baz\",\"age\":30}\n", + "", + ) + } @Test - fun test_eventStreaming() = runBlocking { - client.get("/flow/event-streaming") shouldProduce listOf( - "id:1\n" + "event:MESSAGE_DELIVERED\n" + "data:{\"message_id\":1}\n" + "\n", - "id:2\n" + "event:FOLLOW_REQUEST\n" + "data:{\"user_id\":123}\n" + "\n", - "" - ) - } + fun test_eventStreaming() = + runBlocking { + client.get("/flow/event-streaming") shouldProduce + listOf( + "id:1\n" + "event:MESSAGE_DELIVERED\n" + "data:{\"message_id\":1}\n" + "\n", + "id:2\n" + "event:FOLLOW_REQUEST\n" + "data:{\"user_id\":123}\n" + "\n", + "", + ) + } @Test - fun test_aggregatedJson(): Unit = runBlocking { - val aggregated = client.get("/flow/aggregated-json-obj").aggregate().await() - assertThat(aggregated.contentUtf8()).isEqualTo( - "[{\"name\":\"foo\",\"age\":10},{\"name\":\"bar\",\"age\":20},{\"name\":\"baz\",\"age\":30}]" - ) - } + fun test_aggregatedJson(): Unit = + runBlocking { + val aggregated = client.get("/flow/aggregated-json-obj").aggregate().await() + assertThat(aggregated.contentUtf8()).isEqualTo( + "[{\"name\":\"foo\",\"age\":10},{\"name\":\"bar\",\"age\":20},{\"name\":\"baz\",\"age\":30}]", + ) + } @Test - fun test_customContext(): Unit = runBlocking { - val res = client.get("/flow/custom-context").aggregate().await() - assertThat(res.status()).isEqualTo(HttpStatus.OK) - assertThat(res.contentUtf8()).isEqualTo("OK") - } + fun test_customContext(): Unit = + runBlocking { + val res = client.get("/flow/custom-context").aggregate().await() + assertThat(res.status()).isEqualTo(HttpStatus.OK) + assertThat(res.contentUtf8()).isEqualTo("OK") + } @Test - fun test_customDispatcher(): Unit = runBlocking { - val res = client.get("/flow/custom-dispatcher").aggregate().await() - assertThat(res.status()).isEqualTo(HttpStatus.OK) - assertThat(res.contentUtf8()).isEqualTo("OK") - } + fun test_customDispatcher(): Unit = + runBlocking { + val res = client.get("/flow/custom-dispatcher").aggregate().await() + assertThat(res.status()).isEqualTo(HttpStatus.OK) + assertThat(res.contentUtf8()).isEqualTo("OK") + } @Test fun test_cancellation() { @@ -120,129 +130,140 @@ internal class FlowAnnotatedServiceTest { } @Test - fun test_runsWithinEventLoop(): Unit = runBlocking { - val res = client.get("/flow/runs-within-event-loop").aggregate().await() - assertThat(res.status()).isEqualTo(HttpStatus.OK) - assertThat(res.contentUtf8()).isEqualTo("OK") - } + fun test_runsWithinEventLoop(): Unit = + runBlocking { + val res = client.get("/flow/runs-within-event-loop").aggregate().await() + assertThat(res.status()).isEqualTo(HttpStatus.OK) + assertThat(res.contentUtf8()).isEqualTo("OK") + } companion object { private val cancelled = AtomicBoolean() @JvmField @RegisterExtension - val server = object : ServerExtension() { - override fun configure(sb: ServerBuilder) { - @Suppress("unused") - sb.apply { - annotatedService( - "/flow", - object { - @Get("/byte-streaming") - @ProducesOctetStream - fun byteStreaming(): Flow = flow { - emit("hello".toByteArray()) - emit("world".toByteArray()) - } + val server = + object : ServerExtension() { + override fun configure(sb: ServerBuilder) { + @Suppress("unused") + sb.apply { + annotatedService( + "/flow", + object { + @Get("/byte-streaming") + @ProducesOctetStream + fun byteStreaming(): Flow = + flow { + emit("hello".toByteArray()) + emit("world".toByteArray()) + } - @Get("/json-string-streaming") - @ProducesJsonSequences - fun jsonStreamingString(): Flow = flow { - emit("hello") - emit("world") - } + @Get("/json-string-streaming") + @ProducesJsonSequences + fun jsonStreamingString(): Flow = + flow { + emit("hello") + emit("world") + } - @Get("/json-obj-streaming") - @ProducesJsonSequences - fun jsonStreamingObj(): Flow = flow { - emit(Member(name = "foo", age = 10)) - emit(Member(name = "bar", age = 20)) - emit(Member(name = "baz", age = 30)) - } + @Get("/json-obj-streaming") + @ProducesJsonSequences + fun jsonStreamingObj(): Flow = + flow { + emit(Member(name = "foo", age = 10)) + emit(Member(name = "bar", age = 20)) + emit(Member(name = "baz", age = 30)) + } - @Get("/event-streaming") - @ProducesEventStream - fun eventStreaming(): Flow = flow { - emit( - ServerSentEvent - .builder() - .id("1") - .event("MESSAGE_DELIVERED") - .data("{\"message_id\":1}") - .build() - ) - emit( - ServerSentEvent - .builder() - .id("2") - .event("FOLLOW_REQUEST") - .data("{\"user_id\":123}") - .build() - ) - } + @Get("/event-streaming") + @ProducesEventStream + fun eventStreaming(): Flow = + flow { + emit( + ServerSentEvent + .builder() + .id("1") + .event("MESSAGE_DELIVERED") + .data("{\"message_id\":1}") + .build(), + ) + emit( + ServerSentEvent + .builder() + .id("2") + .event("FOLLOW_REQUEST") + .data("{\"user_id\":123}") + .build(), + ) + } - @Get("/aggregated-json-obj") - @ProducesJson - fun aggregatedJson() = flow { - emit(Member(name = "foo", age = 10)) - emit(Member(name = "bar", age = 20)) - emit(Member(name = "baz", age = 30)) - } + @Get("/aggregated-json-obj") + @ProducesJson + fun aggregatedJson() = + flow { + emit(Member(name = "foo", age = 10)) + emit(Member(name = "bar", age = 20)) + emit(Member(name = "baz", age = 30)) + } - @Get("/custom-context") - @ProducesText - fun userContext() = flow { - val user = checkNotNull(coroutineContext[User]) - assertThat(user.name).isEqualTo("Armeria") - assertThat(user.role).isEqualTo("Admin") - emit("OK") - } + @Get("/custom-context") + @ProducesText + fun userContext() = + flow { + val user = checkNotNull(coroutineContext[User]) + assertThat(user.name).isEqualTo("Armeria") + assertThat(user.role).isEqualTo("Admin") + emit("OK") + } - @Get("/custom-dispatcher") - @ProducesText - fun dispatcherContext() = flow { - assertThat(Thread.currentThread().name).contains("custom-thread") - emit("OK") - } + @Get("/custom-dispatcher") + @ProducesText + fun dispatcherContext() = + flow { + assertThat(Thread.currentThread().name).contains("custom-thread") + emit("OK") + } - @Get("/cancellation") - @ProducesJsonSequences - fun cancellation() = flow { - try { - emit("OK") - delay(2500L) - emit("world") - } catch (e: CancellationException) { - cancelled.set(true) - throw e - } - } + @Get("/cancellation") + @ProducesJsonSequences + fun cancellation() = + flow { + try { + emit("OK") + delay(2500L) + emit("world") + } catch (e: CancellationException) { + cancelled.set(true) + throw e + } + } - @Get("/runs-within-event-loop") - @ProducesText - fun runsWithinEventLoop() = flow { - ServiceRequestContext.current() - assertThat(Thread.currentThread().name).contains("armeria-common-worker") - emit("OK") - } - } - ) - decorator( - Route.builder().path("/flow", "/custom-context").build(), - CoroutineContextService.newDecorator { User(name = "Armeria", role = "Admin") } - ) - decorator( - Route.builder().path("/flow", "/custom-dispatcher").build(), - CoroutineContextService.newDecorator { - Executors - .newSingleThreadExecutor { Thread(it, "custom-thread") } - .asCoroutineDispatcher() - } - ) - requestTimeoutMillis(2000L) + @Get("/runs-within-event-loop") + @ProducesText + fun runsWithinEventLoop() = + flow { + ServiceRequestContext.current() + assertThat(Thread.currentThread().name).contains("armeria-common-worker") + emit("OK") + } + }, + ) + decorator( + Route.builder().path("/flow", "/custom-context").build(), + CoroutineContextService.newDecorator { User(name = "Armeria", role = "Admin") }, + ) + decorator( + Route.builder().path("/flow", "/custom-dispatcher").build(), + CoroutineContextService.newDecorator { + Executors + .newSingleThreadExecutor { Thread(it, "custom-thread") } + .asCoroutineDispatcher() + }, + ) + requestTimeoutMillis(2000L) + } } } - } lateinit var client: WebClient @@ -256,24 +277,25 @@ internal class FlowAnnotatedServiceTest { private data class Member( val name: String, - val age: Int + val age: Int, ) private data class User( val name: String, - val role: String + val role: String, ) : AbstractCoroutineContextElement(User) { companion object Key : CoroutineContext.Key } private infix fun HttpResponse.shouldProduce(expected: List) { - val pub = this.map { - when (it) { - is ResponseHeaders -> it.status() - is HttpData -> it.toStringUtf8() - else -> throw IllegalStateException() + val pub = + this.map { + when (it) { + is ResponseHeaders -> it.status() + is HttpData -> it.toStringUtf8() + else -> throw IllegalStateException() + } } - } StepVerifier.create(pub) .expectNext(HttpStatus.OK) .expectNextSequence(expected) diff --git a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/FlowCollectingPublisherTest.kt b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/FlowCollectingPublisherTest.kt index 566867580b7..086f4d54ca7 100644 --- a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/FlowCollectingPublisherTest.kt +++ b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/FlowCollectingPublisherTest.kt @@ -46,7 +46,7 @@ internal class FlowCollectingPublisherTest : PublisherVerification(TestEnv emit(it) } }, - eventLoopGroup.next() + eventLoopGroup.next(), ) override fun createFailedPublisher(): FlowCollectingPublisher = @@ -54,7 +54,7 @@ internal class FlowCollectingPublisherTest : PublisherVerification(TestEnv flow { throw Throwable() }, - eventLoopGroup.next() + eventLoopGroup.next(), ) @Test @@ -70,21 +70,23 @@ internal class FlowCollectingPublisherTest : PublisherVerification(TestEnv } }, executor = EventLoopGroups.directEventLoop(), - context = MoreExecutors.directExecutor().asCoroutineDispatcher() - ).subscribe(object : Subscriber { - override fun onSubscribe(s: Subscription) { - subscription = s - subscription.request(1L) - } + context = MoreExecutors.directExecutor().asCoroutineDispatcher(), + ).subscribe( + object : Subscriber { + override fun onSubscribe(s: Subscription) { + subscription = s + subscription.request(1L) + } - override fun onNext(t: Int) {} + override fun onNext(t: Int) {} - override fun onError(t: Throwable) {} + override fun onError(t: Throwable) {} - override fun onComplete() { - queue.add(-1) - } - }) + override fun onComplete() { + queue.add(-1) + } + }, + ) assertThat(queue.poll()).isEqualTo(0) assertThat(queue.poll(100L, TimeUnit.MILLISECONDS)).isNull() @@ -109,21 +111,23 @@ internal class FlowCollectingPublisherTest : PublisherVerification(TestEnv } }.buffer(capacity = 1), executor = EventLoopGroups.directEventLoop(), - context = MoreExecutors.directExecutor().asCoroutineDispatcher() - ).subscribe(object : Subscriber { - override fun onSubscribe(s: Subscription) { - subscription = s - subscription.request(1L) - } + context = MoreExecutors.directExecutor().asCoroutineDispatcher(), + ).subscribe( + object : Subscriber { + override fun onSubscribe(s: Subscription) { + subscription = s + subscription.request(1L) + } - override fun onNext(t: Int) {} + override fun onNext(t: Int) {} - override fun onError(t: Throwable) {} + override fun onError(t: Throwable) {} - override fun onComplete() { - queue.add(-1) - } - }) + override fun onComplete() { + queue.add(-1) + } + }, + ) assertThat(queue.poll()).isEqualTo(0) assertThat(queue.poll()).isEqualTo(1) @@ -149,16 +153,18 @@ internal class FlowCollectingPublisherTest : PublisherVerification(TestEnv emit(1) }, eventLoopGroup.next(), - context - ).subscribe(object : Subscriber { - override fun onSubscribe(s: Subscription) {} + context, + ).subscribe( + object : Subscriber { + override fun onSubscribe(s: Subscription) {} - override fun onNext(t: Int) {} + override fun onNext(t: Int) {} - override fun onError(t: Throwable) {} + override fun onError(t: Throwable) {} - override fun onComplete() {} - }) + override fun onComplete() {} + }, + ) await().untilAsserted { assertThat(coroutineNameCaptor.get()).isEqualTo(context) } } } diff --git a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/JacksonModuleAnnotatedServiceTest.kt b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/JacksonModuleAnnotatedServiceTest.kt index 14acdf760d9..bc39f34b238 100644 --- a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/JacksonModuleAnnotatedServiceTest.kt +++ b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/JacksonModuleAnnotatedServiceTest.kt @@ -32,16 +32,16 @@ import org.junit.jupiter.params.provider.CsvSource @GenerateNativeImageTrace class JacksonModuleAnnotatedServiceTest { - @CsvSource(value = ["/echo", "/echo-nullable"]) @ParameterizedTest fun shouldEncodeAndDecodeDataClassWithJson(path: String) { val client = WebClient.of(server.httpUri()) val json = """{"x": 10, "y":"hello"}""" - val response = client.prepare() - .post(path) - .content(MediaType.JSON, json) - .execute().aggregate().join() + val response = + client.prepare() + .post(path) + .content(MediaType.JSON, json) + .execute().aggregate().join() assertThatJson(response.contentUtf8()).isEqualTo(json) } @@ -50,10 +50,11 @@ class JacksonModuleAnnotatedServiceTest { fun shouldEncodeAndDecodeNullableDataClassWithJson(path: String) { val client = WebClient.of(server.httpUri()) val json = """{"x": 10}""" - val response = client.prepare() - .post(path) - .content(MediaType.JSON, json) - .execute().aggregate().join() + val response = + client.prepare() + .post(path) + .content(MediaType.JSON, json) + .execute().aggregate().join() if (path == "/echo") { assertThat(response.status()).isEqualTo(HttpStatus.BAD_REQUEST) } else { @@ -64,15 +65,15 @@ class JacksonModuleAnnotatedServiceTest { companion object { @JvmField @RegisterExtension - val server: ServerExtension = object : ServerExtension() { - override fun configure(sb: ServerBuilder) { - sb.annotatedService(ServiceWithDataClass()) + val server: ServerExtension = + object : ServerExtension() { + override fun configure(sb: ServerBuilder) { + sb.annotatedService(ServiceWithDataClass()) + } } - } } class ServiceWithDataClass { - @ProducesJson @Post("/echo") fun echo(foo: Foo): Foo { diff --git a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/Jsr305StrictTest.kt b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/Jsr305StrictTest.kt index 46285b39ed5..b6341e65473 100644 --- a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/Jsr305StrictTest.kt +++ b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/Jsr305StrictTest.kt @@ -20,7 +20,6 @@ import com.linecorp.armeria.common.logging.LogFormatter import org.junit.jupiter.api.Test class Jsr305StrictTest { - @Test fun shouldAllowReturningNulls() { // Make sure the code compiles with `-Xjsr305=strict` diff --git a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/NullableTypeSupportTest.kt b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/NullableTypeSupportTest.kt index 173ebde9484..4b2703927b5 100644 --- a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/NullableTypeSupportTest.kt +++ b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/NullableTypeSupportTest.kt @@ -44,8 +44,8 @@ class NullableTypeSupportTest { "/value-resolver/of-request-converter", "/value-resolver/of-bean-constructor", "/value-resolver/of-bean-field", - "/value-resolver/of-bean-method" - ] + "/value-resolver/of-bean-method", + ], ) fun test_nullableParameters(testPath: String) { testNullableParameters("/nullable-type/$testPath") @@ -76,84 +76,87 @@ class NullableTypeSupportTest { companion object { @JvmField @RegisterExtension - val server = object : ServerExtension() { - override fun configure(sb: ServerBuilder) { - sb.apply { - annotatedService( - "/nullable-type/value-resolver", - object { - @Get("/of-query-param") - fun ofQueryParam(@Param a: String, @Param b: String?) = - HttpResponse.of("a: $a, b: $b") - - @Get("/of-request-converter") - @RequestConverter(FooBarRequestConverter::class) - fun ofRequestConverter(foo: Foo, bar: Bar?) = - HttpResponse.of("a: ${foo.value}, b: ${bar?.value}") - - @Get("/of-bean-constructor") - fun ofBeanConstructor(baz: Baz) = - HttpResponse.of("a: ${baz.a}, b: ${baz.b}") - - @Get("/of-bean-field") - fun ofBeanField(qux: Qux) = - HttpResponse.of("a: ${qux.a}, b: ${qux.b}") - - @Get("/of-bean-method") - fun ofBeanMethod(quux: Quux) = - HttpResponse.of("a: ${quux.a}, b: ${quux.b}") - } - ) - sb.annotatedService( - "/nullable-annot/value-resolver", - object { - @Get("/of-query-param") - fun ofQueryParam( - @Param a: String, - @Nullable - @Param - b: String? - ) = - HttpResponse.of("a: $a, b: $b") - - @Get("/of-request-converter") - @RequestConverter(FooBarRequestConverter::class) - fun ofRequestConverter(foo: Foo, @Nullable bar: Bar?) = - HttpResponse.of("a: ${foo.value}, b: ${bar?.value}") - - @Get("/of-bean-constructor") - fun ofBeanConstructor(baz: Baz0) = - HttpResponse.of("a: ${baz.a}, b: ${baz.b}") - - @Get("/of-bean-field") - fun ofBeanField(qux: Qux0) = - HttpResponse.of("a: ${qux.a}, b: ${qux.b}") - - @Get("/of-bean-method") - fun ofBeanMethod(quux: Quux0) = - HttpResponse.of("a: ${quux.a}, b: ${quux.b}") - } - ) + val server = + object : ServerExtension() { + override fun configure(sb: ServerBuilder) { + sb.apply { + annotatedService( + "/nullable-type/value-resolver", + object { + @Get("/of-query-param") + fun ofQueryParam( + @Param a: String, + @Param b: String?, + ) = HttpResponse.of("a: $a, b: $b") + + @Get("/of-request-converter") + @RequestConverter(FooBarRequestConverter::class) + fun ofRequestConverter( + foo: Foo, + bar: Bar?, + ) = HttpResponse.of("a: ${foo.value}, b: ${bar?.value}") + + @Get("/of-bean-constructor") + fun ofBeanConstructor(baz: Baz) = HttpResponse.of("a: ${baz.a}, b: ${baz.b}") + + @Get("/of-bean-field") + fun ofBeanField(qux: Qux) = HttpResponse.of("a: ${qux.a}, b: ${qux.b}") + + @Get("/of-bean-method") + fun ofBeanMethod(quux: Quux) = HttpResponse.of("a: ${quux.a}, b: ${quux.b}") + }, + ) + sb.annotatedService( + "/nullable-annot/value-resolver", + object { + @Get("/of-query-param") + fun ofQueryParam( + @Param a: String, + @Nullable + @Param + b: String?, + ) = HttpResponse.of("a: $a, b: $b") + + @Get("/of-request-converter") + @RequestConverter(FooBarRequestConverter::class) + fun ofRequestConverter( + foo: Foo, + @Nullable bar: Bar?, + ) = HttpResponse.of("a: ${foo.value}, b: ${bar?.value}") + + @Get("/of-bean-constructor") + fun ofBeanConstructor(baz: Baz0) = HttpResponse.of("a: ${baz.a}, b: ${baz.b}") + + @Get("/of-bean-field") + fun ofBeanField(qux: Qux0) = HttpResponse.of("a: ${qux.a}, b: ${qux.b}") + + @Get("/of-bean-method") + fun ofBeanMethod(quux: Quux0) = HttpResponse.of("a: ${quux.a}, b: ${quux.b}") + }, + ) + } } } - } data class Foo( - val value: String + val value: String, ) data class Bar( - val value: String + val value: String, ) - class Baz(@Param("a") val a: String, @Param("b") val b: String?) + class Baz( + @Param("a") val a: String, + @Param("b") val b: String?, + ) // Check for backward-compatibility class Baz0( @Param("a") val a: String, @Nullable @Param("b") - val b: String? + val b: String?, ) class Qux { @@ -177,7 +180,10 @@ class NullableTypeSupportTest { lateinit var a: String var b: String? = null - fun setter(@Param("a") a: String, @Param("b") b: String?) { + fun setter( + @Param("a") a: String, + @Param("b") b: String?, + ) { this.a = a this.b = b } @@ -192,7 +198,7 @@ class NullableTypeSupportTest { @Param("a") a: String, @Nullable @Param("b") - b: String? + b: String?, ) { this.a = a this.b = b @@ -204,7 +210,7 @@ class NullableTypeSupportTest { ctx: ServiceRequestContext, request: AggregatedHttpRequest, expectedResultType: Class<*>, - expectedParameterizedResultType: ParameterizedType? + expectedParameterizedResultType: ParameterizedType?, ): Any? { if (expectedResultType.isAssignableFrom(Foo::class.java)) { return ctx.queryParam("a")?.let { Foo(it) } diff --git a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/SuspendingAnnotatedServiceTest.kt b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/SuspendingAnnotatedServiceTest.kt index d1e5388bab7..b19f4699e36 100644 --- a/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/SuspendingAnnotatedServiceTest.kt +++ b/kotlin/src/test/kotlin/com/linecorp/armeria/server/kotlin/SuspendingAnnotatedServiceTest.kt @@ -62,7 +62,6 @@ import kotlin.coroutines.coroutineContext @GenerateNativeImageTrace @Suppress("RedundantSuspendModifier", "unused") class SuspendingAnnotatedServiceTest { - @Test fun test_response_compatible() { get("/default/string").let { @@ -164,149 +163,158 @@ class SuspendingAnnotatedServiceTest { @JvmField @RegisterExtension - val server: ServerExtension = object : ServerExtension() { - override fun configure(serverBuilder: ServerBuilder) { - serverBuilder - .annotatedServiceExtensions( - emptyList(), - listOf(customJacksonResponseConverterFunction()), - listOf(exceptionHandlerFunction()) - ) - .annotatedService( - "/default", - object { - @Get("/string") - suspend fun string(): String { - assertInEventLoop() - return "OK" - } - - @Get("/responseObject") - @ProducesJson - suspend fun responseObject(@Param("a") a: String, @Param("b") b: Int): MyResponse { - assertInEventLoop() - return MyResponse(a = a, b = b) - } - - @Get("/httpResponse/{msg}") - suspend fun httpResponse(@Param("msg") msg: String): HttpResponse { - assertInEventLoop() - return HttpResponse.of(msg) - } - - @Get("/httpResult/{msg}") - suspend fun httpResult(@Param("msg") msg: String): HttpResult { - assertInEventLoop() - return HttpResult.of( - ResponseHeaders.of(200), - msg - ) - } - - @Get("/throwException") - suspend fun throwException(): HttpResponse { - ServiceRequestContext.current() - throw RuntimeException() - } - - @Delete("/noContent") - suspend fun noContent() { - ServiceRequestContext.current() - } - - @Get("/context") - suspend fun bar(): String { - assertInEventLoop() - withContext(Dispatchers.Default) { - delay(1) + val server: ServerExtension = + object : ServerExtension() { + override fun configure(serverBuilder: ServerBuilder) { + serverBuilder + .annotatedServiceExtensions( + emptyList(), + listOf(customJacksonResponseConverterFunction()), + listOf(exceptionHandlerFunction()), + ) + .annotatedService( + "/default", + object { + @Get("/string") + suspend fun string(): String { + assertInEventLoop() + return "OK" + } + + @Get("/responseObject") + @ProducesJson + suspend fun responseObject( + @Param("a") a: String, + @Param("b") b: Int, + ): MyResponse { + assertInEventLoop() + return MyResponse(a = a, b = b) } - assertInEventLoop() - return "OK" - } - } - ) - .annotatedService( - "/customContext", - object { - @Get("/foo") - suspend fun foo(): String { - ServiceRequestContext.current() - assertThat(coroutineContext[CoroutineName]?.name).isEqualTo("test") - return "OK" - } - } - ) - .decoratorUnder( - "/customContext", - CoroutineContextService.newDecorator { - Dispatchers.Default + CoroutineName("test") - } - ) - .annotatedService( - "/blocking", - object { - @Blocking - @Get("/baz") - suspend fun baz(): String { - ServiceRequestContext.current() - assertThat(Thread.currentThread().name).contains("armeria-common-blocking-tasks") - return "OK" - } - } - ) - .annotatedService( - "/downstream-cancellation", - object { - @Get("/long-running-suspend-fun") - suspend fun longRunningSuspendFun(): String { - try { - delay(10000L) - } catch (e: CancellationException) { - cancellationCallCounter.incrementAndGet() - throw e + + @Get("/httpResponse/{msg}") + suspend fun httpResponse( + @Param("msg") msg: String, + ): HttpResponse { + assertInEventLoop() + return HttpResponse.of(msg) + } + + @Get("/httpResult/{msg}") + suspend fun httpResult( + @Param("msg") msg: String, + ): HttpResult { + assertInEventLoop() + return HttpResult.of( + ResponseHeaders.of(200), + msg, + ) } - return "OK" - } - - @Get("/long-running-suspend-fun-ignore-exception") - suspend fun unsafeLongRunningSuspendFun(): HttpResponse { - try { - delay(10000L) - } catch (ignored: CancellationException) { - cancellationCallCounter.incrementAndGet() + + @Get("/throwException") + suspend fun throwException(): HttpResponse { + ServiceRequestContext.current() + throw RuntimeException() + } + + @Delete("/noContent") + suspend fun noContent() { + ServiceRequestContext.current() + } + + @Get("/context") + suspend fun bar(): String { + assertInEventLoop() + withContext(Dispatchers.Default) { + delay(1) + } + assertInEventLoop() + return "OK" } - val response = HttpResponse.of("OK") - httpResponseRef.set(response) - return response - } - } - ) - .annotatedService( - "/response-converter-spi", - object { - @Get("/bar") - suspend fun bar() = Bar() - - @Get("/bar-in-http-result") - suspend fun barInHttpResult() = HttpResult.of( - ResponseHeaders.of(HttpStatus.BAD_REQUEST, "x-custom-header", "value"), - Bar() - ) - } - ) - .annotatedService( - "/return-nothing-suspend-fun", - object { - @Get("/throw-error") - suspend fun returnNothingSuspendFun(): Nothing { - throw NotImplementedError() - } - } - ) - .decorator(LoggingService.newDecorator()) - .requestTimeoutMillis(500L) // to test cancellation + }, + ) + .annotatedService( + "/customContext", + object { + @Get("/foo") + suspend fun foo(): String { + ServiceRequestContext.current() + assertThat(coroutineContext[CoroutineName]?.name).isEqualTo("test") + return "OK" + } + }, + ) + .decoratorUnder( + "/customContext", + CoroutineContextService.newDecorator { + Dispatchers.Default + CoroutineName("test") + }, + ) + .annotatedService( + "/blocking", + object { + @Blocking + @Get("/baz") + suspend fun baz(): String { + ServiceRequestContext.current() + assertThat(Thread.currentThread().name).contains("armeria-common-blocking-tasks") + return "OK" + } + }, + ) + .annotatedService( + "/downstream-cancellation", + object { + @Get("/long-running-suspend-fun") + suspend fun longRunningSuspendFun(): String { + try { + delay(10000L) + } catch (e: CancellationException) { + cancellationCallCounter.incrementAndGet() + throw e + } + return "OK" + } + + @Get("/long-running-suspend-fun-ignore-exception") + suspend fun unsafeLongRunningSuspendFun(): HttpResponse { + try { + delay(10000L) + } catch (ignored: CancellationException) { + cancellationCallCounter.incrementAndGet() + } + val response = HttpResponse.of("OK") + httpResponseRef.set(response) + return response + } + }, + ) + .annotatedService( + "/response-converter-spi", + object { + @Get("/bar") + suspend fun bar() = Bar() + + @Get("/bar-in-http-result") + suspend fun barInHttpResult() = + HttpResult.of( + ResponseHeaders.of(HttpStatus.BAD_REQUEST, "x-custom-header", "value"), + Bar(), + ) + }, + ) + .annotatedService( + "/return-nothing-suspend-fun", + object { + @Get("/throw-error") + suspend fun returnNothingSuspendFun(): Nothing { + throw NotImplementedError() + } + }, + ) + .decorator(LoggingService.newDecorator()) + .requestTimeoutMillis(500L) // to test cancellation + } } - } private fun customJacksonResponseConverterFunction(): JacksonResponseConverterFunction { val objectMapper = ObjectMapper() @@ -314,14 +322,15 @@ class SuspendingAnnotatedServiceTest { return JacksonResponseConverterFunction(objectMapper) } - private fun exceptionHandlerFunction() = ExceptionHandlerFunction { _, _, cause -> - log.info(cause.message, cause) - HttpResponse.of( - HttpStatus.INTERNAL_SERVER_ERROR, - MediaType.PLAIN_TEXT_UTF_8, - cause.javaClass.simpleName - ) - } + private fun exceptionHandlerFunction() = + ExceptionHandlerFunction { _, _, cause -> + log.info(cause.message, cause) + HttpResponse.of( + HttpStatus.INTERNAL_SERVER_ERROR, + MediaType.PLAIN_TEXT_UTF_8, + cause.javaClass.simpleName, + ) + } private fun get(path: String): AggregatedHttpResponse { val webClient = WebClient.of(server.httpUri()).blocking() @@ -335,7 +344,7 @@ class SuspendingAnnotatedServiceTest { private fun assertInEventLoop() { assertThat( - ServiceRequestContext.current().eventLoop().inEventLoop() + ServiceRequestContext.current().eventLoop().inEventLoop(), ).isTrue() } @@ -345,7 +354,7 @@ class SuspendingAnnotatedServiceTest { internal class BarResponseConverterFunctionProvider : DelegatingResponseConverterFunctionProvider { override fun createResponseConverterFunction( returnType: Type, - responseConverter: ResponseConverterFunction + responseConverter: ResponseConverterFunction, ): ResponseConverterFunction? = returnType.toClass()?.let { when { @@ -363,13 +372,13 @@ class SuspendingAnnotatedServiceTest { } private class BarResponseConverterFunction( - private val responseConverter: ResponseConverterFunction + private val responseConverter: ResponseConverterFunction, ) : ResponseConverterFunction { override fun convertResponse( ctx: ServiceRequestContext, headers: ResponseHeaders, result: Any?, - trailers: HttpHeaders + trailers: HttpHeaders, ): HttpResponse = when (result) { is Bar -> responseConverter.convertResponse(ctx, headers, "hello, bar!", trailers) diff --git a/settings.gradle b/settings.gradle index 7182c2ca11e..60705d7e25a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -249,7 +249,7 @@ includeWithFlags ':examples:dropwizard-example', 'java11' project(':examples:dropwizard-example').projectDir = file('examples/dropwizard') includeWithFlags ':examples:graphql-example', 'java11' project(':examples:graphql-example').projectDir = file('examples/graphql') -includeWithFlags ':examples:graphql-kotlin-example', 'java11', 'kotlin' +includeWithFlags ':examples:graphql-kotlin-example', 'java17', 'kotlin' project(':examples:graphql-kotlin-example').projectDir = file('examples/graphql-kotlin') includeWithFlags ':examples:graphql-sangria-example', 'java11', 'scala_2.13' project(':examples:graphql-sangria-example').projectDir = file('examples/graphql-sangria') diff --git a/thrift/thrift0.13/src/test/java/com/linecorp/armeria/server/thrift/ThriftSerializationFormatsTest.java b/thrift/thrift0.13/src/test/java/com/linecorp/armeria/server/thrift/ThriftSerializationFormatsTest.java index 568b1a3a573..9438d00479a 100644 --- a/thrift/thrift0.13/src/test/java/com/linecorp/armeria/server/thrift/ThriftSerializationFormatsTest.java +++ b/thrift/thrift0.13/src/test/java/com/linecorp/armeria/server/thrift/ThriftSerializationFormatsTest.java @@ -25,20 +25,16 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import java.nio.charset.StandardCharsets; - -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; import org.apache.thrift.transport.TTransportException; import org.junit.ClassRule; import org.junit.Test; +import com.linecorp.armeria.client.BlockingWebClient; import com.linecorp.armeria.client.InvalidResponseHeadersException; import com.linecorp.armeria.client.thrift.ThriftClients; +import com.linecorp.armeria.common.AggregatedHttpResponse; import com.linecorp.armeria.common.HttpHeaderNames; +import com.linecorp.armeria.common.HttpStatus; import com.linecorp.armeria.common.Scheme; import com.linecorp.armeria.common.SessionProtocol; import com.linecorp.armeria.server.ServerBuilder; @@ -148,21 +144,18 @@ public void acceptNotSameAsContentType() throws Exception { @Test public void defaultSerializationFormat() throws Exception { - try (CloseableHttpClient hc = HttpClients.createMinimal()) { - // Send a TTEXT request with content type 'application/x-thrift' without 'protocol' parameter. - final HttpPost req = new HttpPost(server.httpUri() + "/hellotextonly"); - req.setHeader("Content-type", "application/x-thrift"); - req.setEntity(new StringEntity( - '{' + - " \"method\": \"hello\"," + - " \"type\":\"CALL\"," + - " \"args\": { \"name\": \"trustin\"}" + - '}', StandardCharsets.UTF_8)); - - try (CloseableHttpResponse res = hc.execute(req)) { - assertThat(res.getStatusLine().toString()).isEqualTo("HTTP/1.1 200 OK"); - } - } + final BlockingWebClient client = server.blockingWebClient(); + final AggregatedHttpResponse response = + client.prepare() + .post("/hellotextonly") + .content(parse("application/x-thrift"), + "{" + + " \"method\": \"hello\"," + + " \"type\":\"CALL\"," + + " \"args\": { \"name\": \"trustin\"}" + + '}') + .execute(); + assertThat(response.status()).isEqualTo(HttpStatus.OK); } @Test diff --git a/tomcat8/src/main/java/com/linecorp/armeria/server/tomcat/ArmeriaEndpoint.java b/tomcat8/src/main/java/com/linecorp/armeria/server/tomcat/ArmeriaEndpoint.java index bce0c0a932c..f4aa0cd2f48 100644 --- a/tomcat8/src/main/java/com/linecorp/armeria/server/tomcat/ArmeriaEndpoint.java +++ b/tomcat8/src/main/java/com/linecorp/armeria/server/tomcat/ArmeriaEndpoint.java @@ -40,6 +40,9 @@ private ArmeriaEndpoint() {} @Override protected void createSSLContext(SSLHostConfig sslHostConfig) throws Exception {} + @Override + protected void setDefaultSslHostConfig(SSLHostConfig sslHostConfig) {} + @Override protected InetSocketAddress getLocalAddress() throws IOException { // Doesn't seem to be used.