From 505cad48c1c2f0327a4b45c50fa35e5f3d46deb7 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 1 Jun 2015 13:23:22 -0700 Subject: [PATCH] Add remote server support classes Add MVC style Dispatcher, Mapper and Handler classes to support remote server calls. Spring MVC is not used directly since it may not be on the classpath (for example if the user is developing a Jersey application). See gh-3086 --- .../remote/server/AccessManager.java | 49 +++++++ .../remote/server/Dispatcher.java | 81 +++++++++++ .../remote/server/DispatcherFilter.java | 81 +++++++++++ .../developertools/remote/server/Handler.java | 41 ++++++ .../remote/server/HandlerMapper.java | 37 +++++ .../remote/server/HttpStatusHandler.java | 59 ++++++++ .../remote/server/UrlHandlerMapper.java | 55 +++++++ .../remote/server/package-info.java | 21 +++ .../remote/server/DispatcherFilterTests.java | 120 ++++++++++++++++ .../remote/server/DispatcherTests.java | 135 ++++++++++++++++++ .../remote/server/HttpStatusHandlerTests.java | 81 +++++++++++ .../remote/server/UrlHandlerMapperTests.java | 84 +++++++++++ 12 files changed, 844 insertions(+) create mode 100644 spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/AccessManager.java create mode 100644 spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/Dispatcher.java create mode 100644 spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/DispatcherFilter.java create mode 100644 spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/Handler.java create mode 100644 spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/HandlerMapper.java create mode 100644 spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/HttpStatusHandler.java create mode 100644 spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/UrlHandlerMapper.java create mode 100644 spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/package-info.java create mode 100644 spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/DispatcherFilterTests.java create mode 100644 spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/DispatcherTests.java create mode 100644 spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/HttpStatusHandlerTests.java create mode 100644 spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/UrlHandlerMapperTests.java diff --git a/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/AccessManager.java b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/AccessManager.java new file mode 100644 index 0000000000..3922a437ce --- /dev/null +++ b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/AccessManager.java @@ -0,0 +1,49 @@ +/* + * Copyright 2012-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.boot.developertools.remote.server; + +import org.springframework.http.server.ServerHttpRequest; + +/** + * Provides access control for a {@link Dispatcher}. + * + * @author Phillip Webb + * @since 1.3.0 + */ +public interface AccessManager { + + /** + * {@link AccessManager} that permits all requests. + */ + public static final AccessManager PERMIT_ALL = new AccessManager() { + + @Override + public boolean isAllowed(ServerHttpRequest request) { + return true; + } + + }; + + /** + * Determine if the specific request is allowed to be handled by the + * {@link Dispatcher}. + * @param request the request to check + * @return {@code true} if access is allowed. + */ + boolean isAllowed(ServerHttpRequest request); + +} diff --git a/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/Dispatcher.java b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/Dispatcher.java new file mode 100644 index 0000000000..8bb3239de8 --- /dev/null +++ b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/Dispatcher.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-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.boot.developertools.remote.server; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.util.Assert; + +/** + * Dispatcher used to route incoming remote server requests to a {@link Handler}. Similar + * to {@code DispatchServlet} in Spring MVC but separate to ensure that remote support can + * be used regardless of any web framework. + * + * @author Phillip Webb + * @since 1.3.0 + * @see HandlerMapper + */ +public class Dispatcher { + + private final AccessManager accessManager; + + private final List mappers; + + public Dispatcher(AccessManager accessManager, Collection mappers) { + Assert.notNull(accessManager, "AccessManager must not be null"); + Assert.notNull(mappers, "Mappers must not be null"); + this.accessManager = accessManager; + this.mappers = new ArrayList(mappers); + AnnotationAwareOrderComparator.sort(this.mappers); + } + + /** + * Dispatch the specified request to an appropriate {@link Handler}. + * @param request the request + * @param response the response + * @return {@code true} if the request was dispatched + * @throws IOException + */ + public boolean handle(ServerHttpRequest request, ServerHttpResponse response) + throws IOException { + for (HandlerMapper mapper : this.mappers) { + Handler handler = mapper.getHandler(request); + if (handler != null) { + handle(handler, request, response); + return true; + } + } + return false; + } + + private void handle(Handler handler, ServerHttpRequest request, + ServerHttpResponse response) throws IOException { + if (!this.accessManager.isAllowed(request)) { + response.setStatusCode(HttpStatus.FORBIDDEN); + return; + } + handler.handle(request, response); + } + +} diff --git a/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/DispatcherFilter.java b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/DispatcherFilter.java new file mode 100644 index 0000000000..4fb157dbf0 --- /dev/null +++ b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/DispatcherFilter.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-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.boot.developertools.remote.server; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.util.Assert; + +/** + * Servlet filter providing integration with the remote server {@link Dispatcher}. + * + * @author Phillip Webb + * @author Rob Winch + * @since 1.3.0 + */ +public class DispatcherFilter implements Filter { + + private final Dispatcher dispatcher; + + public DispatcherFilter(Dispatcher dispatcher) { + Assert.notNull(dispatcher, "Dispatcher must not be null"); + this.dispatcher = dispatcher; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain chain) throws IOException, ServletException { + if (request instanceof HttpServletRequest + && response instanceof HttpServletResponse) { + doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain); + } + else { + chain.doFilter(request, response); + } + } + + private void doFilter(HttpServletRequest request, HttpServletResponse response, + FilterChain chain) throws IOException, ServletException { + ServerHttpRequest serverRequest = new ServletServerHttpRequest(request); + ServerHttpResponse serverResponse = new ServletServerHttpResponse(response); + if (!this.dispatcher.handle(serverRequest, serverResponse)) { + chain.doFilter(request, response); + } + } + + @Override + public void destroy() { + } + +} diff --git a/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/Handler.java b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/Handler.java new file mode 100644 index 0000000000..b32b2062e3 --- /dev/null +++ b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/Handler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-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.boot.developertools.remote.server; + +import java.io.IOException; + +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; + +/** + * A single handler that is able to process an incoming remote server request. + * + * @author Phillip Webb + * @since 1.3.0 + */ +public interface Handler { + + /** + * Handle the request. + * @param request the request + * @param response the response + * @throws IOException + */ + void handle(ServerHttpRequest request, ServerHttpResponse response) + throws IOException; + +} diff --git a/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/HandlerMapper.java b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/HandlerMapper.java new file mode 100644 index 0000000000..8b1881da91 --- /dev/null +++ b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/HandlerMapper.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012-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.boot.developertools.remote.server; + +import org.springframework.http.server.ServerHttpRequest; + +/** + * Interface to provide a mapping between a {@link ServerHttpRequest} and a + * {@link Handler}. + * + * @author Phillip Webb + * @since 1.3.0 + */ +public interface HandlerMapper { + + /** + * Return the handler for the given request or {@code null}. + * @param request the request + * @return a {@link Handler} or {@code null} + */ + Handler getHandler(ServerHttpRequest request); + +} diff --git a/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/HttpStatusHandler.java b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/HttpStatusHandler.java new file mode 100644 index 0000000000..76d0f50e16 --- /dev/null +++ b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/HttpStatusHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2012-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.boot.developertools.remote.server; + +import java.io.IOException; + +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.util.Assert; + +/** + * {@link Handler} that responds with a specific {@link HttpStatus}. + * + * @author Phillip Webb + */ +public class HttpStatusHandler implements Handler { + + private final HttpStatus status; + + /** + * Create a new {@link HttpStatusHandler} instance that will respond with a HTTP OK 200 + * status. + */ + public HttpStatusHandler() { + this(HttpStatus.OK); + } + + /** + * Create a new {@link HttpStatusHandler} instance that will respond with the specified + * status. + * @param status the status + */ + public HttpStatusHandler(HttpStatus status) { + Assert.notNull(status, "Status must not be null"); + this.status = status; + } + + @Override + public void handle(ServerHttpRequest request, ServerHttpResponse response) + throws IOException { + response.setStatusCode(this.status); + } + +} diff --git a/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/UrlHandlerMapper.java b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/UrlHandlerMapper.java new file mode 100644 index 0000000000..64a9b7ba47 --- /dev/null +++ b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/UrlHandlerMapper.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-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.boot.developertools.remote.server; + +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.util.Assert; + +/** + * {@link HandlerMapper} implementation that maps incoming URLs + * + * @author Rob Winch + * @author Phillip Webb + * @since 1.3.0 + */ +public class UrlHandlerMapper implements HandlerMapper { + + private final String requestUri; + + private final Handler hander; + + /** + * Create a new {@link UrlHandlerMapper}. + * @param url the URL to map + * @param handler the handler to use + */ + public UrlHandlerMapper(String url, Handler handler) { + Assert.hasLength(url, "URL must not be empty"); + Assert.isTrue(url.startsWith("/"), "URL must start with '/'"); + this.requestUri = url; + this.hander = handler; + } + + @Override + public Handler getHandler(ServerHttpRequest request) { + if (this.requestUri.equals(request.getURI().getPath())) { + return this.hander; + } + return null; + } + +} diff --git a/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/package-info.java b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/package-info.java new file mode 100644 index 0000000000..b4e66352dd --- /dev/null +++ b/spring-boot-developer-tools/src/main/java/org/springframework/boot/developertools/remote/server/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012-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. + */ + +/** + * Server support for a remotely running Spring Boot application. + */ +package org.springframework.boot.developertools.remote.server; + diff --git a/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/DispatcherFilterTests.java b/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/DispatcherFilterTests.java new file mode 100644 index 0000000000..ac1c34ee37 --- /dev/null +++ b/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/DispatcherFilterTests.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-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.boot.developertools.remote.server; + +import javax.servlet.FilterChain; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +/** + * Tests for {@link DispatcherFilter}. + * + * @author Phillip Webb + */ +public class DispatcherFilterTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Mock + private Dispatcher dispatcher; + + @Mock + private FilterChain chain; + + @Captor + private ArgumentCaptor serverResponseCaptor; + + @Captor + private ArgumentCaptor serverRequestCaptor; + + private DispatcherFilter filter; + + @Before + public void setup() throws Exception { + MockitoAnnotations.initMocks(this); + this.filter = new DispatcherFilter(this.dispatcher); + } + + @Test + public void dispatcherMustNotBeNull() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Dispatcher must not be null"); + new DispatcherFilter(null); + } + + @Test + public void ignoresNotServletRequests() throws Exception { + ServletRequest request = mock(ServletRequest.class); + ServletResponse response = mock(ServletResponse.class); + this.filter.doFilter(request, response, this.chain); + verifyZeroInteractions(this.dispatcher); + verify(this.chain).doFilter(request, response); + } + + @Test + public void ignoredByDispatcher() throws Exception { + HttpServletRequest request = new MockHttpServletRequest("GET", "/hello"); + HttpServletResponse response = new MockHttpServletResponse(); + this.filter.doFilter(request, response, this.chain); + verify(this.chain).doFilter(request, response); + } + + @Test + public void handledByDispatcher() throws Exception { + HttpServletRequest request = new MockHttpServletRequest("GET", "/hello"); + HttpServletResponse response = new MockHttpServletResponse(); + willReturn(true).given(this.dispatcher).handle(any(ServerHttpRequest.class), + any(ServerHttpResponse.class)); + this.filter.doFilter(request, response, this.chain); + verifyZeroInteractions(this.chain); + verify(this.dispatcher).handle(this.serverRequestCaptor.capture(), + this.serverResponseCaptor.capture()); + ServerHttpRequest dispatcherRequest = this.serverRequestCaptor.getValue(); + ServletServerHttpRequest actualRequest = (ServletServerHttpRequest) dispatcherRequest; + ServerHttpResponse dispatcherResponse = this.serverResponseCaptor.getValue(); + ServletServerHttpResponse actualResponse = (ServletServerHttpResponse) dispatcherResponse; + assertThat(actualRequest.getServletRequest(), equalTo(request)); + assertThat(actualResponse.getServletResponse(), equalTo(response)); + } + +} diff --git a/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/DispatcherTests.java b/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/DispatcherTests.java new file mode 100644 index 0000000000..882754a938 --- /dev/null +++ b/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/DispatcherTests.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012-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.boot.developertools.remote.server; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.core.Ordered; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.withSettings; + +/** + * Tests for {@link Dispatcher}. + * + * @author Phillip Webb + */ +public class DispatcherTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Mock + private AccessManager accessManager; + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + private ServerHttpRequest serverRequest; + + private ServerHttpResponse serverResponse; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + this.request = new MockHttpServletRequest(); + this.response = new MockHttpServletResponse(); + this.serverRequest = new ServletServerHttpRequest(this.request); + this.serverResponse = new ServletServerHttpResponse(this.response); + } + + @Test + public void accessManagerMustNotBeNull() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("AccessManager must not be null"); + new Dispatcher(null, Collections. emptyList()); + } + + @Test + public void mappersMustNotBeNull() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Mappers must not be null"); + new Dispatcher(this.accessManager, null); + } + + @Test + public void accessManagerVetoRequest() throws Exception { + given(this.accessManager.isAllowed(any(ServerHttpRequest.class))).willReturn( + false); + HandlerMapper mapper = mock(HandlerMapper.class); + Handler handler = mock(Handler.class); + given(mapper.getHandler(any(ServerHttpRequest.class))).willReturn(handler); + Dispatcher dispatcher = new Dispatcher(this.accessManager, + Collections.singleton(mapper)); + dispatcher.handle(this.serverRequest, this.serverResponse); + verifyZeroInteractions(handler); + assertThat(this.response.getStatus(), equalTo(403)); + } + + @Test + public void accessManagerAllowRequest() throws Exception { + given(this.accessManager.isAllowed(any(ServerHttpRequest.class))) + .willReturn(true); + HandlerMapper mapper = mock(HandlerMapper.class); + Handler handler = mock(Handler.class); + given(mapper.getHandler(any(ServerHttpRequest.class))).willReturn(handler); + Dispatcher dispatcher = new Dispatcher(this.accessManager, + Collections.singleton(mapper)); + dispatcher.handle(this.serverRequest, this.serverResponse); + verify(handler).handle(this.serverRequest, this.serverResponse); + } + + @Test + public void ordersMappers() throws Exception { + HandlerMapper mapper1 = mock(HandlerMapper.class, + withSettings().extraInterfaces(Ordered.class)); + HandlerMapper mapper2 = mock(HandlerMapper.class, + withSettings().extraInterfaces(Ordered.class)); + given(((Ordered) mapper1).getOrder()).willReturn(1); + given(((Ordered) mapper2).getOrder()).willReturn(2); + List mappers = Arrays.asList(mapper2, mapper1); + Dispatcher dispatcher = new Dispatcher(AccessManager.PERMIT_ALL, mappers); + dispatcher.handle(this.serverRequest, this.serverResponse); + InOrder inOrder = inOrder(mapper1, mapper2); + inOrder.verify(mapper1).getHandler(this.serverRequest); + inOrder.verify(mapper2).getHandler(this.serverRequest); + } + +} diff --git a/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/HttpStatusHandlerTests.java b/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/HttpStatusHandlerTests.java new file mode 100644 index 0000000000..954e5a0b78 --- /dev/null +++ b/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/HttpStatusHandlerTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-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.boot.developertools.remote.server; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.http.server.ServletServerHttpResponse; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +/** + * Tests for {@link HttpStatusHandler}. + * + * @author Phillip Webb + */ +public class HttpStatusHandlerTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private MockHttpServletRequest servletRequest; + + private MockHttpServletResponse servletResponse; + + private ServerHttpResponse response; + + private ServerHttpRequest request; + + @Before + public void setup() { + this.servletRequest = new MockHttpServletRequest(); + this.servletResponse = new MockHttpServletResponse(); + this.request = new ServletServerHttpRequest(this.servletRequest); + this.response = new ServletServerHttpResponse(this.servletResponse); + } + + @Test + public void statusMustNotBeNull() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("Status must not be null"); + new HttpStatusHandler(null); + } + + @Test + public void respondsOk() throws Exception { + HttpStatusHandler handler = new HttpStatusHandler(); + handler.handle(this.request, this.response); + assertThat(this.servletResponse.getStatus(), equalTo(200)); + } + + @Test + public void respondsWithStatus() throws Exception { + HttpStatusHandler handler = new HttpStatusHandler(HttpStatus.I_AM_A_TEAPOT); + handler.handle(this.request, this.response); + assertThat(this.servletResponse.getStatus(), equalTo(418)); + } + +} diff --git a/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/UrlHandlerMapperTests.java b/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/UrlHandlerMapperTests.java new file mode 100644 index 0000000000..deeba1c627 --- /dev/null +++ b/spring-boot-developer-tools/src/test/java/org/springframework/boot/developertools/remote/server/UrlHandlerMapperTests.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012-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.boot.developertools.remote.server; + +import javax.servlet.http.HttpServletRequest; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.mock.web.MockHttpServletRequest; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link UrlHandlerMapper}. + * + * @author Rob Winch + * @author Phillip Webb + */ +public class UrlHandlerMapperTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private Handler handler = mock(Handler.class); + + @Test + public void requestUriMustNotBeNull() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("URL must not be empty"); + new UrlHandlerMapper(null, this.handler); + } + + @Test + public void requestUriMustNotBeEmpty() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("URL must not be empty"); + new UrlHandlerMapper("", this.handler); + } + + @Test + public void requestUrlMustStartWithSlash() throws Exception { + this.thrown.expect(IllegalArgumentException.class); + this.thrown.expectMessage("URL must start with '/'"); + new UrlHandlerMapper("tunnel", this.handler); + } + + @Test + public void handlesMatchedUrl() throws Exception { + UrlHandlerMapper mapper = new UrlHandlerMapper("/tunnel", this.handler); + HttpServletRequest servletRequest = new MockHttpServletRequest("GET", "/tunnel"); + ServerHttpRequest request = new ServletServerHttpRequest(servletRequest); + assertThat(mapper.getHandler(request), equalTo(this.handler)); + } + + @Test + public void ignoresDifferentUrl() throws Exception { + UrlHandlerMapper mapper = new UrlHandlerMapper("/tunnel", this.handler); + HttpServletRequest servletRequest = new MockHttpServletRequest("GET", + "/tunnel/other"); + ServerHttpRequest request = new ServletServerHttpRequest(servletRequest); + assertThat(mapper.getHandler(request), nullValue()); + } + +}