From 3a585f8c295ae480b557f8d469243d5ae1553fa0 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Mon, 16 Jan 2023 11:00:33 +0100 Subject: [PATCH] Introduce CoWebFilter This commit introduces the CoWebFilter, an abstract WebFilter implementation that allows for coroutine usage. Closes gh-29672 --- spring-web/spring-web.gradle | 1 + .../springframework/web/server/CoWebFilter.kt | 66 +++++++++++++++++++ .../web/server/CoWebFilterTests.kt | 56 ++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 spring-web/src/main/kotlin/org/springframework/web/server/CoWebFilter.kt create mode 100644 spring-web/src/test/kotlin/org/springframework/web/server/CoWebFilterTests.kt diff --git a/spring-web/spring-web.gradle b/spring-web/spring-web.gradle index 4f04cfc6b4f..8f7091fec06 100644 --- a/spring-web/spring-web.gradle +++ b/spring-web/spring-web.gradle @@ -60,6 +60,7 @@ dependencies { optional("org.apache.groovy:groovy") optional("org.jetbrains.kotlin:kotlin-reflect") optional("org.jetbrains.kotlin:kotlin-stdlib") + optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") optional("org.jetbrains.kotlinx:kotlinx-serialization-cbor") optional("org.jetbrains.kotlinx:kotlinx-serialization-json") optional("org.jetbrains.kotlinx:kotlinx-serialization-protobuf") diff --git a/spring-web/src/main/kotlin/org/springframework/web/server/CoWebFilter.kt b/spring-web/src/main/kotlin/org/springframework/web/server/CoWebFilter.kt new file mode 100644 index 00000000000..d221e7a6004 --- /dev/null +++ b/spring-web/src/main/kotlin/org/springframework/web/server/CoWebFilter.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.server + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.reactor.awaitSingleOrNull +import kotlinx.coroutines.reactor.mono +import reactor.core.publisher.Mono + +/** + * Kotlin-specific implementation of the [WebFilter] interface that allows for + * using co-routines. + * + * @author Arjen Poutsma + * @since 6.0.5 + */ +abstract class CoWebFilter : WebFilter { + + final override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { + return mono(Dispatchers.Unconfined) { + filter(exchange, object : CoWebFilterChain { + override suspend fun filter(exchange: ServerWebExchange) { + return chain.filter(exchange).cast(Unit.javaClass).awaitSingleOrNull() ?: Unit + } + })}.then() + } + + /** + * Process the Web request and (optionally) delegate to the next + * `WebFilter` through the given [WebFilterChain]. + * @param exchange the current server exchange + * @param chain provides a way to delegate to the next filter + */ + protected abstract suspend fun filter(exchange: ServerWebExchange, chain: CoWebFilterChain) + +} + +/** + * Kotlin-specific adaption of [WebFilterChain] that allows for co-routines. + * + * @author Arjen Poutsma + * @since 6.0.5 + */ +interface CoWebFilterChain { + + /** + * Delegate to the next [WebFilter] in the chain. + * @param exchange the current server exchange + */ + suspend fun filter(exchange: ServerWebExchange) + +} \ No newline at end of file diff --git a/spring-web/src/test/kotlin/org/springframework/web/server/CoWebFilterTests.kt b/spring-web/src/test/kotlin/org/springframework/web/server/CoWebFilterTests.kt new file mode 100644 index 00000000000..af6b7dac81f --- /dev/null +++ b/spring-web/src/test/kotlin/org/springframework/web/server/CoWebFilterTests.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.server + +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test +import org.mockito.BDDMockito +import org.mockito.Mockito +import org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest +import org.springframework.web.testfixture.server.MockServerWebExchange +import reactor.core.publisher.Mono +import reactor.test.StepVerifier + +/** + * @author Arjen Poutsma + */ +class CoWebFilterTests { + + @Test + fun filter() { + val exchange = MockServerWebExchange.from(MockServerHttpRequest.get("https://example.com")) + + val chain = Mockito.mock(WebFilterChain::class.java) + BDDMockito.given(chain.filter(exchange)).willReturn(Mono.empty()) + + val filter = MyCoWebFilter() + val result = filter.filter(exchange, chain) + + StepVerifier.create(result) + .verifyComplete() + + assertThat(exchange.attributes["foo"]).isEqualTo("bar") + } +} + + +private class MyCoWebFilter : CoWebFilter() { + override suspend fun filter(exchange: ServerWebExchange, chain: CoWebFilterChain) { + exchange.attributes["foo"] = "bar" + chain.filter(exchange) + } +} \ No newline at end of file