Handle multiple versions in same URL
Asked Answered
F

7

9

what I'm trying to do is to handle multiple versions of the same web application, somewhat like Google does with some of their products where you get the "Try the new version" link.

The goal is to have both a "stable" and a "beta" version of the webapp and letting the users try out the new features without forcing them (and their bugs) on them.

Now, a very simple way of doing this would be to put each version in its own subfolder, like www.mywebapp.com/v1 and www.mywebapp.com/v2.

However, I would like this to be transparent to the user and the webapp URL to stay the same (e.g.: www.mywebapp.com/).

Which version must be loaded is determined server-side after the user logs in (e.g.: active version for the given user is stored in the DB) and may be later changed when the user clicks on the "try the new version"/"go back to the old version" links.

On the server side I must make do with MySQL, PHP and Apache.

I have already managed to get this working placing each version into its own subfolder, then storing version information in cookies (updated by the server at each login or page refresh) and using RewriteRule(s) to "proxy" requests from the base/versionless URL to the proper subfolder. If no cookie is set, a default folder is selected by a fallback RewriteRule.

This kludge works but feels extremely fragile and it puts additional burden on the Apache daemon so here I am asking if anybody knows a better way of doing this.

Thanks!

Furrier answered 10/8, 2011 at 11:43 Comment(1)
Which part about this feels fragile to you exactly? Storing the info in the cookie or the proxying?Hummocky
S
6

htaccess allows for rewrites based on the contents of cookies. Since Apache is AWESOME at redirects and PHP is adequate, I would handle it that way.

This example tests to see if there is a vers cookie. If there is, it adds 'vers=' + whatever was in the vers cookie to the request.

RewriteEngine On
RewriteBase /
RewriteCond %{HTTP_COOKIE} vers=([^;]+) [NC]
RewriteRule ^(.*)$ /$1?vers=%1 [NC,L,QSA]

(that example can be found here)

Schnapps answered 28/8, 2011 at 2:57 Comment(2)
Thanks, but this is basically what I'm doing already :) To be more specific I have also added some statements to avoid loops like: RewriteEngine on RewriteCond %{IS_SUBREQ} ^false$ RewriteCond %{ENV:REDIRECT_STATUS} ^$ RewriteCond %{HTTP_COOKIE} version=([a-z0-9]+) [NC] RewriteRule ^(.*) /v_%1/$1 [NS,L]Furrier
+1, this is a good approach, and not too much work for Apache to doJacaranda
F
2

I think a better way in PHP would be to simply query the database, get the desired version, and then include() it from a subfolder. That way it's transparent to the user.

For instance, assuming the user has opted in for the new beta, you save his entry in the database,

UPDATE `versions_table` SET (`version`) VALUES ('1.02b') WHERE `userid` = 5

And then when he accesses the page, you have something like this going on:

//PDO Connection here, skipped for example purposes
$stmt = $pdo->query('SELECT `version` from `versions_table` WHERE `userid` = 5 LIMIT 1'); 
//Of course 5 is only an example, in actual code that would be a variable
//representing the actual user ID.
$row = $stmt->fetch(); //Should be only one row.
include_once($row['version'].'/myApp'.'.php'); //Include 1.02b/myApp.php
Fool answered 27/8, 2011 at 6:45 Comment(1)
I agree. If you use an MVC framework or are organized with your code base, you can do that check right up front and literally include() a different code base for a specific user with out them knowing, and there is now no need to mess with your .htaccess file.Limey
H
2

Use a single RewriteRule to reroute all requests to a single route.php file in the root folder of your website.

The route.php file should look like this:

$user = authenticateUser();
$version = $user->getPreferredVersion();

$filePath = $_SERVER['DOCUMENT_ROOT'].'v'.$version.$_SERVER['REQUEST_URI'];

if( !file_exists( $filePath ) ) {
    header("Status: 404 Not Found", true, 404 );
    die();
}

$pathDetails = pathinfo( $filePath );

