Spring Boot - replace default embedded Tomcat connector - spring-boot

I need to add an AJP connector to embedded Tomcat and disable (or replace) the default connector that listens on 8080.
I've tried customizing this with EmbeddedServletContainerCustomizer, but I can't get a handle on the Tomcat object to replace the default connector created there. As a result I end up with the http port on 8080 in addition to my AJP ports.
Next, I've tried extending TomcatEmbeddedServletContainerFactory and overriding its getTomcatEmbeddedServletContainer method. Per the JavaDoc, this appears to be the perfect place to replace the default connector, but it still ends up being enabled (and doesn't create my AJP connector either). Any ideas what I might be missing? I've verified with the debugger that my configuration is being run.
Per answer below, here's the cleanest solution:
#Bean
public EmbeddedServletContainerFactory tomcat() {
TomcatEmbeddedServletContainerFactory myFactory = new TomcatEmbeddedServletContainerFactory();
myFactory.setProtocol("AJP/1.3");
myFactory.setPort(9000);
return myFactory;
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer2() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
connector.setRedirectPort(9001);
}
});
}
};
}

You can use a TomcatConnectorCustomizer to configure the existing connector to use AJP by adding it to the TomcatEmbeddedServletContainerFactory.

Just create a EmbeddedServletContainerCustomizer bean and reconfigure it to AJP:
#Configuration
public class ServletConfig {
// AJP port defined in properties (default 666)
#Value("${tomcat.ajp.port:666}")
private Integer ajpPort;
#Bean
public EmbeddedServletContainerCustomizer ajpContainerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.setProtocol("AJP/1.3");
tomcat.setPort(ajpPort);
}
};
}
}

Related

How to set equivalent of web.xml JNDI <env-entry> in a Spring Boot project?

Referring to this SO answer, I'd like to setup the equivalent of this web.xml configuration in a JSF / JoinFaces / SpringBoot application (that doesn't have web.xml).
<env-entry>
<env-entry-name>jsf/ClientSideSecretKey</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>[AES key in Base64 format]</env-entry-value>
</env-entry>
Any pointers?
If you are using spring boot and embedded tomcat server, you can add <env-entry> programmatically with the following configuration.
#SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
#Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
#Override
protected TomcatWebServer getTomcatWebServer(org.apache.catalina.startup.Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatWebServer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
// adding <resource-ref>
ContextResource resource = new ContextResource();
resource.setName("jdbc/myJndiResource");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", "org.postgresql.Driver");
resource.setProperty("url", "jdbc:postgresql://hostname:port/dbname");
resource.setProperty("username", "username");
resource.setProperty("password", "password");
context.getNamingResources()
.addResource(resource);
// adding <env-entry>
ContextEnvironment ce = new ContextEnvironment();
ce.setName("jsf/ClientSideSecretKey");
ce.setType(String.class.getName());
ce.setValue("[AES key in Base64 format]");
context.getNamingResources().addEnvironment(ce);
}
};
}
public static void main(String[] args) throws NamingException {
SpringApplication.run(DemoApplication.class, args);
}
}
Once defined the jndi naming resources they can be accessed in your application using JndiTemplate of InitialContext.
JndiTemplate jndiTemplate = new JndiTemplate();
String str = (String) jndiTemplate.lookup("java:comp/env/jsf/ClientSideSecretKey");
Hope this helps you in resolving your problem.
Essentially <env-entry> declares a web application context attribute.
You can initialize your servlet context and provide the equivalent servlet context attributes in your Spring Boot application.
For that purpose, you can register a bean that implements the ServletContextInitializer interface (or WebApplicationInitializer if your app has to be deployed in a traditional servlet container). For example:
public class JsfServletContextInitializer implements ServletContextInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setAttribute("jsf/ClientSideSecretKey", "[AES key in Base64 format]");
}
}
Do not forget to register it as a bean in your configuration.

How to set max-swallow-size in spring boot 2?

