一尘不染

Spring靴子| 如何动态添加新的tomcat连接器?

spring-boot

我需要使我的Spring Boot应用程序开始/停止动态监听新端口。我知道为此需要在Spring上下文中注入新的tomcat连接器。

我可以使用ServletWebServerFactoryBean和添加连接器tomcatConnectorCustomizer。但是,此bean仅在Spring
Bootup期间加载。

@Bean
public ServletWebServerFactory servletContainer() {

    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
    TomcatConnectorCustomizer tomcatConnectorCustomizer = connector -> {
        connector.setPort(serverPort);

        connector.setScheme("https");
        connector.setSecure(true);

        Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();

        protocol.setSSLEnabled(true);
        protocol.setKeystoreType("PKCS12");
        protocol.setKeystoreFile(keystorePath);
        protocol.setKeystorePass(keystorePass);
        protocol.setKeyAlias("spa");
        protocol.setSSLVerifyClient(Boolean.toString(true));
        tomcat.addConnectorCustomizers(tomcatConnectorCustomizer);
        return tomcat;

    }
}

有什么方法可以在运行时添加tomcat连接器吗?说一个方法调用?

更新:连接器已创建,但对其的所有请求都返回404:

我设法在运行时添加了Tomcat连接器。但是对该端口的请求不会发送到我的RestController。

    TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();

    TomcatConnectorCustomizer tomcatConnectorCustomizer = connector -> {
        Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
        connector.setScheme("http");
        connector.setSecure(false);
        connector.setPort(8472);
        protocol.setSSLEnabled(false);
    };
    tomcat.addConnectorCustomizers(tomcatConnectorCustomizer);

    tomcat.getWebServer().start();

我应该如何进一步进行?


阅读 321

收藏
2020-05-30

共1个答案

一尘不染

嗨,这是我的示例项目:示例项目

1-主要应用程序(DemoApplication.java):

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

2-配置文件(AppConfig.java):

@Configuration
public class AppConfig {

@Autowired
private ServletWebServerApplicationContext server;

private static FilterConfig filterConfig = new FilterConfig();

@PostConstruct
void init() {
    //setting default port config
    filterConfig.addNewPortConfig(8080, "/admin");
}

@Bean
@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public FilterConfig createFilterConfig() {
    return filterConfig;
}

public void addPort(String schema, String domain, int port, boolean secure) {
    TomcatWebServer ts = (TomcatWebServer) server.getWebServer();
    synchronized (this) {
        ts.getTomcat().setConnector(createConnector(schema, domain, port, secure));
    }
}

public void addContextAllowed(FilterConfig filterConfig, int port, String context) {
    filterConfig.addNewPortConfig(port, context);
}

 public void removePort(int port) {
    TomcatWebServer ts = (TomcatWebServer) server.getWebServer();
    Service service = ts.getTomcat().getService();
    synchronized (this) {
        Connector[] findConnectors = service.findConnectors();
        for (Connector connector : findConnectors) {
            if (connector.getPort() == port) {
                try {
                    connector.stop();
                    connector.destroy();
                    filterConfig.removePortConfig(port);
                } catch (LifecycleException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

private Connector createConnector(String schema, String domain, int port, boolean secure) {
    Connector conn = new Connector("org.apache.coyote.http11.Http11NioProtocol");
    conn.setScheme(schema);
    conn.setPort(port);
    conn.setSecure(true);
    conn.setDomain(domain);
    if (secure) {
        // config secure port...
    }
    return conn;
}
}

3-过滤器(NewPortFilter.java):

public class NewPortFilter {
@Bean(name = "restrictFilter")
public FilterRegistrationBean<Filter> retstrictFilter(FilterConfig filterConfig) {
    Filter filter = new OncePerRequestFilter() {

        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                FilterChain filterChain) throws ServletException, IOException {

            // get allowed url contexts
            Set<String> config = filterConfig.getConfig().get(request.getLocalPort());
            if (config == null || config.isEmpty()) {
                response.sendError(403);
            }
            boolean accepted = false;
            for (String value : config) {
                if (request.getPathInfo().startsWith(value)) {
                    accepted = true;
                    break;
                }
            }
            if (accepted) {
                filterChain.doFilter(request, response);
            } else {
                response.sendError(403);
            }
        }
    };
    FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<Filter>();
    filterRegistrationBean.setFilter(filter);
    filterRegistrationBean.setOrder(-100);
    filterRegistrationBean.setName("restrictFilter");
    return filterRegistrationBean;
}
}

4-过滤器配置(FilterConfig.java):

public class FilterConfig {

    private Map<Integer, Set<String>> acceptedContextsByPort = new ConcurrentHashMap<>();

    public void addNewPortConfig(int port, String allowedContextUrl) {
        if(port > 0 && allowedContextUrl != null) {
            Set<String> set = acceptedContextsByPort.get(port);
            if (set == null) {
                set = new HashSet<>();
            }
            set = new HashSet<>(set);
            set.add(allowedContextUrl);
            acceptedContextsByPort.put(port, set);
        }
    }

    public void removePortConfig(int port) {
        if(port > 0) {
            acceptedContextsByPort.remove(port);
        }
    }

    public Map<Integer, Set<String>> getConfig(){
        return acceptedContextsByPort;
    }
}

5-控制器(TestController.java):

@RestController
public class TestController {
@Autowired
AppConfig config;

@Autowired
FilterConfig filterConfig;

@GetMapping("/admin/hello")
String test() {
    return "hello test";
}

@GetMapping("/alternative/hello")
String test2() {
    return "hello test 2";
}

@GetMapping("/admin/addNewPort")
ResponseEntity<String> createNewPort(@RequestParam Integer port, @RequestParam String context) {
    if (port == null || port < 1) {
        return new ResponseEntity<>("Invalid Port" + port, HttpStatus.BAD_REQUEST);
    }
    config.addPort("http", "localhost", port, false);
    if (context != null && context.length() > 0) {
        config.addContextAllowed(filterConfig, port, context);
    }

    return new ResponseEntity<>("Added port:" + port, HttpStatus.OK);
}

@GetMapping("/admin/removePort")
ResponseEntity<String> removePort(@RequestParam Integer port) {
    if (port == null || port < 1) {
        return new ResponseEntity<>("Invalid Port" + port, HttpStatus.BAD_REQUEST);
    }
    config.removePort(port);

    return new ResponseEntity<>("Removed port:" + port, HttpStatus.OK);
 }
}

如何测试呢?

在浏览器中:

1-尝试:

http:// localhost:8080 / admin / hello

预期响应 :你好测试

2-尝试:

http:// localhost:8080 / admin / addNewPort?port = 9090&context =
alternative

预期响应 :已添加端口:9090

3-尝试:

http:// localhost:9090 / alternative /
hello

预期响应 :你好测试2

4-尝试预期的错误:

http:// localhost:9090 / alternative / addNewPort?port = 8181&context =
alternative

预期响应(允许使用上下文[替代],但端点未为此控制器在控制器中注册) :Whitelabel错误页面…

http:// localhost:9090 / any / hello

预期的响应(不允许上下文[任何]) :Whitelabel错误页面…

http:// localhost:8888 / any / hello

预期的响应(无效的端口号) :ERR_CONNECTION_REFUSED

http:// localhost:8080 /你好

预期的响应(不允许上下文[/ hello]) :Whitelabel错误页面…

5-尝试删除端口:

http:// localhost:8080 / admin / removePort?port =
9090

6-检查已移除的端口:

http:// localhost:9090 / alternative /
hello

预期响应(端口关闭) :ERR_CONNECTION_REFUSED

希望对您有所帮助。

2020-05-30