if( $pathDetails['extension'] == 'php' ) {
    require( $filePath );
} else {
    if( $pathDetails['extension'] == 'jpg' ) {
        header( 'Content-Type: image/jpeg' );
    } elseif( $pathDetails['extension'] == 'gif' ) {
        ...
    } elseif (...) {
        ...
    } else {
        // unsupported file type
        header("Status: 404 Not Found", true, 404 );
        die();
    }
    echo file_get_contents( $filePath );
}

This is the outline of what you should do in route.php there are some other security and technical issues that you should take care of in that file.

Horme answered 27/8, 2011 at 9:35 Comment(2)
Thanks for your suggestion, but this approach seems to create lots of undesirable side effects like: 1) relative includes in PHP files placed in subfolders stops working, 2) code that expects the whole app to be in the document root screws up, 3) by proxying images and other stuff via PHP you forfeit all the performance and cacheing benefits that come with Apache (Etags, etc).Furrier
@sergio Those are exactly some of the "other technical issues" that I mentioned you should take care of. Your third concern can be easily handled by sending proper cache-control, last-modified and etag headers in route.php. Handle the third situation by using include(dirname(__FILE__).'/../somefile.php') method for doing relative includes and just avoid situations in your second concern. The reason I'm insisting on this method is that any other method which solely relies on cookies for the version switch is "fragile" as you put it.Horme
P
0

This might help: what I am using for a month now is

  1. Keep all the files in database (path, code, version=Decimal(3,1), flag:stable=3/lab=2/beta=1/alpha=0)

  2. have .htaccess redirects all non existing files (don't redirect images, css, js or other static non version-ate files) internally to loader.pm (for you loader.ph_) don't use same extension as your other files for differentiation

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^   loader.pm [L,QSA]
    
  3. loader loads the latest stable version from database or beta version if no stable version exists.

    SELECT code, version, flag FROM table WHERE path = ? AND flag > 0 ORDER BY flag DESC, version DESC LIMIT 1 Don't show alpha pages that are under development

    output 404 error if no results, and

    when version is specified like ?v=2.0 loader file uses different query

    SELECT code, version, flag FROM table WHERE path = ? AND version <= ? ORDER BY flag DESC LIMIT 1

Assumptions:

  1. A user either want stable or latest; so you actually don't need more than 2 files per path; until unless you want to keep old versions for showcase. If user want latest, use .. ORDER BY version DESC, flag DESC, ..
  2. User doesn't care to know which version it is, hence instead of setting same version for whole website at once, we set version of each file individually; so tracking development is easy.

Problems:

  • No index/primary key
    • You need to make sure that you don't have duplicate entries, it will not break your website though, but it will not look good ;)
    • if same beta & stable version exists for a file, and user has opted for latest (beta); then you might accidentally deliver him beta file instead of stable file.
  • query-string variable ?v= is reserved for selecting versions, but that is fine; you may choose ?var=
Pargeting answered 10/8, 2011 at 11:43 Comment(0)
P
0

While it is possible to use a routing mechanism and session information to make rules, I would rather keep your current solution. All you'd do by implementing it in PHP is that you push the load from the apache2 (which already has a very fast Rewrite-Engine) to the php-binary. Also, you would put your application at risk of interfering versions due to incompatibilities of data, cookies and other session-based variables.

On the other hand, you have the benefit of reusability of shared objects like libraries, Domain Models, Data Mappers, etc. But in my opinion that advantage doesn't weight up the worse performance and interference risk.

So in one phrase, I believe your current solution is best.

Planchet answered 27/8, 2011 at 17:39 Comment(0)
C
0

Load your version-ed page/wepages in IFRAME and IFRAME src could be "webapp.com/v2"..

So whichever version user selects your address bar will read webapp.com..but your IFRAME url keeps on changing depending on version..

There is no need to write rewrite rules..

Candelabra answered 31/8, 2011 at 11:29 Comment(0)
A
0

There is a cloud platform that does this automatically upon deployment. www.cycligent.com
It is just a matter of setting a cookie once the two versions are deployed. A lot less work than some of the other answers shown.
Full Disclosure: I work for Cycligent.

Anfractuosity answered 2/4, 2015 at 23:39 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.