SpringBoot でマルチポートに対応する

# 環境
* Windows10
* IntelliJ IDEA
* Git Bash
* SpringBoot 2.3.12.RELEASE

# はじめに
画面上でポートを閉じて別のポートで同画面を開くみたいな要件があったため、
SpringBoot で multiple ports を実装しなければならず、その備忘録です。
立ち上げるアプリケーションは1つで、内包されている Tomcat にポート口を複数作ります。

# 実装

```application.yml
server:
  port : 8080
  additionalPorts: 8081, 8082
```

```EmbeddedTomcatConfiguration.java
package org.example.configuration;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.catalina.connector.Connector;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

@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 WebServerFactoryCustomizer<TomcatServletWebServerFactory> servletContainer() {

        return factory -> {
            Connector[] additionalConnectors = additionalConnector();
            if (additionalConnectors.length > 0) {
                factory.addAdditionalTomcatConnectors(additionalConnectors);
            }
        };
    }

    private Connector[] additionalConnector() {
        if (this.additionalPorts == null || this.additionalPorts.equals("")) {
            return new Connector[0];
        }
        Set<String> defaultPorts = new HashSet<>(Arrays.asList(this.serverPort, this.managementPort));
        String[] ports = this.additionalPorts.split(",");
        List<Connector> result = new ArrayList<>();
        for (String port : ports) {
            if (StringUtils.hasText(port) && !"null".equalsIgnoreCase(port) && !defaultPorts.contains(port)) {
                Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
                connector.setScheme("http");
                connector.setPort(Integer.parseInt(port.trim()));
                result.add(connector);
            }
        }
        return result.toArray(new Connector[] {});
    }
}
```

上記で作成した設定クラスを起動時に読み込ませます。

```BootApp.java
package org.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BootApp {

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

動作確認用の Controller を作成します。

```Controller.java
package org.example;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
public class Controller {

    @RequestMapping("/")
    public String index() {
        return "Hello World!";
    }
}
```


# 確認
実行し、Git Bash 上で確認します。

```
$ curl http://localhost:8080/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    13  100    13    0     0    149      0 --:--:-- --:--:-- --:--:--   149
Hello World!

$ curl http://localhost:8081/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    13  100    13    0     0    812      0 --:--:-- --:--:-- --:--:--   866
Hello World!

$ curl http://localhost:8082/
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    13  100    13    0     0    866      0 --:--:-- --:--:-- --:--:--   866
Hello World!
```