9

In the Spring-Boot documentation, there is a section that describe how to enable multiple connectors for tomcat (http://docs.spring.io/spring-boot/docs/1.1.7.RELEASE/reference/htmlsingle/#howto-enable-multiple-connectors-in-tomcat).

But is there a way to simply add connectors to the existing one (the web and the management connectors)? And to bind them to some mvc controllers? What I want to do is to create some web services that are accessible on a different ports.

tleveque
  • 507
  • 3
  • 9
  • 16
  • 1
    Do you want to make all your web services available on multiple ports (for example service A and service B both on 8080 and 8081), or do you want certain services to be available on certain ports (for example service A on 8080 and service B on 8081)? – Andy Wilkinson Sep 30 '14 at 08:27
  • Service A on port 8080 and service B on port 8081, ect... – tleveque Sep 30 '14 at 13:19

2 Answers2

12

You could use a child application for each service, with each child configured to use a separate port by setting its server.port property. Any components that you want to be isolated should go in one of the children. Any components that you want to share should go in the parent.

Here's an example of this approach. There are two child applications, one listening on port 8080 and one listening on port 8081. Each contains a controller which is mapped to /one and /two respectively.

package com.example;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

public class Application {

    public static void main(String[] args) {
        SpringApplicationBuilder parentBuilder 
                = new SpringApplicationBuilder(ApplicationConfiguration.class);
        parentBuilder.child(ServiceOneConfiguration.class)
                .properties("server.port:8080").run(args);
        parentBuilder.child(ServiceTwoConfiguration.class)
                .properties("server.port:8081").run(args);      
    }

    @Configuration
    static class ApplicationConfiguration {

        @Bean
        public MySharedService sharedService() {
            return new MySharedService();

        }   
    }

    @Configuration
    @EnableAutoConfiguration
    static class ServiceOneConfiguration {

        @Bean
        public ControllerOne controller(MySharedService service) {
            return new ControllerOne(service);
        }
    }

    @Configuration
    @EnableAutoConfiguration
    static class ServiceTwoConfiguration {

        @Bean
        public ControllerTwo controller(MySharedService service) {
            return new ControllerTwo(service);
        }
    }

    @RequestMapping("/one")
    static class ControllerOne {

        private final MySharedService service;

        public ControllerOne(MySharedService service) {
            this.service = service;
        }

        @RequestMapping
        @ResponseBody
        public String getMessage() {
            return "ControllerOne says \"" + this.service.getMessage() + "\"";
        }
    }

    @RequestMapping("/two")
    static class ControllerTwo {

        private final MySharedService service;

        public ControllerTwo(MySharedService service) {
            this.service = service;
        }

        @RequestMapping
        @ResponseBody
        public String getMessage() {
            return "ControllerTwo says \"" + this.service.getMessage() + "\"";
        }
    }

   static class MySharedService {

        public String getMessage() {
            return "Hello";
        }
    }
}
Andy Wilkinson
  • 108,729
  • 24
  • 257
  • 242
  • What if I like to let a user to set ports of both APIs in property-files and also make both of them profile-specific? In this case, I think that I need to use two properties with **different** names, like `api1.server.port` and `api2.server.port`. These are easily configurable using standard spring-boot's environment and profiles handling. But how can I pass these properties as `server.port` property to each child context? – Ruslan Stelmachenko Jan 22 '19 at 09:22
  • @RuslanStelmachenko It sounds like what you want to do is map one property in a parent to a different property in the child. I think that warrants a separate question so that it can be answered properly rather than being lost in the comments. – Andy Wilkinson Jan 22 '19 at 11:46
  • Not exactly. I just want to know any approach to achive it: *allow user to set child-specific properties, but preserve ability to do this using external `application.properties` or `application.yml` files, with support of profiles etc*. The approach with mapping `api1.server.port` to `server.port` for chilld1 - is just an example. It can be another approach. e.g. make base properties filename different for each context: `application.yml` + `child1.yml` + `child2.yml` (with support for profile-specific suffixes). I just want to know **any** working approach. I'll try to ask new q – Ruslan Stelmachenko Jan 22 '19 at 12:09
  • Here is my new question: https://stackoverflow.com/questions/54308472/multi-context-spring-boot-application-how-to-define-standard-spring-boot-proper – Ruslan Stelmachenko Jan 22 '19 at 12:38
  • Saved my day :) – Alok Nath Saha Feb 28 '19 at 20:08
  • Hello, is it possible to run these two apps, the first one in https and the second in http? – Alex_Pap Oct 08 '22 at 10:06
  • @Alex_Pap Yes, it should be. You can use the `server.ssl.*` properties to configure SSL in one of the children. – Andy Wilkinson Oct 08 '22 at 13:04
  • @AndyWilkinson thank you. Please in case you could help me I am [posting](https://stackoverflow.com/questions/73988873/spring-boot-app-with-spring-security-and-https-enabled-not-possible-to-expose-h) my initial question in regards with this issue. – Alex_Pap Oct 08 '22 at 13:14
2

server.port, management.port and management.context-path properties (the last two to access Actuator endpoints) could be set to make the embedded container to listen on different ports.

In case it would be desirable to listen on ports other than server.port and management.port:

@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: 8881,8882

Application.java

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

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

In http://tech.asimio.net/2016/12/15/Configuring-Tomcat-to-Listen-on-Multiple-ports-using-Spring-Boot.html I covered how to make Embedded Tomcat to listen on a different port and configured JavaMelody to exclusively access it through that new port.

ootero
  • 3,235
  • 2
  • 16
  • 22
  • 1
    Isn't these additional ports will just mirror base port and not serve different endpoints? If yes, then I don't see any reason to do it like this. Different ports usually required to serve different content (controllers), maybe with different authorization, logging etc. – Ruslan Stelmachenko Jan 22 '19 at 12:43
  • I think Spring Boot provides support for actuator endpoints via management port, which could be set via a configuration property. Other than this, you would probably have to restrict requests on an added port via a Servlet Filter or request interceptor. The mentioned blog post covers so using a Filter – ootero Jan 22 '19 at 20:07