From 57dc8199fb7e3781a8b2a083e85ac0debe35b8bf Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 20 Nov 2015 12:48:24 -0500 Subject: [PATCH] Add ReactiveHttpFilter --- .../http/server/FilterChainHttpHandler.java | 68 +++++++++ .../http/server/ReactiveHttpFilter.java | 30 ++++ .../http/server/ReactiveHttpFilterChain.java | 31 ++++ .../server/FilterChainHttpHandlerTests.java | 142 ++++++++++++++++++ ...mpleUrlHandlerMappingIntegrationTests.java | 2 +- 5 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 spring-web-reactive/src/main/java/org/springframework/http/server/FilterChainHttpHandler.java create mode 100644 spring-web-reactive/src/main/java/org/springframework/http/server/ReactiveHttpFilter.java create mode 100644 spring-web-reactive/src/main/java/org/springframework/http/server/ReactiveHttpFilterChain.java create mode 100644 spring-web-reactive/src/test/java/org/springframework/http/server/FilterChainHttpHandlerTests.java diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/FilterChainHttpHandler.java b/spring-web-reactive/src/main/java/org/springframework/http/server/FilterChainHttpHandler.java new file mode 100644 index 0000000000..4550a45212 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/http/server/FilterChainHttpHandler.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2015 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 + * + * http://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.http.server; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.reactivestreams.Publisher; + +import org.springframework.util.Assert; + +/** + * An {@link ReactiveHttpHandler} decorator that delegates to a list of + * {@link ReactiveHttpFilter}s and the target {@link ReactiveHttpHandler}. + * + * @author Rossen Stoyanchev + */ +public class FilterChainHttpHandler implements ReactiveHttpHandler { + + private final List filters; + + private final ReactiveHttpHandler targetHandler; + + + public FilterChainHttpHandler(ReactiveHttpHandler targetHandler, ReactiveHttpFilter... filters) { + Assert.notNull(targetHandler, "'targetHandler' is required."); + this.filters = (filters != null ? Arrays.asList(filters) : Collections.emptyList()); + this.targetHandler = targetHandler; + } + + + @Override + public Publisher handle(ReactiveServerHttpRequest request, ReactiveServerHttpResponse response) { + return new DefaultHttpFilterChain().filter(request, response); + } + + + private class DefaultHttpFilterChain implements ReactiveHttpFilterChain { + + private int index; + + @Override + public Publisher filter(ReactiveServerHttpRequest request, ReactiveServerHttpResponse response) { + if (this.index < filters.size()) { + ReactiveHttpFilter filter = filters.get(this.index++); + return filter.filter(request, response, this); + } + else { + return targetHandler.handle(request, response); + } + } + } + +} diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/ReactiveHttpFilter.java b/spring-web-reactive/src/main/java/org/springframework/http/server/ReactiveHttpFilter.java new file mode 100644 index 0000000000..e189e0f777 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/http/server/ReactiveHttpFilter.java @@ -0,0 +1,30 @@ +/* + * Copyright 2002-2015 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 + * + * http://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.http.server; + +import org.reactivestreams.Publisher; + +/** + * @author Rossen Stoyanchev + */ +public interface ReactiveHttpFilter { + + + Publisher filter(ReactiveServerHttpRequest request, ReactiveServerHttpResponse response, + ReactiveHttpFilterChain chain); + +} diff --git a/spring-web-reactive/src/main/java/org/springframework/http/server/ReactiveHttpFilterChain.java b/spring-web-reactive/src/main/java/org/springframework/http/server/ReactiveHttpFilterChain.java new file mode 100644 index 0000000000..0ea094ff83 --- /dev/null +++ b/spring-web-reactive/src/main/java/org/springframework/http/server/ReactiveHttpFilterChain.java @@ -0,0 +1,31 @@ +/* + * Copyright 2002-2015 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 + * + * http://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.http.server; + +import org.reactivestreams.Publisher; + +import org.springframework.http.server.ReactiveServerHttpRequest; +import org.springframework.http.server.ReactiveServerHttpResponse; + + +/** + * @author Rossen Stoyanchev + */ +public interface ReactiveHttpFilterChain { + + Publisher filter(ReactiveServerHttpRequest request, ReactiveServerHttpResponse response); + +} diff --git a/spring-web-reactive/src/test/java/org/springframework/http/server/FilterChainHttpHandlerTests.java b/spring-web-reactive/src/test/java/org/springframework/http/server/FilterChainHttpHandlerTests.java new file mode 100644 index 0000000000..3617318e53 --- /dev/null +++ b/spring-web-reactive/src/test/java/org/springframework/http/server/FilterChainHttpHandlerTests.java @@ -0,0 +1,142 @@ +/* + * Copyright 2002-2015 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 + * + * http://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.http.server; + + +import java.util.concurrent.TimeUnit; + +import org.junit.Before; +import org.junit.Test; +import org.reactivestreams.Publisher; +import reactor.Publishers; +import reactor.rx.Streams; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +/** + * @author Rossen Stoyanchev + */ +public class FilterChainHttpHandlerTests { + + private ReactiveServerHttpRequest request; + + private ReactiveServerHttpResponse response; + + + @Before + public void setUp() throws Exception { + this.request = mock(ReactiveServerHttpRequest.class); + this.response = mock(ReactiveServerHttpResponse.class); + } + + @Test + public void multipleFilters() throws Exception { + StubHandler handler = new StubHandler(); + TestFilter filter1 = new TestFilter(); + TestFilter filter2 = new TestFilter(); + TestFilter filter3 = new TestFilter(); + FilterChainHttpHandler filterHandler = new FilterChainHttpHandler(handler, filter1, filter2, filter3); + + Publisher voidPublisher = filterHandler.handle(this.request, this.response); + Streams.wrap(voidPublisher).toList().await(10, TimeUnit.SECONDS); + + assertTrue(filter1.invoked()); + assertTrue(filter2.invoked()); + assertTrue(filter3.invoked()); + assertTrue(handler.invoked()); + } + + @Test + public void zeroFilters() throws Exception { + StubHandler handler = new StubHandler(); + FilterChainHttpHandler filterHandler = new FilterChainHttpHandler(handler); + + Publisher voidPublisher = filterHandler.handle(this.request, this.response); + Streams.wrap(voidPublisher).toList().await(10, TimeUnit.SECONDS); + + assertTrue(handler.invoked()); + } + + @Test + public void shortcircuitFilter() throws Exception { + StubHandler handler = new StubHandler(); + TestFilter filter1 = new TestFilter(); + ShortcircuitingFilter filter2 = new ShortcircuitingFilter(); + TestFilter filter3 = new TestFilter(); + FilterChainHttpHandler filterHandler = new FilterChainHttpHandler(handler, filter1, filter2, filter3); + + Publisher voidPublisher = filterHandler.handle(this.request, this.response); + Streams.wrap(voidPublisher).toList().await(10, TimeUnit.SECONDS); + + assertTrue(filter1.invoked()); + assertTrue(filter2.invoked()); + assertFalse(filter3.invoked()); + assertFalse(handler.invoked()); + } + + + private static class TestFilter implements ReactiveHttpFilter { + + private boolean invoked; + + + public boolean invoked() { + return this.invoked; + } + + @Override + public Publisher filter(ReactiveServerHttpRequest req, ReactiveServerHttpResponse res, + ReactiveHttpFilterChain chain) { + + this.invoked = true; + return doFilter(req, res, chain); + } + + public Publisher doFilter(ReactiveServerHttpRequest req, ReactiveServerHttpResponse res, + ReactiveHttpFilterChain chain) { + + return chain.filter(req, res); + } + } + + private static class ShortcircuitingFilter extends TestFilter { + + @Override + public Publisher doFilter(ReactiveServerHttpRequest req, ReactiveServerHttpResponse res, + ReactiveHttpFilterChain chain) { + + return Publishers.empty(); + } + } + + private static class StubHandler implements ReactiveHttpHandler { + + private boolean invoked; + + public boolean invoked() { + return this.invoked; + } + + @Override + public Publisher handle(ReactiveServerHttpRequest req, ReactiveServerHttpResponse res) { + this.invoked = true; + return Publishers.empty(); + } + } + +} diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java index 15788670b3..94bf276d1d 100644 --- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java +++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/handler/SimpleUrlHandlerMappingIntegrationTests.java @@ -55,7 +55,7 @@ public class SimpleUrlHandlerMappingIntegrationTests extends AbstractHttpHandler StaticWebApplicationContext wac = new StaticWebApplicationContext(); wac.registerSingleton("hm", TestHandlerMapping.class); wac.registerSingleton("ha", HttpHandlerAdapter.class); - wac.registerSingleton("hhrh", SimpleHandlerResultHandler.class); + wac.registerSingleton("rh", SimpleHandlerResultHandler.class); wac.refresh(); DispatcherHandler dispatcherHandler = new DispatcherHandler();