Monday, January 14, 2013

Multi-Hosting with Apache

As David Wheeler once famously said, "All problems in computer science can be solved by another level of indirection."  In this case, the indirection is a web server router.  In developing an Apache/Tomcat application, I've often needed to run multiple separate servers for Dev, QA, and staging environments at the same time.  Since I want them all to be easily accessible, I want separate, user-friendly url's all hosted on port 80.  How could you solve a problem like this?

The obvious solution is to run a single Tomcat server with multiple server entries for the separate urls.  However, this ties up port 80 to a single application, and I lose the isolation of the systems that is representative of the production environment.  The solution?  Add another layer of abstraction, via an Apache server that simply does routing.  Download and install a bare-bones Apache service, then add entries like the following to proxy the requests (httpd.conf configuration file):

<VirtualHost *:80>
 ServerName sub.domain.com
 ProxyPass / http://internal-ip:port/
 ProxyPassReverse / http://internal-ip:port/

 # ==== UPDATE HEADERS FOR FILTERING ====
 Header edit Location http://internal.domain.com/(.*) http://sub.domain.com/$1
 
 # ==== SETUP SUBSTITUTION CONTENT TYPES ====
 AddOutputFilterByType SUBSTITUTE text/html
 AddOutputFilterByType SUBSTITUTE text/css
 AddOutputFilterByType SUBSTITUTE text/javascript
 
 # ==== APPLY URL RENAME SUBSTITUTIONS TO LOCALHOST ====
 FilterChain replace
 Substitute "s|internal.domain.com|sub.domain.com|ni"
</VirtualHost>

<VirtualHost *:443>
 ServerName sub.domain.com
 
 # ==== HANDLE SSL REQUESTS ====
 SSLEngine On
    SSLProxyEngine On
    SSLCertificateFile      "/path/to/cer"
    SSLCertificateKeyFile   "/path/to/key"
 
 # ==== PERFORM PROXYING TO LOCAL SERVER ====
 ProxyPass / https://internal-ip:ssl-port/
 ProxyPassReverse / https://internal-ip:ssl-port/
 
 # ==== UPDATE HEADERS FOR FILTERING ====
 Header edit Location https://internal.domain.com/(.*) https://sub.domain.com/$1
 
 # ==== SETUP SUBSTITUTION CONTENT TYPES ====
 AddOutputFilterByType SUBSTITUTE text/html
 AddOutputFilterByType SUBSTITUTE text/css
 AddOutputFilterByType SUBSTITUTE text/javascript
 
 # ==== APPLY URL RENAME SUBSTITUTIONS TO LOCALHOST ====
 FilterChain replace
 Substitute "s|internal.domain.com|sub.domain.com|ni"

</VirtualHost>

You will also need to turn on the following modules for this to work (uncomment in modules section of httpd.conf - you don't need ssl_module if you're not going to use the SSL section):
  • filter_module
  • proxy_module
  • proxy_connect_module
  • proxy_ftp_module
  • proxy_html_module
  • proxy_http_module
  • ssl_module
  • xml2enc_module

The proxying logic above also makes the internal server think it has the url "internal.domain.com" rather than "sub.domain.com" so you can test multiple servers with the same external url but allow access via a custom url. This is a very powerful solution that allows you to point anything just about anywhere. In fact, you can move internal servers around, without needing to make configuration changes, by simply updating the routing Apache config (and opening the proper ports in the firewall if you are routing to another machine). I've used it extensively to manage a collection of 5 nearly-identical Apache/Tomcat servers.

No comments:

Post a Comment