Last April we were working on a large PHP application over at our office. We wanted to continue future development of the application in Ruby on Rails. A complete rewrite of the whole application was not an option, it could take years. What we needed was to come up with a solution that would allow us to keep the PHP application and develop new features in Ruby on Rails.
In this article we like to demonstrate the solution we came up with for both production and local development environments. This solution consists of a reverse proxy server and two separate web applications. We use Apache to host the PHP and Rails applications. The reverse proxy is configured with Apache's reliable built-in proxy module.
In order to demonstrate the solution we have also created a demo application that shares a database and session data between the Ruby on Rails and the PHP application.
The production environment is set up based on the assumption that we configure the proxy rules on the Apache server that hosts the Ruby on Rails application. Requests that this server cannot handle are proxied to the PHP server. Images and the stylesheet can be hosted on either server. We chose to keep them on the PHP server.
In order to configure Apache as a proxy server you need to enable the proxy modules by editing the
httpd.conf file as follows.
# Allow reverse proxy of HTTP requests from one server to another LoadModule proxy_module /usr/lib/apache2/modules/mod_proxy.so LoadModule proxy_http_module /usr/lib/apache2/modules/mod_proxy_http.so
The proxy rules can be added to the Virtual Host configuration. We added one
ProxyPassMatch directive to forward PHP pages and two
ProxyPass directives to forward image and stylesheet requests.
<VirtualHost *:80> # Redirect all PHP URLs ProxyPassMatch ^/(.*\.php)$ http://php_server_name/$1 # Redirect all images and stylesheets ProxyPass /images/ http://php_server_name/images/ ProxyPass /stylesheets/ http://php_server_name/stylesheets/ </VirtualHost>
Notice that you can specify an IP address instead of a server name in the proxy rules. Specifying a domain name may not be desirable if it relies on an external DNS server.
We use Phusion Passenger to run our Rails applications. For the proxy directives to work correctly you should ensure that
PassengerHighPerformance is not enabled (it is turned off by default).
<VirtualHost *:80> # PassengerHighPerformance needs to be turned off with Rails # for RewriteRule or ProxyPass directives to work # PassengerHighPerformance off </VirtualHost>
When the PHP server is configured for virtual hosting it will not know what website to show when it receives a proxied request. To solve this you can set the
ProxyPreserveHost directive so the domain name is preserved in the request to the PHP server.
<VirtualHost *:80> # Preserve the host name so both applications can listen to the # same domain name in combination with virtual hosting ProxyPreserveHost On </VirtualHost>
Ruby on Rails applications are usually developed using application servers like WEBrick or Thin. They are handy for running from the command line. If you do, the Apache rules will have no effect. A different solution is needed.
Luckily we can still proxy requests in our development environment by using a gem called rack-reverse-proxy. You can add this gem to the application by modifying the Gemfile and running
bundle to install the gem.
# Use Reverse Proxy for development group :development do gem "rack-reverse-proxy", require: "rack/reverse_proxy" end
The same proxy rules used for Apache can now be added to
config/environments/development.rb. This will forward the same requests during local development, in our case a PHP application running on port 8000. We added the following lines.
# Reverse Proxy for development purposes config.middleware.use Rack::ReverseProxy do # Redirect all PHP URLs reverse_proxy %r(\.php), "http://localhost:8000/" # Redirect all images and stylesheets reverse_proxy '/images/', "http://localhost:8000/" reverse_proxy '/stylesheets/', "http://localhost:8000/" end
The Ruby on Rails application now proxies the PHP pages, stylesheets and images.
Sharing session data between the Ruby on Rails and PHP applications is an essential part of the solution. This allows a user to be signed in with both applications at the same time.
By default Rails will serialise the session data in a cookie and PHP will usually store it in a temporary file. What we need to do is change the location of the session to a central location that is accessible by both, for example a database or memcache server. We need to make sure both applications can read and write the same session data format: either the Ruby serialised format, the PHP format, or a standardised format like JSON.
In a future blog article we hope to show you our solution for sharing session data between PHP and Rails using a MySQL database and serialised PHP data.
The following working demo should give an idea of what the solution could actually look like. The demo shows two identical web pages hosted on www.railsandphp.com. One page is written in Ruby on Rails and the other is written in PHP. They both show 5 high scores from the database. They also show the number of pageviews stored in the session. If you look carefully you will notice that the pageview count increments each time you reload either demo page.
In this article we have demonstrated how to run two web applications together on the same website. The solution is based on a Ruby on Rails and a PHP application.
Each application is hosted on Apache. Incoming traffic is directed to one of the servers and a reverse proxy makes sure some of the requests are forwarded to the other server. In the setup the user will be under the impression that all pages come from one website.
Hosting different applications on the same domain is helpful in various situations: for example when migrating an application step by step or when running an application based on different web technologies. This article is written for Ruby on Rails and PHP, but the same principle can be applied to JSP, Perl, Classic ASP and Microsoft .NET.