春季靴子|如何动态添加新的tomcat连接器? [英] Spring Boot | How to dynamically add new tomcat connector?
问题描述
我需要使我的Spring Boot应用程序动态启动/停止在新端口上的侦听. 我知道为此需要在Spring上下文中注入新的tomcat连接器.
I need to make my Spring Boot application start/stop listening on a new port dynamically. I understand a new tomcat connector needs to be injected into Spring context for this.
我可以使用ServletWebServerFactory
bean和tomcatConnectorCustomizer
添加连接器.但是此bean仅在Spring Bootup期间加载.
I'm able to add a connector using a ServletWebServerFactory
bean and tomcatConnectorCustomizer
. But this bean is loaded only during 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连接器?说方法调用吗?
Is there any way to add a tomcat connector during run time? Say on a method call?
我设法在运行时添加了Tomcat连接器.但是对该端口的请求不会发送到我的RestController.
I've managed to add a Tomcat connector at runtime. But the request made to that port are not going to my 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();
我应该怎么做?
推荐答案
这是我的示例项目:示例项目
1-主要应用程序(DemoApplication.java):
1- Main Application (DemoApplication.java):
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2-配置文件(AppConfig.java):
2 - Config file (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):
3 - Filter (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):
4 - Filter Config (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):
5 - Controller (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
Expected Response: Added port:9090
3-尝试:
http://localhost:9090/alternative/hello
预期的响应:您好测试2
4-尝试预期的错误:
4 - try expected errors:
http://localhost:9090/alternative/addNewPort?port = 8181& context = alternative
预期的响应(允许上下文[替代],但端点未为此控制器在控制器中注册):白标错误页面...
Expected Response (context [alternative] allowed but endpoint not registered in controller for this context): Whitelabel Error Page...
http://localhost:9090/any/hello
预期的响应(不允许任何上下文):白标错误页面...
Expected Response (context [any] not allowed): Whitelabel Error Page...
http://localhost:8888/any/hello
预期的响应(无效的端口号):ERR_CONNECTION_REFUSED
Expected Response (invalid port number): ERR_CONNECTION_REFUSED
预期的响应(不允许上下文[/hello]):白标错误页面...
Expected Response (no context allowed [/hello]): Whitelabel Error Page...
5-尝试删除端口:
http://localhost:8080/admin/removePort?port = 9090
6-检查已删除的端口:
6 - check removed port:
http://localhost:9090/alternative/hello
预期的响应(端口关闭):ERR_CONNECTION_REFUSED
Expected Response (port closed): ERR_CONNECTION_REFUSED
希望对您有帮助.
这篇关于春季靴子|如何动态添加新的tomcat连接器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!