Showing posts with label PHP. Show all posts
Showing posts with label PHP. Show all posts

Wednesday, 15 August 2007

Prevent browser caching of web pages

I'm having problems with cached web pages in some of my web applications.
There is this particular application that is in continuous development, while employees are using it and whenever I make some modifications the old version conflicts with the new one throwing errors or displaying some mixed information: some from the old html code and some new information the web page retrieves by AJAX.

I got a fix for the html code, now it isn't cached any more. I use this code in PHP:

  header("Cache-Control: no-cache, must-revalidate");
  header("Expires: Tue, 29 Mar 1983 07:20:55 GMT");

This really works, everything I modified in the PHP code is updated every time someone browses the web page, but not the JS and CSS files that are linked from the html code :-(

I found some more tips for preventing caching here. They consist in adding some meta tags in the head section of the web page:

  <meta http-equiv="expires" value="Tue, 29 Mar 1983 07:20:55 GMT"/>
  <meta http-equiv="pragma" content="no-cache"/>

The value of the first one is a date in the past, obviously. I'll let you guess the miracle that happened on that particular date I chose to put in there :-)

I tried this as well and it doesn't work for js and css files, unfortunately :-(

This wouldn't have been a problem if my js code was inline, but I never insert inline JavaScript code if there are more than 20 lines. I always like to split the code in many files each representing a module that can be taken and put on another website and works without changing anything except maybe a few variables.

Another idea was to send a header for each js or css file, but that would mean changing the js and css files to .php and send the headers I wrote at the begining of this post. I really don't want to do that, I want to keep them as js and css, because it's makes the application neat and organised. Many editors highlight code according to the file extension, I don't want to switch to choosing manually the highlighting style.

Some other ideas I got from Matt Raible and this forum. The idea is simple, very simple, just add a random number as a variable sent by GET method. This will foul the browser to think that it's always a new file without affecting the file itself.

Some other idea is to use subfolders for each version and just change the version folder in the path provided in the html code. This won't be a big problem because I use this piece of code to load dependencies, if versions change, I'll need to change only the main file that loads the dependencies, but others might find this annoying.

These last methods work fine, problem solved. But thinking about optimisation, the js and css files will never be cached so I lose one of the most important advantages I got by using external files, exactly what I want to get rid of: caching. Caching is meant to store information that is used often instead of loading it again and again.

As I don't update these files so often I decided to use a combination of all these methods. I use the headers to make sure the html code is always updated (usually there isn't too much html code in my html files, so not too much overhead ;-) ). I added a variable called version to the end of the file and I update it when I want the code in js and css files updated as well.

Here are the sample lines:

  <link rel='stylesheet' type='text/css' href='stylesheet.css?version=3' />
  <script type="text/javascript" src="js/toolbox.js?version=3"></script>

Don't forget to update the version number to the files you load dynamically from other js files!

Thursday, 26 July 2007

Installing PHP under Windows

Installing PHP is supposed to be easy under Windows using the installer.

First you need to install Apache, or other web server.
Then launch the php windows installer and follow the instructions.

What I installed was Apache 2.2.4 and I tried to install PHP 5.2.2.
Everything was fine until I wanted to use some extension functions, like pg_connect, which is the function to connect to a PostgreSQL server. It didn't work and I was very surprised, because when I installed PHP I specified that I wanted to install the PostgreSQL extension.

I checked the php.ini file and the extension was indeed activated. I checked if the dll exists, it was there. So what was the problem? I figured out that php.ini was not being parsed, so I had to check the httpd.conf file for Apache where you need to specify the path for the php file.

I noticed that the PHP installer was writing the httpd.conf file wrongly, giving the path in forward slash notation, but everywhere else in the file all the paths were given in back slash notation, so I changed the path notation for php specifying the correct path both to the php.ini file and to the php5 module that Apache needs to load.

I restarted Apache and I got even more confused, the server couldn't recognise the php files as scripts and it displayed the code as text. That happens usually when the php module fails to load, so I changed the path only for the php5 module back to the way it was, no improvement. Then I changed the PHPIniDir attribute to the way it was before and it worked again, but I was back to no php.ini parsing. I changed only the PHPIniDir attribute to the backslash notation and I got the same result as earlier: code displayed as text.

