How to make Javamelody use different port (Spring Boot+two HTTP ports exposed)

2.7k views Asked by At

I have Spring Boot web application. It exposes REST API on port 8080. It also exposes management port 8081 with Spring Boot Management endpoints (http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-monitoring.html). I do not have any custom Tomcat configuration to achieve that. I just have property management.port=8081 in my application.properties file.

I have JavaMelody configured as described in https://github.com/javamelody/javamelody/wiki/UserGuideAdvanced#spring-boot-app (I have my custom JavaMelodyConfiguration class, with org.springframework.boot.web.servlet.FilterRegistrationBean that registers net.bull.javamelody.MonitoringFilter).

@Bean
    public FilterRegistrationBean javaMelody() {
        final FilterRegistrationBean javaMelody = new FilterRegistrationBean();
        javaMelody.setFilter(new MonitoringFilter());
        javaMelody.setAsyncSupported(true);
        javaMelody.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
        javaMelody.addUrlPatterns("/*");
        return javaMelody;
    }

With this configuration, Javamelody is exposed on port 8080 (business port). I would like to move it to 8081(management port). How to change that?

I use Spring Boot 1.4.2.RELEASE, javamelody 1.62.0

3

There are 3 answers

0
Michał Cegielski On BEST ANSWER

If the goal is to expose monitoring on management port starting from java melody version 1.76 is now much simpler.

You need Spring Boot 2.x, actuator and in yml or properties file:

  • management.server.port: {your custom port}
  • management.endpoints.web.exposure.include: {what you want normally}, monitoring
  • javamelody.management-endpoint-monitoring-enabled: true

See more details here: https://github.com/javamelody/javamelody/wiki/SpringBootStarter#configuration-in-case-of-management-port

3
ootero On

edit: this answer is still correct, but see accepted answer for simpler solution.

EmbeddedTomcatConfiguration.java

package ...

import java.util.ArrayList;
import java.util.List;

import org.apache.catalina.connector.Connector;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class EmbeddedTomcatConfiguration {

    @Value("${server.additionalPorts}")
    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;
        }
        String[] ports = this.additionalPorts.split(",");
        List<Connector> result = new ArrayList<>();
        for (String port : ports) {
            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[] {});
    }
}

application.yml

server:
  port: ${appPort:8800}
  additionalPorts: 8880,8881

Application.java

@SpringBootApplication
@ComponentScan(...)
@Import(EmbeddedTomcatConfiguration.class)
public Application {

    public static void main(String[] args) {
        SpringApplication.run(Application .class, args);
    }
}

and my suggestion for limiting accessing javamelody from a specific port would be to extend the javamelody filter and just chain the request if it comes from a specific port otherwise send back a 404.

From the logs:

INFO  TomcatEmbeddedServletContainer:185 - Tomcat started on port(s): 8800 (http) 8880 (http) 8881 (http)

This approach BTW exposes other endpoints on these ports. To solve this and limiting javamelody filter (/monitoring) to a specific port, you would need to write a filter that verifies path (servlet and filter path) being requested from allowable ports keeping in mind that the ordering of these filters is important.

Based on this answer and partial source code that I had already available when I answered this question, I had published a blog post about this topic at http://tech.asimio.net/2016/12/15/Configuring-Tomcat-to-Listen-on-Multiple-ports-using-Spring-Boot.html

0
Anders Båtstrand On

You can use the ReportServlet through a MvcEndpoint. Something like this:

    import net.bull.javamelody.MonitoringFilter;
    import net.bull.javamelody.ReportServlet;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.actuate.endpoint.Endpoint;
    import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.bind.annotation.GetMapping;

    import javax.servlet.ServletConfig;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;

    /**
     * We configure the Java Melody {@link MonitoringFilter} normally, but disables all access to the UI. Instead,
     * we create a {@link ReportServlet}, and expose it through a {@link MvcEndpoint} in {@link #javaMelodyReportEndpoint()}.
     */
    @Configuration
    public class JavaMelodyConfiguration {

        private final ServletConfig servletConfig;

        @Autowired
        public JavaMelodyConfiguration(ServletConfig servletConfig) {
            this.servletConfig = servletConfig;
        }

        @Bean
        MvcEndpoint javaMelodyReportEndpoint() {
            ReportServlet reportServlet = new ReportServlet();
            // We initialize the servlet with the servlet configuration from the server that runs on server.port, as
            // it currently only uses it to access the Collector instance, and some system information.
            reportServlet.init(servletConfig);

            return new MvcEndpoint() {
                @Override
                public String getPath() {
                    return "/monitoring";
                }

                @Override
                public boolean isSensitive() {
                    return false;
                }

                @Override
                public Class<? extends Endpoint> getEndpointType() {
                    return null;
                }

                @GetMapping
                public void report(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws ServletException, IOException {
                    reportServlet.service(httpRequest, httpResponse);
                }
            };
        }

        @Bean
        FilterRegistrationBean javaMelodyFilterRegistration() {
            FilterRegistrationBean javaMelody = new FilterRegistrationBean();
            javaMelody.setFilter(monitoringFilter());
            javaMelody.setName("javamelody");
            return javaMelody;
        }

        @Bean
        MonitoringFilter monitoringFilter() {
            return new MonitoringFilter() {
                @Override
                protected boolean isAllowed(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException {
                    // We allow no access to the report (/monitoring) from this filter, access is done through the
                    // MvcEndpoint above, using the management port.
                    return false;
                }
            };
        }
    }

(I also posted this here: https://github.com/javamelody/javamelody/issues/601)