I'm trying to set max-swallow-size property of tomcat to -1 in springboot microservice while upgrading to springboot version 2; my earlier code was working but in upgrade some classes have changed so it stopped working.
I tried to set property in two ways but both are not working;
with service configuration
#Bean
public ServletWebServerFactory servletContainerFactory() {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
if(connector.getProtocolHandler() instanceof AbstractHttp11Protocol) {
logger.debug("Setting maxSwallowSize for server connector as "+maxSwallowSize);
((AbstractHttp11Protocol <?>) connector.getProtocolHandler()).setMaxSwallowSize(maxSwallowSize);
}
}
});
return factory;
}
In control flow, I can see the debug line printed but it have not taken effect as end -point response is 502(Bad gateway) instead of 400
Second way :
2. through application.properties file with property
server.tomcat.max-swallow-size=-1
This is also not honored.
Now, How can I verify the property value whether it is actually set or not ?
or Am I setting the value in correct way ?
This New class has resolved my issue
#Component
public class TomcatCustomizer implements
WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(new TomcatConnectorCustomizer() {
#Override
public void customize(Connector connector) {
if(connector.getProtocolHandler() instanceof AbstractHttp11Protocol) {
((AbstractHttp11Protocol <?>) connector.getProtocolHandler()).setMaxSwallowSize(maxSwallowSize);
}
}
});
}
}
and I have used updated properties in spring boot 2.0
spring.servlet.multipart.max-file-size= XX MB
spring.servlet.multipart.max-request-size= YY MB

Spring boot - Embedded Tomcat - Connector Customizer - fail to add parseBodyMethods attributes

The original problem is when I sent a http request with method 'DELETE', the body part couldn't be sent to the server.
After googling, I found this article that suggests modifying the server.xml file and adding 'parseBodyMethods' to the Connector part can solve the problem:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
parseBodyMethods="POST,PUT,DELETE"
redirectPort="8443" />
However, because I'm using spring's embedded tomcat, I have to find a way to do the same in spring's way. So, I found this article that seems to allow me to add ConnectorCustomizer and add additional attribute to the Connector. The following is my code:
public class MyTomcatConnectorCustomizer implements EmbeddedServletContainerCustomizer {
#Override
public void customize(ConfigurableEmbeddedServletContainer factory) {
if(factory instanceof TomcatEmbeddedServletContainerFactory) {
customizeTomcat((TomcatEmbeddedServletContainerFactory) factory);
}
}
public void customizeTomcat(TomcatEmbeddedServletContainerFactory factory) {
TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) factory;
tomcat.addConnectorCustomizers(connector -> {
connector.setAttribute("parseBodyMethods", "POST,PUT,DELETE");
});
}
}
#Bean
MyTomcatConnectorCustomizer myTomcatConnectorCustomizer() {
MyTomcatConnectorCustomizer myTomcatConnectorCustomizer = new MyTomcatConnectorCustomizer();
return myTomcatConnectorCustomizer;
}
But still, the same issue exists. the body is still empty when I send a 'DELETE' request to the server. Does anyone have encountered the same issue before? Help appreciated!
change
connector.setAttribute("parseBodyMethods", "POST,PUT,DELETE");
to
connector.setParseBodyMethods("POST,PUT,DELETE")
or just
#Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory(){
#Override
protected void customizeConnector(Connector connector) {
super.customizeConnector(connector);
connector.setParseBodyMethods("POST,PUT,DELETE");
}
};
}
Yes, I know this is a bit old question but I stumbled upon this issue and found that SO don't have a solution for Spring Boot version 2.0.0 and later.
I had found this article on Baeldung which describes the changes in SB 2 versus SB 1.
Quoting the article (code emphasis is mine):
In Spring Boot 2, the EmbeddedServletContainerCustomizer interface is replaced by WebServerFactoryCustomizer, while the ConfigurableEmbeddedServletContainer class is replaced with ConfigurableServletWebServerFactory.
So to fix your issue you need to implement interface WebServerFactoryCustomizer from package org.springframework.boot.web.server and parametrize it with TomcatServletWebServerFactory and override it's customize method.
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
#Component
public class MyTomcatConnectorCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
TomcatConnectorCustomizer parseBodyMethodCustomizer = connector -> {
connector.setParseBodyMethods("POST,PUT,DELETE");
};
factory.addConnectorCustomizers(parseBodyMethodCustomizer);
}
}

Configure Spring Boot with two ports

