I am currently working on making AJAX chat. This has had many issues associated with it, the major one being bandwidth usage. By default the headers sent/received in a basic AJAX request under Firefox with mootools are are around 1kb! That is huge, and will put massive stretches on bandwidth. For this reason, I tried to minimize the headers as best I could, and got them down to around 150bytes each way, total. Read on for what I did…
I used the following options for the client (mootools, but you should be able to get the gist):
Client headers (JavaScript with Mootools):
new XHR({method:‘get’, headers:{‘Accept’:‘’, ‘Accept-Language’:‘’, ‘Accept-Encoding’:‘’, ‘Accept-Charset’:‘’, ‘User-Agent’:‘’ }});
This saved a lot of the unnecessary details being sent (if you require these headers, why not cache them in your $_SESSION variable in PHP?). The biggest of these headers was actually the User-Agent header. Turning this off gave me issues with sessions under CakePHP though. It turns out that Cake uses the User-Agent string to increase the security of Sessions. Well, for now I class the bandwidth saved more important than the security implications, so I added the following lines to /app/webroot/index.php in cake:
Fix sessions (Apache server with CakePHP):
$_SERVER[‘OLD_HTTP_USER_AGENT’] = $_SERVER[‘HTTP_USER_AGENT’];\ $_SERVER[‘HTTP_USER_AGENT’] =“REMOVED”;
Which made Cake “think” that all web browsers were called “REMOVED.” An alternative method could be to introduce a new session purely for usage through the chat interface, but that seemed like too much hassle to me.
I also removed the following headers from the server side, using PHP code:
Server headers (Apache, PHP and CakePHP):
header(‘X-Powered-By:’);\ header(‘Cache-Control:’);\ header(‘Expires:’);\ header(‘Vary:’);\ header(‘P3P:’);\ header(‘Server:’,true);\ header(‘Pragma:’);\
And edited my Apache config to reduce the size of the ‘Server’ header (as apparently I cannot remove it using PHP):
Reducing Apache signature header:
ServerSignature Off\ ServerTokens Prod
The ServerSignature option is more of a security thing - stopping people from knowing exactly what versions of software my server runs.\
This worked well. I then compressed all my XML outputs as far as I could, using one character tags with one character properties, and reducing empty f tags to <f/> wherever possible. This worked fine. But I realised that we were still sending 0.5kb/second/client. It was at this point that I thought
Why not get the server to wait for new content?
So by using the php usleep() function I had the data fetching function poll the database every $delay milliseconds. If there were changes, it would output the changes and return. If there were no changes after 30 seconds, it would exit anyway with an “empty” message: <f/>. If the client disconnected it would discontinue.
This seemed to be working great, except new posts that I wrote to the chat were not appearing until the aforementioned 30seconds had expired. It took me a while to track down the reason, but it is due to CakePHP and PHP serializing session access so as to prevent race conditions. Well my chat function just cached all the session data (it did not need to edit it) and then ran
Releasing the session (so other connections work):
session_write_close();
in order to release the session, so other connections could continue. Now I am aware of this issue, I think I will be building session_write_close() into many more of my functions!
But now I have a chat solution with variable delays (currently set to 0.5s) that allows you to have AJAX chat without requiring huge amounts of bandwidth. I can even increase the 30 seconds by changing PHP’s max_execution_time.
What if the server suddenly comes under attack?
I didn’t mention: I also (optionally) send out the delay number to the scripts, so if I want to I can change the delay from 0.5s to 5s across all currently connected clients within 30 seconds, without any of them having to reload, or even realise the change! This means that if bandwidth is suddenly a problem, I can ask the web browsers to reduce their pounding of the servers, and they will almost instantly. Pretty neat, eh?
I am beginning to think that AJAX is a viable solution to chat, so long as you think about it enough.
Keywords for Google:
Increasing AJAX efficiency, making efficient AJAX code, make AJAX more efficient, reduce header count, decrease number of headers, remove headers, reduce AJAX bandwidth\
Ideas for the future:
Currently the chat is stored in tables in a MySQL database, but I think we will be using memcached to improve efficiency some time soon. We will also probably remove the overhead of CakePHP as a framework just for the processor intensive chatroom feed script.