我需要让我的Spring Boot应用程序在新端口上动态启动/停止侦听。我知道为此需要在Spring环境中注入一个新的tomcat连接器。
我能够使用ServletWebServerFactory和tomcatConnectorCustomizer添加连接器。但是这个bean只在Spring启动时加载。
@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连接器?比如说方法调用?
我设法在运行时添加了一个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();
我应该如何继续?
基于大家公认的“阿里尔·卡雷拉”的答案,我做了一些不同的事情(IMO cleaner):
import package.IgnoredBean;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import static org.apache.commons.lang3.reflect.MethodUtils.getMethodsWithAnnotation;
//must be declared in a separate java file so that it's not picked up by component scanning as inner class
@IgnoredBean
@RestController
@Slf4j
@RequiredArgsConstructor
class DynamicController {
static final Method HANDLER_METHOD = getMethodsWithAnnotation(DynamicController.class, RequestMapping.class)[0];
private final String myContext;
@RequestMapping
public Object handle(
@RequestBody Map<String, Object> body,
@RequestParam MultiValueMap<String, Object> requestParams,
@PathVariable Map<String, Object> pathVariables
) {
Map<String, Object> allAttributes = new HashMap<>(body.size() + requestParams.size() + pathVariables.size());
allAttributes.putAll(body);
allAttributes.putAll(pathVariables);
requestParams.forEach((name, values) -> allAttributes.put(name, values.size() > 1 ? values : values.get(0)));
log.info("Handling request for '{}': {}", myContext, allAttributes);
return allAttributes;
}
// this handler only affects this particular controller. Otherwise it will use any of your regular @ControllerAdvice beans or fall back to spring's default
@ExceptionHandler
public ResponseEntity<?> onError(Exception e) {
log.debug("something happened in '{}'", myContext, e);
return ResponseEntity.status(500).body(Map.of("message", e.getMessage()));
}
}
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target(TYPE)
@Retention(RUNTIME)
public @interface IgnoredBean {
}
@SpringBootApplication
@ComponentScan(excludeFilters = @ComponentScan.Filter(IgnoredBean.class))
...
public class MyApplication{...}
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.startup.Tomcat;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.coyote.http11.Http11NioProtocol;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toUnmodifiableSet;
@Service
@RequiredArgsConstructor
@Slf4j
class DynamicControllerService {
private final RequestMappingHandlerMapping requestHandlerMapper;
private final Map<Integer, RequestMappingInfo> mappingByPort = new ConcurrentHashMap<>();
private Tomcat tomcat;
@Autowired
void setTomcat(ServletWebServerApplicationContext context) {
tomcat = ((TomcatWebServer) context.getWebServer()).getTomcat();
}
public int addMapping(@Nullable Integer givenPort, RequestMethod method, String path, Object myContext) {
val connector = new Connector(new Http11NioProtocol());
connector.setThrowOnFailure(true);
//0 means it will pick any available port
connector.setPort(Optional.ofNullable(givenPort).orElse(0));
try {
tomcat.setConnector(connector);
} catch (IllegalArgumentException e) {
// if it fails to start the connector, the object will still be left inside here
tomcat.getService().removeConnector(connector);
val rootCause = ExceptionUtils.getRootCause(e);
throw new IllegalArgumentException(rootCause.getMessage(), rootCause);
}
int port = connector.getLocalPort();
val mapping = RequestMappingInfo
.paths(path)
.methods(method)
.customCondition(new PortRequestCondition(port))
.build();
requestHandlerMapper.registerMapping(
mapping,
new DynamicController("my context for port " + port),
DynamicController.HANDLER_METHOD
);
mappingByPort.put(port, mapping);
log.info("added mapping {} {} for port {}", method, path, port);
return port;
}
public void removeMapping(Integer port) {
Stream.of(tomcat.getService().findConnectors())
.filter(connector -> connector.getPort() == port)
.findFirst()
.ifPresent(connector -> {
try {
tomcat.getService().removeConnector(connector);
connector.destroy();
} catch (IllegalArgumentException | LifecycleException e) {
val rootCause = ExceptionUtils.getRootCause(e);
throw new IllegalArgumentException(rootCause.getMessage(), rootCause);
}
val mapping = mappingByPort.get(port);
requestHandlerMapper.unregisterMapping(mapping);
log.info("removed mapping {} {} for port {}",
mapping.getMethodsCondition().getMethods(),
Optional.ofNullable(mapping.getPathPatternsCondition())
.map(PathPatternsRequestCondition::getPatternValues)
.orElse(Set.of()),
port
);
});
}
@RequiredArgsConstructor
private static class PortRequestCondition implements RequestCondition<PortRequestCondition> {
private final Set<Integer> ports;
public PortRequestCondition(Integer... ports) {
this.ports = Set.of(ports);
}
@Override
public PortRequestCondition combine(PortRequestCondition other) {
return new PortRequestCondition(Stream.concat(ports.stream(), other.ports.stream()).collect(toUnmodifiableSet()));
}
@Override
public PortRequestCondition getMatchingCondition(HttpServletRequest request) {
return ports.contains(request.getLocalPort()) ? this : null;
}
@Override
public int compareTo(PortRequestCondition other, HttpServletRequest request) {
return 0;
}
}
}
您应该使用@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
创建ServletWebServerFactory bean作为原型bean。
现在在bean中,您需要将新的tomcat连接器注入到Spring上下文中(示例中为MySingletonBean),自动连接应用程序上下文,并从getBean方法中获取ServletWebServerFactoryBean(示例中为MyPrototypeBean)。这样,您将始终获得新的tomcat连接器bean。
以下是一个简单的示例代码:-
public class MySingletonBean {
@Autowired
private ApplicationContext applicationContext;
public void showMessage(){
MyPrototypeBean bean = applicationContext.getBean(MyPrototypeBean.class);
}
}
嗨,这里是我的示例项目:示例项目
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
预期响应:添加端口:9090
3-尝试:
http://localhost:9090/alternative/hello
预期响应:hello测试2
4-尝试预期错误:
http://localhost:9090/alternative/addNewPort?port=8181
预期响应(允许上下文[alternative],但endpoint未在此上下文的控制器中注册):白标签错误页。。。
http://localhost:9090/any/hello
预期响应(不允许上下文[任何]):白标签错误页。。。
http://localhost:8888/any/hello
预期响应(无效端口号):ERR\u CONNECTION\u拒绝
http://localhost:8080/hello
预期响应(不允许上下文[/hello]):Whitelabel错误页面...
5-尝试删除端口:
http://localhost:8080/admin/removePort?port=9090
6-检查已移除的端口:
http://localhost:9090/alternative/hello
预期响应(端口关闭):ERR\u CONNECTION\u拒绝
我希望这有帮助。
在Spring Boot文档中,有一节描述了如何为tomcat启用多个连接器(http://docs.spring.io/spring-boot/docs/1.1.7.RELEASE/reference/htmlsingle/#howto-在tomcat中启用多个连接器)。 但是有没有一种方法可以简单地将连接器添加到现有的连接器(web和管理连接器)?并将它们绑定到一些mvc控制器?我想做的是创建
本文向大家介绍SpringBoot如何取消内置Tomcat启动并改用外接Tomcat,包括了SpringBoot如何取消内置Tomcat启动并改用外接Tomcat的使用技巧和注意事项,需要的朋友参考一下 这篇文章主要介绍了SpringBoot如何取消内置Tomcat启动并改用外接Tomcat,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1,修改
在我的情况下,有可能,例如,一个新的设备被启动,因此必须处理另一个流。但是如何动态添加这个新流呢?
我需要在我的表中添加一个新产品。这http://localhost:8080/product/create/在Postman上工作。当我发布并添加新产品时,它会返回id并自动将其添加到我的表中。我的问题是如何在角CLI上实现它。我不知道我该怎么做。有人能帮我吗?这是我的代码: 产品Controller.java 应用程序。组成部分ts 产品服务ts app.component.html
问题内容: 我正在写一些演示Web服务器,提供静态html,css和javascript。服务器看起来像 我的客户端javascript使ajax调用到其他服务器。我该如何添加 对我的服务器响应,以便客户端javascript可以进行ajax调用? 问题答案: 自从Express宠坏了我以来,弄清楚这个问题有点麻烦。 看看enable cors。基本上,您需要做的是添加到要启用cors的域中。re
我们有一个spring-boot应用程序,它使用嵌入式tomcat进行部署,并使用MySQL后端的默认tomcat-jdbc连接池,而没有为MySQL或tomcat端定制。 该应用程序有一些调度程序,它们主要在一天中的特定时间运行,即在昨天的最后一次cron运行和今天的第一次cron运行之间,有超过9个小时的间隙。然而,无论何时cron在早期运行,它都从未遇到过空闲连接问题。 现在我们看到一条错误