views:

517

answers:

5

We have an old legacy PHP application. Now I want to write a new application module using Ruby on Rails.

Deployment is a one problem. I guess that it should be possible to run PHP app (via mod_php) and RoR app (via mod_proxy / mongrel) on a one Apache server. I don't want to use mod_rails because it requires to run php via fcgi. So is a risk of breaking something. Both PHP and RoR will use the same DB.

The tricky part is how to pass login info from PHP application to RoR app. Users login into PHP and their info is stored in PHP session data. The RoR app will be placed in a subdirectory of main PHP app (eg www.example.com/railsapp). So RoR should receive all HTTP cookies. And the question is how to extract PHP session data from RoR app.

Above this is just my first idea which is rather bad because of possible race conditions between PHP mod and RoR. I can modify the PHP app to store some info in DB when a user logs in. But I don't know how to handle a case when PHP session data expired and some data in DB should be updated (logout a user).

Does anyone solved similar problem? Or at least can point a most promising direction?

Update: It should be possible to configure mod_php to store session data in sql DB. In this way there will be no race conditions. DB engine should prevent race conditions.

Update2: Actually it is possible to use mod_rails with Apache in prefork mode. Which is required by the mod_php. It is just recommended for mod_rails to run Apache in worker mpm mode. So the whole deployment of PHP / RoR apps is greatly simplified.

+1  A: 

First, it seems like you're asking for trouble by mixing the two technologies. My first suggestion is "don't do that."

However, since you're probably not going to listen to that advice I'll make a suggestion about your actual question. In PHP apps that I've seen store session data in the database I've noticed too approaches to cleaning the data. Both include always time stamping the records so you know how old they are, and refreshing that time stamping from time to time while the user is active (sometimes every page view, sometimes less often depending on expected load and query count).

If your app does relatively few database calls, and therefore has a little time to spare, you can run an extra query against your session table every page view to at least certain pages. This means an extra query and a busy application that's a problem. The alternative tends to be a cron job that runs periodically to clean the table of expired records. These periodic cleaning jobs also can get run only when specific other tasks are done (like a user log in, which is often a little slow anyway since you have to setup the session data).

acrosman
I know about the risk. But I have to made difficult decision how to develop new projects / modules. Continue to write messy PHP code without unit / functional tests or switch to a MVC framework, Symfony or RoR. This is a very subjective topic and I didn't wanted to ask such questions.
Greg Dan
Good luck. You're right it's a subjective issue, you can tell where I come out on that issue.
acrosman
What's preventing you from writing good PHP code with unit tests?
Frank Farmer
+2  A: 

First, if you are placing the rails app in a sub directory it is possible to use mod_rails. In your configuration for the PHP site you can have a location that has a root for rails.

<Location /railsapp>
  DocumentRoot /.../app/public
</Location>

To get a session over to the rails side, you could either create a connect page on the rails and call it from the PHP side and pass in some data to login. You just need to protect this page from any request not from the localhost (easy to do).

You could also switch rails to use a database to store its sessions, you should then be able to generate a session id, store it in a cookie with the correct name and secret, and create a session in the database manually with that id.

You can also (which I recommend) have proxy page on the rails side which logs the user in and redirects them to their desired page. You could do it like this (not actual working code, but you get the idea):

PHP

$key = md5hash($user_id . $user_password_hash . $timestamp)
$url = "/railsapp/proxy?userid=" . $user_id . "&key=" . $key . "&page=home%2Fwelcome"
<a href="<$ $url $>">Rails App</a>

Rails

map.proxy 'proxy', :controller => 'proxy', :action => 'connect'

class ProxyController < ActionController::Base
  def connect
    key = ...
    if params[:key] == key
      login_user params[:userid]
      redirect_to params[:page]
    else
      render :nothing, :status => 403
    end
  end
end
Samuel
mod_rails requires apache to work in "worker" not "prefork" configuration. Then PHP has to run over fcgi. Additionally I saw also comments about possible clash between mod_rails and mod_rewrite. All above introduces additional risks of making old PHP code less stable.
Greg Dan
OK so $timestamp is in fact a secret key known only to PHP and RoR code. "key = ..." means that RoR finds in DB pass hash for given user_id and generates the key again. Correct?
Greg Dan
Key can be calculated how ever you want to. Just don't make the timestamp too accurate because a user could have your page open from seconds to hours before clicking the rails link.
Samuel
I get it, the time stamp limits the period of time link will be valid. It should be also safe to make additional PHP proxy page which would redirect the user's browser to RoR app with your magic link. So there is no big delay between the key creation and check.
Greg Dan
Exactly, I've done the exact same thing before (only with two ASP.Net sites).
Samuel
+1  A: 

I've done a mixed PHP/RoR app before (old PHP code, new hawt RoR features, as stuff needed fixing it got re-implemented). It's not really all that hard -- you just serve up the PHP as normal files, and use a 404 handler to redirect everything else to the Rails app.

As far as the session data goes, you could stuff it into a DB, but if you're willing to write/find routines to read and write PHP's marshalled data formats, PHP uses flock() to ensure that there are no race conditions in reading/writing the session data file. Do the same thing in your Rails app for minimal pain.

womble
A: 

Hey, Ive always thought this is actually a pretty common problem from those moving to ruby from php who have legacey apps/data in php and am surprised its not asked more.

I would be tempted to go with either of the following two approaches, both of which have worked well in the past. The second option is going to require a little more work and both of them will require modifications to your existing login code:

1) Use openid to handle your login so you dont have to worry about rolling your own solution. Either run your own openid server or use google, yahoo etc. Run each of your apps as a unique subdomain. There are openid plugins/code for rails and php and it is a tried and tested secure standard

2) Use memcached to store login sessions (as opposed to a db). Have a login page (either in you php app or rails app). Whether you ruby app or php app is accessed the oucome would be similar to this.

a) User tries to access a login protected page

b) App checks its own session data to see if user is logged in

c) If correct session data exists then user is logged in and app proceeds

d) If App cant find current login session checks for browser cookie

e) Then App checks memcache for this cookie key.

f) if the cookie key exists then the user must be logged in (otherwise redirects to login page)

g) app grabs user_id from memcached (stored as cookie key) so it knows which user is logged in

h) app sets login session so it doesnt have to go past c again unless session expires

This is an overly simplified version of what is happening but does work. I would use memcached in this scenario because it has auto expiration of values which you define and is damn fast. Remember dont pass usernames and passwords between apps or even userids for that matter. Pass a unique key which is merely a pointer to information stored in your DB/memcached

ADAM
A: 

1) I successfully use Passenger and mod_php simultaneously on a single prefork Apache, both on the development machine and on the server.

2) I'd connect the applications via a HTTP challenge/response sequence or maybe via shared memcached keys.

Leonid Shevtsov