11

I've been trying to configure Apache web server with a Spring Boot app that uses embedded Tomcat. Before Spring Boot I used to create an ajp.conf file like:

<VirtualHost *:80>
   ServerName localhost
   <Proxy *>
      AddDefaultCharset Off
      Order deny,allow
      Allow from all
   </Proxy>

   ProxyPass /app ajp://localhost:8009/app
   ProxyPassReverse /app ajp://localhost:8009/app

 </VirtualHost>

And include in the httpd.conf file like

Include /opt/lampp/apache2/conf/ajp.conf

And in the Tomcat's server.xml file, I used to configure it to listen to port 8009

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" connectionTimeout="5000"

This setup works. But, now with Spring Boot I am trying to achieve something similar with an embedded tomcat. I read Spring Boot Documentation here and added the following propertied on my application.yml file:

server:
    port: 8080
    tomcat:
        remote_ip_header: x-forwarded-for
        protocol_header: x-forwarded-proto

My ajp.conf file looks like so:

<VirtualHost *:80>
   ServerName localhost
   <Proxy *>
      AddDefaultCharset Off
      Order deny,allow
      Allow from all
   </Proxy>

   ProxyPass /app ajp://localhost:8009/
   ProxyPassReverse /app ajp://localhost:8009/

 </VirtualHost>

I have my spring boot tomcat configuration class as

@Configuration
public class TomcatConfiguration {

private final Logger log = LoggerFactory.getLogger(TomcatConfiguration.class);

@Bean
public EmbeddedServletContainerFactory servletContainer() {
    TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
    tomcat.addAdditionalTomcatConnectors(createConnector());
    tomcat.addContextValves(createRemoteIpValves());
    return tomcat;
}

private RemoteIpValve createRemoteIpValves(){
    RemoteIpValve remoteIpValve = new RemoteIpValve();
    remoteIpValve.setRemoteIpHeader("x-forwarded-for");
    remoteIpValve.setProtocolHeader("x-forwarded-protocol");
    return remoteIpValve;
}

private Connector createConnector() {
    Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
    Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
    connector.setScheme("ajp");
    connector.setProtocol("AJP/1.3");
    connector.setRedirectPort(8443);
    //connector.setSecure(true);
    connector.setPort(8009);
    return connector;
}

On my apache error logs I see:

AH01080: ajp_msg_check_header() got bad signature 4854
[proxy_ajp:error] [pid 24073] AH01031: ajp_ilink_receive() received bad header
[proxy_ajp:error] ajp_read_header: ajp_ilink_receive failed
[proxy_ajp:error] (120007)APR does not understand this error code: [client xx.xx.xx.xx:60916] AH00878: read response failed from (null) (*)

Not sure what's going on here. I searched a lot online, but could not find a good documentation on how to serve tomcat behind apache with spring boot apps. Eventually, I would like to load balance multiple tomcat instances too.

Nayan
  • 235
  • 1
  • 3
  • 10
  • 1
    http://docs.spring.io/autorepo/docs/spring-boot/1.0.0.RC4/api/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactory.html#setProtocol%28java.lang.String%29 or http://docs.spring.io/spring-boot/docs/current/reference/html/howto-embedded-servlet-containers.html#howto-enable-multiple-connectors-in-tomcat . – Pavel Horal Nov 21 '14 at 22:44
  • 2
    Why are you creating HTTP NIO connector? You need to create AJP connector - `new Connector("AJP/1.3")`. – Pavel Horal Nov 22 '14 at 16:11
  • 1
    @PavelHoral you pushed me towards the right direction. When I used org.apache.coyote.ajp.AjpProtocol connector, it worked. Not sure, how I am doing it is the best way to do it. But, it works now. Thanks a ton! – Nayan Nov 22 '14 at 22:50

3 Answers3

6

Deduced from the comments above:

@Configuration
public class TomcatAjpConfig {

@Bean
@SuppressWarnings("static-method")
public EmbeddedServletContainerFactory servletContainer() {
    TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
    tomcat.addAdditionalTomcatConnectors(createConnector());
    tomcat.addContextValves(createRemoteIpValves());
    return tomcat;
}

private static RemoteIpValve createRemoteIpValves() {
    RemoteIpValve remoteIpValve = new RemoteIpValve();
    remoteIpValve.setRemoteIpHeader("x-forwarded-for");
    remoteIpValve.setProtocolHeader("x-forwarded-proto");
    return remoteIpValve;
}

private static Connector createConnector() {
    Connector connector = new Connector("AJP/1.3");
    connector.setPort(8009);
    return connector;
}

}
Tim
  • 19,793
  • 8
  • 70
  • 95
4

Had a similar problem but with HTTP-Proxy. After some debugging of Spring Boot 1.3 I found the following solution. It should be similar for the AJP Proxy.

1. You have to setup the headers on your Apache proxy:

<VirtualHost *:443>
    ServerName www.myapp.org
    ProxyPass / http://127.0.0.1:8080/
    RequestHeader set X-Forwarded-Proto https
    RequestHeader set X-Forwarded-Port 443
    ProxyPreserveHost On
    ... (SSL directives omitted for readability)
</VirtualHost>

2. You have to tell your Spring Boot app to use these headers. So put the following line in your application.properties (or any other place where Spring Boots understands properties):

server.use-forward-headers=true

If you do these two things correctly, every redirect your application sends will not go to http://127.0.0.1:8080/[path] but automatically to https://www.myapp.com/[path]

Update 1. The documentation about this topic is here. You should read it at least to be aware of the property server.tomcat.internal-proxies which defines the range of IP-addresses for proxy servers that can be trusted.

Igor Mukhin
  • 15,014
  • 18
  • 52
  • 61
  • Nice addition; That's what I'd arrived at as well. All works great, with the minor issue that when I use spring hateoas `ControllerLinkBuilder.linkTo` + `methodOn` my generated links start with https, but also contain the port number 443. When I use `BasicLinkBuilder` the port is not added to the URL. Any thoughts on that? – Tim Jan 07 '16 at 14:32
  • @tim Never worked with these classes. You can try to debug into ContollerLinkBuilder and in BasicLinkBuilder. If they have a difference, you can post a bug report to the Spring guys. They react very quick. – Igor Mukhin Jan 08 '16 at 20:06
1

Configurable throught properties or yml file.

@Configuration
@ConfigurationProperties(prefix = "tomcat")
public class TomcatConfiguration {
   private int ajpPort = 8009;

   private boolean ajpAllowTrace = false;
   private boolean ajpSecure = false;
   private String ajpScheme = "http";
   private boolean ajpEnabled;


  @Bean
  public EmbeddedServletContainerFactory servletContainer() {

    TomcatEmbeddedServletContainerFactory tomcat = new TomcatEmbeddedServletContainerFactory();
    if (isAjpEnabled()) {
        Connector ajpConnector = new Connector("AJP/1.3");
        ajpConnector.setProtocol("AJP/1.3");
        ajpConnector.setPort(getAjpPort());
        ajpConnector.setSecure(isAjpSecure());
        ajpConnector.setAllowTrace(isAjpAllowTrace());
        ajpConnector.setScheme(getAjpScheme());
        tomcat.addAdditionalTomcatConnectors(ajpConnector);
    }

    return tomcat;
    }
// ... Get/Set
}

application.yml

tomcat:
  ajpEnabled: true
  ajpPort: 9009
  ...
pdorgambide
  • 1,787
  • 19
  • 33