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()); + } + +}