Search results
Configuring Tomcat to Listen on Multiple ports using Spring Boot
1. OVERVIEW
Today I landed in a stackoverflow question about how to access the JavaMelody UI using a port other than the one used by the APIs. I went ahead I answered the question with some initial code I had for another blog post I’m working on but then decided to make it its own post.
While this could certainly be accomplished adding a couple of Spring Boot configuration properties: management.context-path and management.port to application.yml
for instance, and reuse the same path used for actuator endpoints (/admin/info
, /admin/health
, …), there might be some cases where having the servlet container listening on a port other that the two mentioned earlier might make sense, maybe for monitoring, traffic analysis, firewall rules, etc..
In this post I’ll add support for configuring embedded Tomcat to listen on multiple ports and configure JavaMelody to exclusively use one of those ports to display its reports using Spring Boot.
2. REQUIREMENTS
- Java 7+.
- Maven 3.2+.
- Familiarity with Spring Framework.
3. CREATE THE SPRING BOOT DEMO APPLICATION
curl "https://start.spring.io/starter.tgz" -d bootVersion=1.4.2.RELEASE -d dependencies=actuator,web -d language=java -d type=maven-project -d baseDir=springboot-tomcat-multiple-ports -d groupId=com.asimio.demo.api -d artifactId=springboot-tomcat-multiple-ports -d version=0-SNAPSHOT | tar -xzvf -
This command will create a Maven project in a folder named springboot-tomcat-multiple-ports
with most of the dependencies used in the accompanying source code.
Alternatively, it can also be generated using Spring Initializr tool then selecting Actuator and Web dependencies.
Application.java
, the entry point to the application looks like:
...
@SpringBootApplication(scanBasePackages = { "com.asimio.api.demo.rest" })
@Import({ EmbeddedTomcatConfiguration.class, JavaMelodyConfiguration.class, FiltersConfiguration.class })
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
It scans for @Component-based annotated classes in the specified package and more interesting, will instantiate the beans defined in the imported classes via @Import annotation.
3.1. CONFIGURING TOMCAT
EmbeddedTomcatConfiguration.java
, imported in Application.java is where embedded Tomcat is added support to listen on multiple and configurable ports:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
...
@Configuration
public class EmbeddedTomcatConfiguration {
@Value("${server.port}")
private String serverPort;
@Value("${management.port:${server.port}}")
private String managementPort;
@Value("${server.additionalPorts:null}")
private String additionalPorts;
@Bean
public EmbeddedServletContainerFactory servletContainer() {
TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
Connector[] additionalConnectors = this.additionalConnector();
if (additionalConnectors != null && additionalConnectors.length > 0) {
tomcat.addAdditionalTomcatConnectors(additionalConnectors);
}
return tomcat;
}
private Connector[] additionalConnector() {
if (StringUtils.isBlank(this.additionalPorts)) {
return null;
}
Set<String> defaultPorts = Sets.newHashSet(this.serverPort, this.managementPort);
String[] ports = this.additionalPorts.split(",");
List<Connector> result = new ArrayList<>();
for (String port : ports) {
if (!defaultPorts.contains(port)) {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
connector.setPort(Integer.valueOf(port));
result.add(connector);
}
}
return result.toArray(new Connector[] {});
}
}
This class adds an additional Tomcat connector for each port from server.additionalPorts configuration property excluding ports that were set in server.port and management.port.
The relevant configuration properties used in conjunction with EmbeddedTomcatConfiguration.java are included in application.yml
:
server:
port: ${appPort:8880}
additionalPorts: 8882,8883
# Spring MVC actuator endpoints available via /admin/info, /admin/health, ...
server.servlet-path: /
management:
context-path: /admin
port: 8881
...
The embedded Tomcat instance powering this application will listen on 8880
for all the requests except for Spring Boot actuator endpoints which will be exposed on 8881
through /admin
path. Notice the custom property server.additionalPorts, this is used in EmbeddedTomcatConfiguration.java to add more connectors to Tomcat, in fact, the endpoints reachable using port 8880
will be reachable through ports 8882
and 8883
.
This is a sample log once this application is started:
...
2016-12-14 21:07:03 INFO TomcatEmbeddedServletContainer:87 - Tomcat initialized with port(s): 8880 (http) 8882 (http) 8883 (http)
...
2016-12-14 21:07:04 INFO TomcatEmbeddedServletContainer:87 - Tomcat initialized with port(s): 8881 (http)
...
2016-12-14 21:07:04 INFO Http11NioProtocol:179 - Initializing ProtocolHandler ["http-nio-8881"]
2016-12-14 21:07:04 INFO Http11NioProtocol:179 - Starting ProtocolHandler [http-nio-8881]
2016-12-14 21:07:04 INFO NioSelectorPool:179 - Using a shared selector for servlet write/read
2016-12-14 21:07:04 INFO TomcatEmbeddedServletContainer:185 - Tomcat started on port(s): 8881 (http)
...
2016-12-14 21:07:04 INFO Http11NioProtocol:179 - Initializing ProtocolHandler ["http-nio-8880"]
2016-12-14 21:07:04 INFO Http11NioProtocol:179 - Starting ProtocolHandler [http-nio-8880]
2016-12-14 21:07:04 INFO NioSelectorPool:179 - Using a shared selector for servlet write/read
2016-12-14 21:07:04 INFO Http11NioProtocol:179 - Initializing ProtocolHandler ["http-nio-8882"]
2016-12-14 21:07:04 INFO NioSelectorPool:179 - Using a shared selector for servlet write/read
2016-12-14 21:07:04 INFO Http11NioProtocol:179 - Starting ProtocolHandler [http-nio-8882]
2016-12-14 21:07:04 INFO Http11NioProtocol:179 - Initializing ProtocolHandler ["http-nio-8883"]
2016-12-14 21:07:04 INFO NioSelectorPool:179 - Using a shared selector for servlet write/read
2016-12-14 21:07:04 INFO Http11NioProtocol:179 - Starting ProtocolHandler [http-nio-8883]
2016-12-14 21:07:04 INFO TomcatEmbeddedServletContainer:185 - Tomcat started on port(s): 8880 (http) 8882 (http) 8883 (http)
...
3.2. CONFIGURING JAVAMELODY
JavaMelodyConfiguration.java
, also imported in Application.java was gotten from here and here and is used to configure the JavaMelody servlet filter and which data would be collected using AOP:
...
@Configuration
public class JavaMelodyConfiguration implements ServletContextInitializer {
...
@Bean(name = "javaMelodyFilter")
public FilterRegistrationBean javaMelodyFilter() {
final FilterRegistrationBean javaMelody = new FilterRegistrationBean();
javaMelody.setFilter(new MonitoringFilter());
javaMelody.setOrder(1);
javaMelody.setAsyncSupported(true);
javaMelody.setName("javaMelody");
javaMelody.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
javaMelody.addUrlPatterns("/*");
javaMelody.addInitParameter("monitoring-path", "/monitoring");
return javaMelody;
}
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
// Monitoring JDBC datasources
@Bean
public SpringDataSourceBeanPostProcessor monitoringDataSourceBeanPostProcessor() {
final SpringDataSourceBeanPostProcessor processor = new SpringDataSourceBeanPostProcessor();
processor.setExcludedDatasources(null);
return processor;
}
// Monitoring of beans or methods annotated with @MonitoredWithSpring
@Bean
public MonitoringSpringAdvisor monitoringAdvisor() {
final MonitoringSpringAdvisor interceptor = new MonitoringSpringAdvisor();
interceptor.setPointcut(new MonitoredWithAnnotationPointcut());
return interceptor;
}
// Monitoring of all services and controllers (even without having
// @MonitoredWithSpring annotation)
@Bean
public MonitoringSpringAdvisor springServiceMonitoringAdvisor() {
final MonitoringSpringAdvisor interceptor = new MonitoringSpringAdvisor();
interceptor.setPointcut(new AnnotationMatchingPointcut(Service.class));
return interceptor;
}
@Bean
public MonitoringSpringAdvisor springControllerMonitoringAdvisor() {
final MonitoringSpringAdvisor interceptor = new MonitoringSpringAdvisor();
interceptor.setPointcut(new AnnotationMatchingPointcut(Controller.class));
return interceptor;
}
@Bean
public MonitoringSpringAdvisor springRestControllerMonitoringAdvisor() {
final MonitoringSpringAdvisor interceptor = new MonitoringSpringAdvisor();
interceptor.setPointcut(new AnnotationMatchingPointcut(RestController.class));
return interceptor;
}
}
But this setup would expose the JavaMelody UI on ports 8880
, 8882
and 8883
, lets fix that by adding another servlet filter and a configuration property to application.yml
:
javaMelodyPort: 8883
FiltersConfiguration.java
imported in Application.java too looks like:
...
public class FiltersConfiguration {
@Value("${javaMelodyPort:${server.port}}")
private Integer javaMelodyPort;
@Value("${javaMelodyPortOnly:true}")
private Boolean javaMelodyPortOnly;
@Bean(name = "javaMelodyRestrictingFilter")
public FilterRegistrationBean javaMelodyRestrictingFilter(FilterRegistrationBean javaMelodyFilter) {
Filter filter = new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (!javaMelodyPortOnly || request.getLocalPort() == javaMelodyPort) {
filterChain.doFilter(request, response);
} else {
response.sendError(404);
}
}
};
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.setOrder(-100);
filterRegistrationBean.setName("javaMelodyPortRestriction");
filterRegistrationBean.addUrlPatterns(javaMelodyFilter.getInitParameters().get("monitoring-path"));
return filterRegistrationBean;
}
}
This servlet filter verifies if the requests to the JavaMelody UI should be exclusively accepted using the configured port, if so, and the request comes from a different port, it sends back a 404
, otherwise it chains the request to the next servlet filter.
4. RUNNING THE DEMO APP
mvn spring-boot:run
Sending a request to a resource implemented in DemoResource.java
(not reviewed here but included in the source code):
curl http://localhost:8880/demo
demo
Sending a request to a Spring Boot actuator endpoint on the management port:
curl http://localhost:8881/admin/info
{"app":{"name":"springboot-tomcat-multiple-ports"},"build":{"version":"0-SNAPSHOT"}}
Sending a couple of requests on non-allowed ports to JavaMelody:
curl http://localhost:8880/monitoring
{"timestamp":1481786965410,"status":404,"error":"Not Found","message":"No message available","path":"/monitoring"}
curl http://localhost:8882/monitoring
{"timestamp":1481787018786,"status":404,"error":"Not Found","message":"No message available","path":"/monitoring"}
Loading JavaMelody UI in a browser using configured port 8883
:
JavaMelody on an additional Tomcat port
As it can be seen JavaMelody UI can only be accessed through port 8883
, a similar approach might be taken to restrict access to the API, which wasn’t done in this demo.
Thanks for reading and as always, feedback is very much appreciated. If you found this post helpful and would like to receive updates when content like this gets published, sign up to the newsletter.
5. SOURCE CODE
Accompanying source code for this blog post can be found at:
6. REFERENCES
NEED HELP?
I provide Consulting Services.ABOUT THE AUTHOR
