Skip to content

Commit 641f660

Browse files
committed
Add option to configure the public url
In case the server is running behind a reverse proxy doing path- rewriting, the public url can now be specified so that back-references (e.g. base-path) is now correct. closes codecentric#983
1 parent 66e4189 commit 641f660

File tree

8 files changed

+110
-9
lines changed

8 files changed

+110
-9
lines changed

spring-boot-admin-docs/src/main/asciidoc/server.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
| Headers not to be forwarded when making requests to clients.
4747
| `"Cookie", "Set-Cookie", "Authorization"
4848

49+
| spring.boot.admin.ui.public-url
50+
| Brand to be shown in then navbar.
51+
| If running behind a reverse proxy (using path rewriting) this can be used to make correct self references. If the host/port is omitted it will be inferred from the request.
52+
4953
| spring.boot.admin.ui.brand
5054
| Brand to be shown in then navbar.
5155
| `"<img src="assets/img/icon-spring-boot-admin.svg"><span>Spring Boot Admin</span>"`

spring-boot-admin-server-ui/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@
5656
<groupId>com.google.code.findbugs</groupId>
5757
<artifactId>jsr305</artifactId>
5858
</dependency>
59+
<!-- Test -->
60+
<dependency>
61+
<groupId>org.springframework.boot</groupId>
62+
<artifactId>spring-boot-starter-test</artifactId>
63+
<scope>test</scope>
64+
</dependency>
5965
</dependencies>
6066
<build>
6167
<plugins>

spring-boot-admin-server-ui/src/main/frontend/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
<html>
2020
<head>
21-
<base th:href="@{${adminContextPath} + '/'}" href="/">
21+
<base th:href="${baseUrl}" href="/">
2222
<meta charset="utf-8">
2323
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
2424
<meta name="format-detection" content="telephone=no,email=no">

spring-boot-admin-server-ui/src/main/frontend/login.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
<html>
2020
<head>
21-
<base th:href="@{${adminContextPath} + '/'}" href="/">
21+
<base th:href="${baseUrl}" href="/">
2222
<meta charset="utf-8">
2323
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
2424
<meta name="format-detection" content="telephone=no,email=no">

spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/config/AdminServerUiAutoConfiguration.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ public AdminServerUiAutoConfiguration(AdminServerUiProperties uiProperties,
6464
@ConditionalOnMissingBean
6565
public UiController homeUiController() throws IOException {
6666
return new UiController(
67-
this.adminServerProperties.getContextPath(),
67+
this.uiProperties.getPublicUrl() !=
68+
null ? this.uiProperties.getPublicUrl() : this.adminServerProperties.getContextPath(),
6869
this.uiProperties.getTitle(),
6970
this.uiProperties.getBrand(),
7071
this.uiExtensions()

spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/config/AdminServerUiProperties.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ public class AdminServerUiProperties {
5555
*/
5656
private String brand = "<img src=\"assets/img/icon-spring-boot-admin.svg\"><span>Spring Boot Admin</span>";
5757

58+
/**
59+
* If running behind a reverse proxy (using path rewriting) this can be used to output correct self references.
60+
* If the host/port is omitted it will be inferred from the request.
61+
*/
62+
@Nullable
63+
private String publicUrl = null;
64+
65+
/**
66+
* Wether the thymeleaf templates should be cached.
67+
*/
5868
private boolean cacheTemplates = true;
5969

6070
private final Cache cache = new Cache();

spring-boot-admin-server-ui/src/main/java/de/codecentric/boot/admin/server/ui/web/UiController.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,21 @@
2828
import org.springframework.http.MediaType;
2929
import org.springframework.web.bind.annotation.GetMapping;
3030
import org.springframework.web.bind.annotation.ModelAttribute;
31+
import org.springframework.web.util.UriComponents;
32+
import org.springframework.web.util.UriComponentsBuilder;
3133

3234
import static java.util.Collections.emptyMap;
3335
import static java.util.Collections.singletonMap;
3436

3537
@AdminController
3638
public class UiController {
37-
private final String adminContextPath;
39+
private final String publicUrl;
3840
private final List<UiExtension> cssExtensions;
3941
private final List<UiExtension> jsExtensions;
4042
private final Map<String, Object> uiSettings;
4143

42-
public UiController(String adminContextPath, String title, String brand, List<UiExtension> uiExtensions) {
43-
this.adminContextPath = adminContextPath;
44+
public UiController(String publicUrl, String title, String brand, List<UiExtension> uiExtensions) {
45+
this.publicUrl = publicUrl;
4446
this.uiSettings = new HashMap<>();
4547
this.uiSettings.put("title", title);
4648
this.uiSettings.put("brand", brand);
@@ -52,9 +54,19 @@ public UiController(String adminContextPath, String title, String brand, List<Ui
5254
.collect(Collectors.toList());
5355
}
5456

55-
@ModelAttribute(value = "adminContextPath", binding = false)
56-
public String getAdminContextPath() {
57-
return adminContextPath;
57+
@ModelAttribute(value = "baseUrl", binding = false)
58+
public String getBaseUrl(UriComponentsBuilder uriBuilder) {
59+
UriComponents publicComponents = UriComponentsBuilder.fromUriString(publicUrl).build();
60+
if (publicComponents.getHost() != null) {
61+
uriBuilder.host(publicComponents.getHost());
62+
}
63+
if (publicComponents.getPort() != -1) {
64+
uriBuilder.port(publicComponents.getPort());
65+
}
66+
if (publicComponents.getPath() != null) {
67+
uriBuilder.path(publicComponents.getPath());
68+
}
69+
return uriBuilder.path("/").toUriString();
5870
}
5971

6072
@ModelAttribute(value = "uiSettings", binding = false)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2014-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package de.codecentric.boot.admin.server.ui.web;
18+
19+
import de.codecentric.boot.admin.server.web.servlet.AdminControllerHandlerMapping;
20+
21+
import java.util.Collections;
22+
import org.junit.Test;
23+
import org.springframework.test.web.servlet.MockMvc;
24+
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
25+
26+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
27+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
28+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
29+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
30+
31+
public class UiControllerTest {
32+
33+
@Test
34+
public void should_use_default_url() throws Exception {
35+
MockMvc mockMvc = setupController("");
36+
37+
mockMvc.perform(get("http://example/"))
38+
.andExpect(status().isOk())
39+
.andExpect(view().name("index"))
40+
.andExpect(model().attribute("baseUrl", "http://example/"));
41+
}
42+
43+
@Test
44+
public void should_use_path_from_public_url() throws Exception {
45+
MockMvc mockMvc = setupController("/public");
46+
47+
mockMvc.perform(get("http://example/"))
48+
.andExpect(status().isOk())
49+
.andExpect(view().name("index"))
50+
.andExpect(model().attribute("baseUrl", "http://example/public/"));
51+
}
52+
53+
@Test
54+
public void should_use_host_and_path_from_public_url() throws Exception {
55+
MockMvc mockMvc = setupController("http://public/public");
56+
57+
mockMvc.perform(get("http://example/"))
58+
.andExpect(status().isOk())
59+
.andExpect(view().name("index"))
60+
.andExpect(model().attribute("baseUrl", "http://public/public/"));
61+
}
62+
63+
private MockMvc setupController(String publicUrl) {
64+
return MockMvcBuilders.standaloneSetup(new UiController(publicUrl, "", "", Collections.emptyList()))
65+
.setCustomHandlerMapping(() -> new AdminControllerHandlerMapping(""))
66+
.build();
67+
}
68+
}

0 commit comments

Comments
 (0)