Thursday, 3 June 2010

Websocket gets an update, and it breaks stuff.

Scroll to the bottom of this post for a cheat sheet of what has changed.
Unfortunately the change is not backward compatible. From the blog post:
"These changes make it incompatible with draft-hixie-thewebsocketprotocol-75; a client implementation of -75 can’t talk with a server implementation of -76, and vice versa."
So, lets take a look at the changes, and try to make sense of them.
The specification document is just not readable unless you want to go completely insane. Here's a few lovely bits from the document.
26. Let /key3/ be a string consisting of eight random bytes (or
equivalently, a random 64 bit integer encoded in big-endian
order).
EXAMPLE: For example, 0x47 0x30 0x22 0x2D 0x5A 0x3F 0x47 0x58.
What??? Wait. Let me read that again. a random 64 bit integer encoded in big-endian order. Sure. Make sure you don't use a random 64 bit integer encoded in LITTLE-endian order, that would completely mess up the protocol.
32. Let /fields/ be a list of name-value pairs, initially empty.
33. _Field_: Let /name/ and /value/ be empty byte arrays.
34. Read a byte from the server.
If the connection closes before this byte is received, then fail
the WebSocket connection and abort these steps.
Otherwise, handle the byte as described in the appropriate entry below:
-> If the byte is 0x0D (ASCII CR)
If the /name/ byte array is empty, then jump to the fields processing step. Otherwise, fail the
WebSocket connection and abort these steps.
-> If the byte is 0x0A (ASCII LF)
Fail the WebSocket connection and abort these steps.
-> If the byte is 0x3A (ASCII :)
Move on to the next step.
-> If the byte is in the range 0x41 to 0x5A (ASCII A-Z)
Append a byte whose value is the byte's value plus 0x20 to the /name/ byte array and redo this step for the next byte.
-> Otherwise Append the byte to the /name/ byte array and redo this step for the next byte.
NOTE: This reads a field name, terminated by a colon, converting upper-case ASCII letters to lowercase, and aborting if a stray CR or LF is found.
35. Let /count/ equal 0.
NOTE: This is used in the next step to skip past a space character after the colon, if necessary.
36. Read a byte from the server and increment /count/ by 1.
If the connection closes before this byte is received, then fail the WebSocket connection and abort these steps.
Otherwise, handle the byte as described in the appropriate entry below:
-> If the byte is 0x20 (ASCII space) and /count/ equals 1 Ignore the byte and redo this step for the next byte.
-> If the byte is 0x0D (ASCII CR) Move on to the next step.
-> If the byte is 0x0A (ASCII LF) Fail the WebSocket connection and abort these steps.
-> Otherwise Append the byte to the /value/ byte array and redo this step for the next byte. NOTE: This reads a field value, terminated by a CRLF, skipping past a single space after the colon if there is one.
37. Read a byte from the server. If the connection closes before this byte is received, or if the byte is not a 0x0A byte (ASCII LF), then fail the WebSocket connection and abort these steps. NOTE: This skips past the LF byte of the CRLF after the field.
38. Append an entry to the /fields/ list that has the name given by the string obtained by interpreting the /name/ byte array as a UTF-8 byte stream and the value given by the string obtained by interpreting the /value/ byte array as a UTF-8 byte stream.
39. Return to the "Field" step above.
Do you enjoy seeing basic HTTP header parsing code rewritten in English??? For me, it's beyond excruciating. It's verging on obfuscation. Where is the actual spec? Can we please just see an example packet dump conversation from client to server? You know, the 10 lines or so we actually need?
So, in the initial spec, things were reasonably sane, the client sent over "Hey can I be your friend and play websocket?", and the server sent back "hehe sure lets play bro". Then the two conversed using a reasonably sane binary protocol.
It seems that this was decided to have potential for misuse. Presumably if an insecure server (Not HTTP, something else), was out there, that you could get to say "hehe sure lets play bro", then potentially, you could establish a connection to it, and send fairly binary data to it from there on in (It would have packet headers, but you may still be able to do damage).
Note that the issue here doesn't seem to be anything within the expected usage, rather in forcing a browser to connect to some other say mail/irc server, and getting it to do bad stuff.
So in this new version of the spec, they've added a simple challenge / response. I say simple, but I mean needlessly complex.
Firstly, there's 2 new headers in the request. sec-websocket-key1 and sec-websocket-key2. These contain 2 integer keys. But for some reason, those keys are intersperced with random characters!
16. Let /spaces_1/ be a random integer from 1 to 12 inclusive.
Hickson Expires November 24, 2010 [Page 21]
Internet-Draft The WebSocket protocol May 2010
Let /spaces_2/ be a random integer from 1 to 12 inclusive.
EXAMPLE: For example, 5 and 9.
17. Let /max_1/ be the largest integer not greater than
4,294,967,295 divided by /spaces_1/.
Let /max_2/ be the largest integer not greater than
4,294,967,295 divided by /spaces_2/.
EXAMPLE: Continuing the example, 858,993,459 and 477,218,588.
18. Let /number_1/ be a random integer from 0 to /max_1/ inclusive.
Let /number_2/ be a random integer from 0 to /max_2/ inclusive.
EXAMPLE: For example, 777,007,543 and 114,997,259.
19. Let /product_1/ be the result of multiplying /number_1/ and
/spaces_1/ together.
Let /product_2/ be the result of multiplying /number_2/ and
/spaces_2/ together.
EXAMPLE: Continuing the example, 3,885,037,715 and
1,034,975,331.
20. Let /key_1/ be a string consisting of /product_1/, expressed in
base ten using the numerals in the range U+0030 DIGIT ZERO (0)
to U+0039 DIGIT NINE (9).
Let /key_2/ be a string consisting of /product_2/, expressed in
base ten using the numerals in the range U+0030 DIGIT ZERO (0)
to U+0039 DIGIT NINE (9).
EXAMPLE: Continuing the example, "3885037715" and "1034975331".
21. Insert between one and twelve random characters from the ranges
U+0021 to U+002F and U+003A to U+007E into /key_1/ at random
positions.
Insert between one and twelve random characters from the ranges
U+0021 to U+002F and U+003A to U+007E into /key_2/ at random
positions.
NOTE: This corresponds to random printable ASCII characters
other than the digits and the U+0020 SPACE character.
Hickson Expires November 24, 2010 [Page 22]
Internet-Draft The WebSocket protocol May 2010
EXAMPLE: Continuing the example, this could lead to "P388O503D&
ul7{K%gX(%715" and "1N?|kUT0or3o4I97N5-S3O31".
22. Insert /spaces_1/ U+0020 SPACE characters into /key_1/ at random
positions other than the start or end of the string.
Insert /spaces_2/ U+0020 SPACE characters into /key_2/ at random
positions other than the start or end of the string.
EXAMPLE: Continuing the example, this could lead to "P388 O503D&
ul7 {K%gX( %7 15" and "1 N ?|k UT0or 3o 4 I97N 5-S3O 31".
23. Add the string consisting of the concatenation of the string
"Sec-WebSocket-Key1:", a U+0020 SPACE character, and the /key_1/
value, to /fields/.
Add the string consisting of the concatenation of the string
"Sec-WebSocket-Key2:", a U+0020 SPACE character, and the /key_2/
value, to /fields/.
24. For each string in /fields/, in a random order: send the string,
encoded as UTF-8, followed by a UTF-8-encoded U+000D CARRIAGE
RETURN U+000A LINE FEED character pair (CRLF). It is important
that the fields be output in a random order so that servers not
depend on the particular order used by any particular client.
Oh sweet Jesus what were you smoking? So instead of just sending over a challenge, we're sending 2 challenges, interspersed with some random characters. OK, whatever. But wait, you're saying that the client should send the headers in a *random* order? That's just crazy. That's not a good solution to "Dumb ass server expects headers in specific order".
2 challenges enough for you? Apparently not.
26. Let /key3/ be a string consisting of eight random bytes (or equivalently, a random 64 bit integer
encoded in big-endian order). EXAMPLE: For example, 0x47 0x30 0x22 0x2D 0x5A 0x3F 0x47 0x58.
27. Send /key3/ to the server.
This 8 byte random key is sent after the initial headers. Don't forget, big-endian or it won't work ;)
OK, so we have the new request from the client, with THREE challenge keys. 2 of them as headers, and 1 of them after the headers.
To make our server work with this, all we now need to do is firstly, send back the headers Sec-WebSocket-Origin and Sec-WebSocket-Location (They were WebSocket-Origin and WebSocket-Location in previous version of protocol), and then after the headers have been sent, send the response to the challenge. Which is md5(BIG_ENDIAN_4byte(key1) + BIG_ENDIAN_4byte(key2)+key3).
Here's the cheat sheet for people who have better things to do than read endless English descriptions of code:
Client sends over
* header - sec-websocket-key1 - extract numerics (0-9) from value, and convert to int base 10. Divide by number of space characters in value!
* header - sec-websocket-key2 - extract numerics (0-9) from value, and convert to int base 10. Divide by number of space characters in value!
* key3 sent straight after the initial headers. 8 bytes data.
Server response
* headers Sec-WebSocket-Origin and Sec-WebSocket-Location must be sent instead of WebSocket-Origin and Websocket-Location
* After headers have been sent, send 16 byte md5 of (key1 + key2 + key3) (BIG_ENDIAN). That's 4 byte big endian key1, 4 byte big endian key2, and 8 byte key3.
It's fairly simple to setup your server to support both WebSocket versions. If the new key1/key2 headers are present, proceed with the new version. Else use the old.
Why couldn't the spec just include the 'meat'. It's a simple protocol which can be summed up in a page or two. The current spec runs to 55 pages! I'd bet far more than any implementation of the spec.
Are 3 challenge keys more secure than 1? Is adding random characters into the middle of the keys more secure than not doing that? Will it work if we use little-endian for the random number instead of big-endian?
Sometimes these things seem to be ridiculously over engineered to me. It took me far more time to read the spec than it did to update the Mibbit server to support it.

Wednesday, 31 March 2010

Google Closure Compiler Advanced mode

Benefits of using Google Closure Compiler
Closure is a set of tools to help developers write javascript. At its core is a compiler designed to reduce script size drastically by shortening variables and other techniques, much like the objectives of JSMin and YUI Compressor.
At Mibbit, we serve our web based webchat client to millions of unique users every week. Written entirely in Javascript it's made up of 42 separate files totaling 428Kb so even zipped up it's responsible for over 100GB bandwidth each week.
As well as wanting to reduce our bandwidth usage, we want to make our code loads as fast as possible for users.
Why Google closure?
Closure takes the idea of a Javascript minifier a step further than ever before. It does this by doing real compilation to remove unused code, inline variables and rewrite code to make it as small as possible.
Comparison
Now lets take a look at some JS minifiers, to see how Closure compares. Note though that closure compiler is much more than a simple minifier, it also goes to great lengths to analyze program flow, re-organizing and removing unused code as much as it can.
The table below includes gzipped byte counts, because Javascript should always be served up gzipped if the browser can accept it.
We compare JSMin, by Douglas Crockford and Yahoo! YUI
MinifierBytes% of originalgz Bytesgz % original
None428,264100%91,750100%
JSMin249,37258%57,33862%
YUI235,21455%55,99061%
Closure (STANDARD)219,44651%53,51558%
Closure (ADVANCED)166,77439%47,37252%
As you can see, the best by a mile is Closure running in ADVANCED mode.
Real world experience and tips
To get started with Closure checkout the website here http://code.google.com/closure/compiler/ or jump straight in and test out how the compiler will change things in your code by using the online version: http://closure-compiler.appspot.com/
You should be able to use the STANDARD mode with your js without any issues. If you're after the ADVANCED mode though, you're going to need to do a little more work. Firstly, in advanced mode, closure checks what parts of the js are actually used, and which bits can be safely left out. This is useful if you have libraries which you only pick and choose specific parts of. Remember to keep your original js files as this is a one-way trip. If you reference your js code from an html file, you need to tell closure that, by defining some externs. Otherwise it will assume your code isn't used by anything, and remove it! This is done in the following manner:
function init() {
// Some code
}
// Make sure it's extern so that things can access it.
window["init"] = init;
The other thing you need to do is understand how closure treats foo.bar vs foo["bar"].
with foo.bar the bar will be shortened if possible. With foo["bar"] the value bar is left unchanged by the compiler, as it is a string. This means that if you have any external interfaces, such as JSON, you'll need to access that JSON data using ["key"] rather than .key
At first look, this seems counter-intuitive. foo["bar"] is more characters than foo.bar. However, the closure compiler actually changes those foo["bar"] to foo.bar to save space, so everything will be ok :)
The other issue you may come across is externs. If you use external libraries, you need to tell closure about the external functions you're going to be calling, so that it doesn't minify any of those function names. This can be done using an externs file as per their guide. Closure already has an extensive list of externs, which it won't touch. For example, it knows about standard style names. So if you have foo.borderTop = "1px solid #ccc" it will already know to leave 'borderTop' alone. A list can be found here.
This is a little bit of extra work, since you won't see any error if closure has minified something it shouldn't have, but your code won't work. One way to assist you is by asking closure to output a property map list, in which it reports exactly which names it has shortened. You can then look through this, and if you notice something that is for example only in JSON data, or should be an external library reference, you can adjust and compile again. There's also a firebug extension for closure called inspector for better debugging.
The closure compiler is a really good tool in getting your js to the smallest size possible. Saving you and your users bandwidth and cpu. It's worth looking at the issues list for Closure http://code.google.com/p/closure-compiler/issues/list . Of course often, javascript is the least of your worries if you're serving up images, sound files, video etc. But it's good to get as many things optimized as you can. As you can see in the table above though, the worst case (raw js, no gz) compared to the best case (closure advanced, gz) means a reduction to just 11% of the original.
Benefits
So summing up the benefits are of course smaller size and lower bandwidth, should also mean better performance in the browser. One area Mibbit is keen to see how closure can perform is on mobile devices where bandwidth and processor aren't as plentiful as on the desktop. Any small code efficiency gain is good news for mobile where any excessive downloads or processor usage just chew the battery. Minifying in Advanced mode also makes it harder for others to read/steal parts of your code too. So if you're into protecting your code - here's a fair start.
So, look for real world examples like this and try out closure on your code, see the benefits.
If you have any questions or comments on web development in general, we can be found from time to time in http://mibbit.com/#webdev
Mibbit is going to Google i/o, are you? Follow us on Twitter http://twitter.com/mibbit

Friday, 11 December 2009

WebSocket - some numbers

I just implemented WebSocket into the Mibbit server, and thought I'd get some real numbers on performance. Having recently updated the Mibbit server to use deflate compression on XHR responses where it provides a net gain, I wanted to see how the two compare.
Note that one of my main focuses is on bandwidth usage. We use a fair amount of bandwidth, and anything we can do to optimize this is a good thing. Also it usually means a speed up for users which is extremely important.
First of all, a recap/explanation of how the conventional Comet works here.
We have two connections, which are set to keep-alive. One is for sending from browser to server, the other one is for server to browser.
The server->browser one is opened by the browser, doing an XHR POST, and held open by the server until data is ready, or until a timeout. Then a new request is again sent from the browser. This means that as soon as data is ready, it's delivered to the browser.
There are a couple of downsides to the above method.
1. Keep-alive isn't failsafe. Sometimes browsers/proxies/etc ignore you, or for other reasons decide to open new connections. Creating a new tcp/ip connection is expensive, and may mean that lag is introduced. For the vast majority of cases though, no new TCP/IP connections are created, and you just have your 2 connections to the mibbit server to handle all communications.
2. Every HTTP request sent from the browser includes headers, wether you want them or not. These are not small headers. expect 2k+ per request. You can remove *some* of these headers (See previous post), but most of them you're stuck with.
Now, enter WebSocket. This basically gives you a bi-directional socket with the server after a small HTTP handshake. The advantages are that there's no HTTP headers from then on, and there shouldn't be any lag due to keep-alive issues.
For the initial test, I opened a Mibbit Widget pointed at a channel on irc.mibbit.net said a couple of things, and did a whois lookup. A reasonably small scale test, but a useful one involving packets sent both ways, and some large packets (MOTD, topic).
First lets see the results for standard XHR:
data recvd: 1222
data sent: 7220
overhead In: 4456
overhead Out: 1229
Total data: 14127
ok, so we have 14k of data, and 5.6k of that is 'overhead' - HTTP headers/request/responses.
Lets see how WebSocket improves on that:
data recvd: 1350
data sent: 7307
overhead In: 118
overhead Out: 176
Total data: 8951
Wow. That's a big improvement. We've cut the overhead down to just 294 bytes (Basically the initial handshake).
Given the above data, it's clear that using WebSocket is a big win both in terms of bandwidth usage, and (although I haven't measured yet) lag. Anecdotally, the WebSocket version did seem a lot 'snappier', so I'd expect the lag to be reduced.
However, we haven't looked into one other area - compression. With HTTP, we can compress responses from the server, and the browser will decompress them fast, and pass them onto js. There is no mechanism for this with WebSockets (yet). If you want compression with WebSockets, you're likely going to have to do it yourself in javascript, which may burn precious browser cpu cycles.
So, finally, here's the numbers for XHR+deflate:
data recvd: 1222
data sent: 1868 (Compressed)
overhead In: 4456
overhead Out: 1229
Total data: 8775
So, this just beats WebSocket for bandwidth usage. It would depend heavily on the type of data you're sending as to how good your compression is, and how the numbers compare with WebSocket.
Just to recap,
XHR: 14,127 bytes
WebSocket: 8,951 bytes
XHR+deflate: 8,775 bytes
Note that this is a reasonably small scale test, but I do believe the numbers will scale pretty much like this. In general, for our type of traffic, the HTTP headers in XHR double the traffic. Again, for our type of traffic, the compression pretty much halves the traffic. So XHR+deflate vs WebSocket is pretty close.
We'll be rolling out WebSocket support in Mibbit in the next few days, and will be able to get some more definitive data on how the two compare. We have quite a large Chrome userbase, hopefully some of which are on the dev builds which support WebSocket.
It's certainly a great upgrade to the web, and hopefully compression support will come in due course.
Implementing WebSocket wasn't actually too bad at all, there were a couple of hoops to jump through, the protocol seems reasonably sane. Sadly the protocol doc is completely insane, and tries to describe what you should do using plain english instead of just giving you the data you need. eg "take the value \b\ and bitwise and it with 0x7f and put the result in a variable \b2\"
We have a very early alpha Mibbit Widget setup on http://wbe02.mibbit.com/?debug=true&channel=%23websocket with support for WebSocket if available, else XHR+deflate, or XHR worst case. To see if it's using WebSocket, click on the debug tab, and you should see a message saying WebSocket created. Alternatively if you use the developer timeline, you can see if there's any XHR going on or not.

Tuesday, 16 June 2009

Revenue / Browser

Here's some interesting stats from Mibbit... I checked out the average revenue generated per 1,000 visits on the main site. The data covered about 800k visits, so reasonably statistically valid I think.
First off, here's the visit breakdown:
Firefox: 58.75%
IE: 26.11%
Chrome: 6.46%
Opera: 3.95%
Safari: 3.63%
Mozilla: 0.67%
Now here's the average revenue generated per 1,000 visits for each browser. Calculated for example as Safari_revenue * 1000 / Safari_visits:
Safari: $2.392
Firefox: $1.599
Mozilla: $1.476
Chrome: $1.053
IE: $1.050
Opera: $0.388
This sort of went against some of my assumptions. I imagined that IE would be the top revenue generator, as you sort of imagine IE users as being less tech savvy, more 'used to' clicking on adverts etc.
The other interesting point to note is that you should never believe the extremely vocal minority who tell you that all firefox users have AdBlockPlus installed. They don't. As you can see Firefox users are the 2nd best revenue generators.
I didn't know where to place safari before I did the calculations, but it does make some sense. Apple users are more used to spending money, (They likely value their time more than their money), so perhaps this is why they generate more revenue.
The shocker was Opera. An Opera user generates just 16% of an average Safari user! That's really poor. Someone mentioned something about built in content blocking in Opera, but I couldn't find it in a default install.
So should I start pushing people away from Opera, and toward firefox+safari? Well, no, they're probably more likely using the browser *because* of their advertising behavior, not the other way around.... Still, food for thought.
So what about OSes?
Macintosh: $2.156
Linux: $2.076
Windows: $1.285
So once again, we have Mac users ready to spend money, click on ads, etc. The surprise is that Linux users generate quite a bit more revenue than Windows users. Counter to what I had assumed previously.
Note that these stats exclude iphone/ipod/opera mobile which aren't really big enough to draw many conclusions from - also people don't click often on ads on mobiles.
The stats were generated using Google analytics tied to adsense, which works really well for things like this.
In summary then:
  • Apple users are good at generating revenue - they buy stuff
  • Linux and firefox users are also good - don't listen to the overly vocal AdBlockPlus user that likes to tell you how everyone using firefox doesn't see any ads anyway
  • IE/windows is solid enough
  • Opera is terrible
  • Google analytics rocks
If you have any thoughts on why Opera should be so bad, please post a comment, perhaps it's to do with the 'turbo mode'? afaik this puts everything through their opera-mini web proxies? so perhaps that blocks ads?
Update:
As mentioned by some commenters, this may have more to do with different locations than browsers, coupled to the fact that some browsers have definite geographical biases. For example, Opera usage in these stats for the US, is 2%, whilst Opera usage for eastern Europe is 8%. In short, Opera may have a geographical bias toward less-easily-monetized countries (At least using adsense). 

Friday, 26 October 2007

Communications for ajax apps

What are the options? Communication with server could be done with a java applet, or with flash. However, both of these are a little kludgy and assume the user has those plugins. It can also be done with AJAX, which often makes for a more streamlined solution. XMLHttpRequest Unless you've been hiding under a rock for the last couple of years, you'll have heard of AJAX - the latest in a long line of buzz words. AJAX covers a number of technologies - Javascript, DOM manipulation, XML, and XMLHttpRequest. The last one of these is a powerful beast. It allows Javascript code to call back to the server to request more data. This is used for all sorts of new interactive webapps that mean the end user doesn't have to wait for page reloads every time they do something. Essentially using the XMLHttpRequest means that you can break out of the request/response paradigm of the web, and write webapps that function much more like desktop applications. The basic usage of an XMLHttpRequest is to perform an HTTP operation with the server (GET/POST/etc), so the client sends a request to the server, and receives a response back. But how much further can we push the XMLHttpRequest object? Doing a request/response is it a bit limited. Why the hell didn't they just allow us to open a raw socket to the server and have javascript functions send() recv() close() etc. I don't want http. I don't want xml. Give me a raw socket and I'm happy. Fact is they didn't do that, so we'll have to work around it. First lets take an example. We're writing a simple chat application. This requires data to be sent in both directions. When someone else says something, the server needs to 'send' that message to the client. When the client says something, it has to 'tell' the server about it. This doesn't instantly fit into the HTTP protocol request/response idea. Polling First method people would probably use would be 'polling'. With this you would periodically ask the server if it has any new messages for you. You could either send messages you have in these poll requests, or in a separate ajax call if you want them to be instant. This is ok, but an utter waste of resources and bandwidth. Lazy polling A better method is to use lazy polling. Here, you ask the server if it has any new messages for you, and the server holds your connection open until either it does, or a timeout expires. Then you immediately ask it again. Advantage: You get the messages as soon as you should. Unlike polling, where you may get a message late, with lazy polling, you pretty much get messages as soon as the server has them for you. This makes your webapp far more responsive. Advantage: Far less requests, so less HTTP headers etc. Instead of polling the server, maybe sending a request every second, you can just send 1 request, and wait until a timeout say 60 seconds later. Disadvantage: Your webserver has a connection held open to every client using the app. Depends how well your webserver is configured/programmed to handle this one. Connections are cheap, so it's not really an issue. But how do you send data to the server? Well it turns out that most common web browsers all you 2 concurrent requests to any one domain at the same time. So that means we can have one connection doing lazy polling for receives, and when we need to send a message (or request an image or other), we can use a separate 'send' ajax request. Note that for further efficiency this request can also respond with messages from the server. For example, if you have your lazy polling request going, then you send a chat message on your other 'send' request, and the server wants to reply with something, or perhaps coincidently someone else has just said something at exactly the same time, it makes more sense for the server to send these messages down the 'send' request which is open, rather than send them down the 'recv' lazy polling request. If it did that, the client would have to start a new lazy poll request which would waste a bit of bandwidth. Keep-Alive Any more optimisations? Well certainly configure to use keep-alive. Keep-alive allows the client to stay connected to the server and simply send more requests on the open connection. In this configuration, the webapp maintains 2 connections to the server. 1 is the lazy polling 'recv' connection, used to enable the server to 'push out' messages to the client. The other connection is used for sending data to the server, receiving messages if applicable, and also to fetch other media/images/etc if needed (Although it may be a good idea to put other media on a separate subdomain to allow it to run in its own connection). HTTP headers? I have a gripe with http headers... If I open a keep-alive connection to a server, and send 10 requests down that connection, it doesn't make sense to me to send all headers 10 times. The server already knows my userAgent from the first time I said it. For the other direction we get a little more say, so we can minimise the headers we send out from the server to the client. How much we talking? Well, it's not good... for my IRC webapp, 84% of received traffic is HTTP headers. 42% of sent traffic is HTTP headers. However, it's a small amount of data in the grand scale of things, and is the best that's possible. So although the XMLHttpRequest is pretty lame compared to a raw socket, it can be used exactly like a raw socket if you configure it right. Using this type of 'raw socket' emulation turns out to give pretty good transfer rates for data, as well as 'ping' times.

Welcome to Axod's Hack

So here it is, my new blog. Hopefully I can write something interesting :)