I'm trying configure an application in Spring Boot with two differents ports, but I haven't got still.
My first aproximation has been with two controllers and I have defined a #Bean inside the two controller with container.setPort(8080);
And my second aproximation has been add the actuator dependency and change the port of the managament, but my application don't run. "Address already in use: bind",
How can I confiure an application with two ports? I want one port for admin and the other port is for consults of my api.
As is has been mentioned before, server.port and management.port along with management.context-path properties could be set to make the embedded container to listen on different ports (management-related properties to access Actuator endpoints).
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);
}
}
I recently blogged about this topic at http://tech.asimio.net/2016/12/15/Configuring-Tomcat-to-Listen-on-Multiple-ports-using-Spring-Boot.html
Since springboot 2, EmbeddedServletContainerFactory mentioned in ootero solution is no longer available, so you should use either TomcatServletWebServerFactory or TomcatReactiveWebServerFactory according to your context.
The solution stays the same aside from the factory injection :
#Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
Connector[] additionalConnectors = this.additionalConnector();
if (additionalConnectors != null && additionalConnectors.length > 0) {
tomcat.addAdditionalTomcatConnectors(additionalConnectors);
}
return tomcat;
}
To change Actuator management port you can use property
management.port=8081
See full list of properties here
Update:
Actuator creates one more Embedded Tomcat(servlet container) instance in this case.
See here and here
To run 2 or more applications within a single project or change the default port, you can perform the action like this
#SpringBootApplication
public class NewApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(NewApplication .class);
app.setDefaultProperties(Collections.singletonMap("server.port", "8083"));
app.run(args);
}
}
If only one additional port is to be opened, the following is sufficient (Kotlin):
#Configuration
class AdditionalEndpointConfig {
#Bean
#ConditionalOnProperty(PORT_PROPERTY)
fun tomcatServletWebServerFactory(#Value("\${$PORT_PROPERTY}") additionalPort: Int) =
TomcatServletWebServerFactory().apply {
addAdditionalTomcatConnectors(
Connector("org.apache.coyote.http11.Http11NioProtocol").apply {
scheme = "http"
port = additionalPort
})
}
companion object {
const val PORT_PROPERTY = "server.additional.port"
}
}

How to redirect automatically to https with Spring Boot

How I can easily configure the embedded tomcat server to redirect all http traffic to https? I have Spring Boot running on an ec2 instance that is behind an elastic load balancer. I have configured the ELB to handle ssl for me (which is awesome) and it sets the X-FORWARDED-PROTO header to "https". I want to detect when that isn't set, and redirect the user to force them to use https if they aren't already.
So far, I have tried adding the following to my application.properties file with no luck:
server.tomcat.protocol-header=x-forwarded-proto
security.require-ssl=true
My answer is a little late but I just recently had this problem and want to post a solution which worked for me.
Originally, I thought that setting tomcat up to use the X-Forwarded headers would suffice but the RemoteIPValve from Tomcat, which should normally handle this case, didnt work for me.
My solution was to add an EmbeddedServletContainerCustomizer and add a ConnectorCustomizer:
(note that I am using Tomcat 8 here)
#Component
public class TomcatContainerCustomizer implements EmbeddedServletContainerCustomizer {
private static final Logger LOGGER = LoggerFactory.getLogger(TomcatContainerCustomizer.class);
#Override
public void customize(final ConfigurableEmbeddedServletContainer container) {
if (container instanceof TomcatEmbeddedServletContainerFactory) {
final TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) container;
tomcat.addConnectorCustomizers(connector -> {
connector.setScheme("https");
connector.setProxyPort(443);
});
LOGGER.info("Enabled secure scheme (https).");
} else {
LOGGER.warn("Could not change protocol scheme because Tomcat is not used as servlet container.");
}
}
}
The important thing is that you not only set the Scheme to https but also the ProxyPort without which all internal redirects from Spring Boot were routed to port 80.
The configuration property security.require-ssl doesn't work when basic authentication is disabled (at least on old versions of Spring Boot). So you probably need to secure all the requests manually with code similar to this one:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Inject private SecurityProperties securityProperties;
#Override
protected void configure(HttpSecurity http) throws Exception {
if (securityProperties.isRequireSsl()) http.requiresChannel().anyRequest().requiresSecure();
}
}
You can check my full answer here: Spring Boot redirect HTTP to HTTPS
You will need a keystore file and few config classes.
The below link explains it in detail.
Https on embedded tomcat
Spring Boot 2.0 redirection of http to https:
Add the following to the #Configuration
#Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
#Override
protected void postProcessContext(Context context) {
SecurityConstraint securityConstraint = new SecurityConstraint();
securityConstraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
securityConstraint.addCollection(collection);
context.addConstraint(securityConstraint);
}
};
tomcat.addAdditionalTomcatConnectors(redirectConnector());
return tomcat;
}
private Connector redirectConnector() {
Connector connector = new Connector(
TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
connector.setPort(8080);
connector.setSecure(false);
connector.setRedirectPort(8443);
return connector;
}

Resources