I thought maybe the php.ini has a problem, but going through all of didn't reveal any mistake. I decided to rename it for a while to see what happens. Well, surprise, surprise, PHP worked, but no extensions...

My conclusion: writing the paths in backslash notations is the right thing to do, but if the server parses the php.ini file, it fails to load the php module.

php.ini looks good, everything is fine, the doc_root (that the installer failed to write) is there written correctly. I also followed the instructions to install php manually and I did everything accordingly, no improvement.

Is PHP meant to be used on Windows with Apache?

UPDATE: I solved my problem by installing XAMPP. Thank you very much Jo Cook for the tip.

Thursday, 12 July 2007

LDAP issue

A few weeks ago I decided that it would be better if all the web applications we develop would log users in by using their Active Directory authentication details.

This makes things easier for our users, they don't need to remember their username and password for each application. They will most probably use the same details everywhere anyway, but there's no point in keeping this information in many places, is there?

Well this task seemed pretty SF when I got the idea, but with researching, reading and testing, I managed to make it work. So I use LDAP to connect to our AD server and because I use PHP on the server side, I use PHP's LDAP Functions which are not enabled by default.

The typical sequence of LDAP calls you will make in an application will follow this pattern:

ldap_connect() // establish connection to server
|
ldap_bind() // anonymous or authenticated "login"
|
ldap_search() // search for some information
|
ldap_close() // "logout"

For authentication a typical application will do this:
- connect to the server
- bind anonymously or with a predefined user made especially for authenticating
- search for the user name in the directory
- try to bind with the user name and password given and see if it works
- disconnect from the server

Well this approach is very common. Here's a code example. If you search on google for "php ldap authentication" you'll find many code samples that do it in a similar way.

Well, my approach is different. Why bind anonymously or with a special user name and password? Why not try binding directly with the given name and password? That's what I did and it worked, but I noticed a weird thing about the ldap_bind function that I didn't find in the documentation: if the user name doesn't exist in the directory, the function will bind automatically anonymously even though a password is given.

To make sure the user is really authenticated after a successful bind, do a search of the directory and see if the user name exists and of course if it belongs to the right branch of the directory tree. I think this is more efficient and as secure as the other approach. The other approach does one more bind without reason and if a user enters wrong login password a bind and a search is done anyway, in my approach binding fails and returns failure quicker. Also instead of getting the search results and then the right data out of the structure, I check only the number of results which should be one for a successful authentication.
I know, I know, I'm an optimization freak ;-)

I found another weird behaviour that needed some more research. I noticed that even though my login details were correct the search returned no results. Here's the comment that helped me get to the bottom of it. So I had to change the port I was connecting to, from 389 to 3268.

However I was wondering what did "search the entire tree" mean. I thought I wasn't searching the entire tree, I was searching only for users in a certain domain because I was specifying the DC attribute. Changing the port made everything work, but I just was a bit confused so I tried different filters for my search.

I finally found that if I include in my filter the OU (OrganisationalUnit) the search works fine even when connecting to the 389 port. Isn't that interesting? So what does exactly "search the entire tree" mean?

Here's my code, take a look, give me some comments.


$LDAPFail = true;
//ldap rdn or dn
$ldaprdn = $username."@mydomain.com";
// associated password
$ldappass = $password;
// connect to ldap server
@$ldapconn = ldap_connect("xxx.xxx.xxx.xxx","389");
if ($ldapconn)
{
   //binding to ldap server
   $ldapbind = ldap_bind($ldapconn, $ldaprdn, $ldappass);

   // verify binding
   if ($ldapbind)
   {
     $dn = "OU=MyCompany_Users;DC=mydomain,DC=com";
     $filter="(SAMAccountName=".$username.")";
     $sr = ldap_search($ldapconn, $dn, $filter,Array("samaccountname"));

     if(ldap_count_entries($ldapconn, $sr) == 1)
       $LDAPFail = false;

   }
   ldap_close($ldapconn);
}


I hope this helps someone out there...