[Home] You are not logged in. You can create an account here or login here

Just my stuff

Benchmark the load time of a page with Javascript

Sometimes the bottleneck is not the Database or the Application itself, but something between the client and the app. So it can be useful to track the load time with js on the client directly. This script measures the load time and then sends it back to the server with an AJAX GET request, so the server can save it somewhere.
<script Language="JavaScript">
var from_time = new Date();
from_time = from_time.getTime();

function benchmark_loading_time()
{
  var to_time = new Date();
  to_time = to_time.getTime();
  var msecs = (to_time - from_time);

  //submit the result
  var req = null;
  try { req = new XMLHttpRequest(); } catch(e) {}
  if (!req) try { req = new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) {}
  if (!req) try { req = new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) {}
  req.open("GET", '/benchmark_loading_time/?msecs=' + msecs + '&url=' + location.href, false);
  req.send(null);
}
</script>

<body onLoad="benchmark_loading_time()">

Oracle concat function

If you need to concatenate varchars:
CREATE OR REPLACE FUNCTION Fnc_Concat_List (cur SYS_REFCURSOR, separator VARCHAR2) RETURN VARCHAR2 IS
    ret VARCHAR2(32000);
    tmp VARCHAR2(4000);
BEGIN
    LOOP
    FETCH cur INTO tmp;
        EXIT WHEN cur%NOTFOUND;
        ret := ret || separator || tmp;
    END LOOP;

    ret := SUBSTR(ret, LENGTH(separator) + 1);
    RETURN ret;
END;
/

Example:
CREATE TABLE hallo (ID number(10) PRIMARY KEY, NAME varchar2(255));
INSERT INTO hallo (ID, NAME) VALUES (1, 'a');
INSERT INTO hallo (ID, NAME) VALUES (2, 'b');
INSERT INTO hallo (ID, NAME) VALUES (3, 'c');
COMMIT;

SELECT fnc_concat_list(CURSOR(SELECT name from hallo), ', ') from dual;

Oracle pipelined function

  
CREATE TABLE hallo (ID number(10) PRIMARY KEY, NAME varchar2(255));
INSERT INTO hallo (ID, NAME) VALUES (1, 'a');
INSERT INTO hallo (ID, NAME) VALUES (2, 'b');
INSERT INTO hallo (ID, NAME) VALUES (3, 'c');
COMMIT;

CREATE OR REPLACE PACKAGE hoi IS
    TYPE hallo_lines IS TABLE OF hallo%ROWTYPE;
    FUNCTION h RETURN hallo_lines pipelined;
END;

CREATE OR REPLACE PACKAGE BODY hoi IS

    FUNCTION h RETURN hallo_lines pipelined IS
        r_hallo         hallo%ROWTYPE;
    BEGIN

        FOR i IN (SELECT ID, NAME FROM hallo)
        LOOP
            r_hallo := i;
            -- r_hallo.ID := i.ID;
            -- r_hallo.NAME := i.NAME;

            PIPE ROW(r_hallo);
        END LOOP;
    END;
END;

select * from table(hoi.h);

JBoss and SPNEGO authentication with GSS-API

This took about a million google searches to get done, so here is a blog post to explain the steps that need to be done to get a Java application deployed on JBoss to use GSS-API to authenticate the user making the request.

Prerequisites


  • A working KDC (Active Directory or something equivalent)
  • JBoss (I used 5.0.1.GA)
  • a keytab file

Configure JBoss


On a clean JBoss installation, open server/default/conf/login-config.xml and add the following at the end (just before </policy>):
    <application-policy name="com.sun.security.jgss.accept">
        <authentication>
            <login-module code="com.sun.security.auth.module.Krb5LoginModule" flag="required">
                <module-option name="debug">true</module-option>
                <module-option name="principal">HTTP/principal@REALM</module-option>
                <module-option name="storeKey">true</module-option>
                <module-option name="useKeyTab">true</module-option>
                <module-option name="doNotPrompt">true</module-option>
                <module-option name="keyTab">/path/to/keytabfile.keytab</module-option>
            </login-module>
        </authentication>
    </application-policy> 

and comment the whole <application-policy name="Others"> section right above.
Now we need to add some global VM parameters to Java: open /bin/run.conf and add
JAVA_OPTS="$JAVA_OPTS -Djavax.security.auth.useSubjectCredsOnly=false" 
JAVA_OPTS="$JAVA_OPTS -Djava.security.krb5.conf=/path/to/krb.conf" 

/path/to/krb.conf looks like this
[libdefaults]
    default_realm = YOURREALM
[realms]
 YOURREALM = {
  kdc = your.kdc
 }

and is the global Kerberos config file for your apps.
You should also increase the maximum HTTP header size permitted by the embedded Tomcat installation, since with Single-Sign-On HTTP headers may exceed the default of 4kb in complex Active Directory environments. Add a property maxHttpHeaderSize="32768" to your HTTP connector configuration in server/default/deploy/jboss-web.deployer. If your HTTP headers become larger than this setting, Tomcat just discards the requests without any log output, which can cause a lot of trouble.
Well, almost done. Now you are free to get the SPNEGO token from inside your app and do with it whatever you want (for ex. delegate the credentials to call some other service).

SPNEGO authentication and credential delegation with Java

Almost all our Web Services are secured with SPNEGO. This way we can forward the identity of the user calling a service to another service, or/and through the tiers of the service. If, for example, a user calls the “check my mail” WS, the service can get the identity of the user from the SPNEGO token, request a new kerberos token from the KDC for that user, and use that token to check for new mail on the IMAP server on behalf of the user.
Client[1] ---> WS[2] ---> more services[3]

Client calls WS, authenticates with his kerberos token, and WS can use that token to authenticate to more services on behalf of the Client.
I had to search the Web for a few hours to find out how SPNEGO and delegation works with Java, so here is a summary:
  1. The client sends a request to the server
  2. The server answers with
    Status: 401 - Authorization Required
    WWW-Authenticate: Negotiate
    
  3. The client receives the 401 and sends the SPNEGO token to the server:
    Authorization=Negotiate YIIGHwYGKwYBBQUCoIIGE[...]
    
  4. The server then has to decode and validate that token. If the token is forwardable then the server can use it to request a new token to authenticate to other services.

This is how to decode a SPNEGO token in Java:
byte[] token = null;
byte[] tokenForPeer = null;
byte[] tokenForEndpoint = new byte[0];

String endpointSPN = null;

GSSManager manager = GSSManager.getInstance();

GSSContext context = null;

GSSCredential clientCred = null;
GSSCredential myCred = null;

try {
  //Oid krb5MechOid = new Oid("1.2.840.113554.1.2.2");
  Oid spnegoMechOid = new Oid("1.3.6.1.5.5.2"); 

  //first obtain it's own credentials...
  myCred = manager.createCredential(null, GSSCredential.DEFAULT_LIFETIME, spnegoMechOid, GSSCredential.ACCEPT_ONLY);
  //...and create a context for this credentials...
  context = manager.createContext(myCred);

  //...then use that context to authenticate the calling peer by reading his
  //spnego token
  System.out.println(authorization);
  token = Base64.decode(authorization);
  tokenForPeer = context.acceptSecContext(token, 0, token.length);

  if (!context.isEstablished()) return false;
  if (tokenForPeer != null) {
    System.out.println("there is a token to send back to the peer, but I leave this out for now");
  }

  System.out.println("Context Established! ");
  System.out.println("Client principal is " + context.getSrcName());
  System.out.println("Server principal is " + context.getTargName());

} catch (WSSecurityException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
} catch (GSSException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

the authorization String contains the SPNEGO token from the request. Once we have established the context, we can check if the credentials can be delegated, and then request the new token:
//check if the credentials can be delegated
if (!context.getCredDelegState()) {
  System.out.println("credentials can not be delegated!");
  return false;
}

//get the delegated credentials from the calling peer...
clientCred = context.getDelegCred();

//now create the spnego token to send to the endpoint:
//create target server SPN
endpointSPN = "HTTP/spnegotestserver.domain.com@REALM.COM";
System.out.println("Endpoint: " + endpointSPN);
GSSName gssServerName = manager.createName(endpointSPN, GSSName.NT_USER_NAME);

//...and create a new context pretending to be the caller
clientContext = manager.createContext(gssServerName.canonicalize(spnegoMechOid), spnegoMechOid, clientCred, GSSContext.DEFAULT_LIFETIME);

//this should be an option: enable GSS credential delegation
clientContext.requestCredDeleg(true);
// create a SPNEGO token for the target server
tokenForEndpoint = clientContext.initSecContext(tokenForEndpoint, 0, tokenForEndpoint.length);

Done. Now we have a new tokenForEndpoint object that contains a valid SPNEGO token with the delegated credentials from the calling user. Insert it in the headers for the HTTP request to the next service in the chain like this
"Negotiate " + Base64.encode(tokenForEndpoint)

and you are good to go.

Automating svn delete and svn add

Patrick asked me on Friday if I could somehow make
svn move file
svn delete file
and
svn add file
somehow transparent to his not-so-computer-savy secretary. He is using svn to centrally store documents and files other people in his company edit and create, so the people can comment on changes, and work on the files while offline.
We thought a bit about it, and this is what came out:

#!/bin/bash
#this depends on inotify-tools
#folder to watch
FOLDER=~/tmp/svn_test/repo
###############################

inotifywait -m --format '%e %w %f' -r $FOLDER -e move -e create -e delete --exclude .svn 2>/dev/null|
while read EVENT
do
  EV=`echo $EVENT|cut -d' ' -f1|cut -d',' -f1`
  FOLDER=`echo $EVENT|cut -d' ' -f2`
  FILE=`echo $EVENT|cut -d' ' -f3`
  echo event $EVENT
  #continue
  if [ "$EV" == "CREATE" ] || [ "$EV" == "MOVED_TO" ]; then
    svn add --force "$FOLDER/$FILE" 
  elif [ "$EV" == "DELETE" ] || [ "$EV" == "MOVED_FROM" ]; then
    svn delete "$FOLDER/$FILE" 
  fi
done

This way, if Ms. Secretary works on files on her working copy of the svn repository, and this script is running, for every file creation, move or delete the corresponding svn command is executed.
This of course works while offline too, so when Ms. Secretary is back in the office, she can just execute the svn update && svn commit command and everything is back in sync.

Consume SSL protected Web Services with soap4r

After hours of google this deserves a blog post. I did not find a clear example about this, so I am writing one.
I had the need to call a .NET Web Service over https with mutual authentication and basic authentication.
First of all I installed the soap4r gem, then the httpclient gem (because that one supports basic authentication).
Then I made a folder called “certs” with all the certificates and key files I had:
- ca.cer – the certificate of the certification authority that signed the server certificate
- server.cer – the certificate of the server (signed by the guys who own ca.cer)
- client.cer – the client certificate I need to send along the request to get the content
- client.key – the key file for the client certificate
That’s all the certs and key files I needed.
Now it was time to try to get the wsdl:
require 'http-access2'

url = 'https://secure.example.com/web_service/wsdl'

client = HTTPAccess2::Client.new()
client.ssl_config.set_client_cert_file('certs/client.cer', 'certs/client.key')
client.ssl_config.set_trust_ca('certs/ca.cer')
client.set_basic_auth(url, 'username', 'password')
puts client.get(url).content

This worked.
Time to try soap4r:
require 'rubygems'  #if you installed httpclient with rubygems you need this
require 'soap/wsdlDriver'

#this validates the server certificate
#so you can be sure that the server you are
#sending data to is the server you have the
#certificate of in certs/server.cer
def validate_certificate(is_ok, ctx)
  cert = ctx.current_cert
  unless (cert.subject.to_s == cert.issuer.to_s) #check the server certificate only
    is_ok &&= File.open('certs/server.cer').read == ctx.current_cert.to_pem
  end
  is_ok
end 

wsdl = 'https://secure.example.com/web_service/wsdl'
driver = SOAP::WSDLDriverFactory.new(wsdl).create_rpc_driver
#driver.wiredump_dev = STDOUT

driver.options['protocol.http.ssl_config.verify_callback'] = method(:validate_certificate)

results = driver.web_service_method(arg1, arg2)
p results

To tell soap4r that you want basic authentication and where the certificate files are, you need to create a soap/property file with the following content:
client.protocol.http.basic_auth.1.url = https://secure.example.com/web_service/wsdl
client.protocol.http.basic_auth.1.userid = username
client.protocol.http.basic_auth.1.password = password

client.protocol.http.ssl_config.client_cert = certs/client.cer
client.protocol.http.ssl_config.client_key = certs/client.key
client.protocol.http.ssl_config.ca_file = certs/ca.cer
client.protocol.http.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_PEER
client.protocol.http.ssl_config.ciphers = ALL
client.protocol.http.ssl_config.verify_depth = 1

This file is loaded at startup (you can find other options in soap/lib/soap/httpconfigloader.rb), and configures the ssl and basic auth stuff for soap4r.

Apache mod_ssl mutual authentication

Certificates are usually used to authenticate the server only: you connect to your banks site, and you know that it’s the bank you connected to, because the certificate they send to your browser is valid and signed by a Certification Authority you (or your browser) trust. But you can use Certificates to authenticate the user too, and that is called mutual authentication. How does it work?
First of all we need to generate a CA certificate, that we then are going to use to sign the server and the client cert:

#generate the key
openssl genrsa -out ./CA/freshCA.key 1024
#generate a certificate request
openssl req -new -key ./CA/freshCA.key -out ./CA/freshCA.csr
#self-sign the request
openssl x509 -req -days 3650 -in ./CA/freshCA.csr \
-out ./CA/freshCA.crt -signkey ./CA/freshCA.key

now we have a valid, self-signed CA certificate.
Next we are going to generate a certificate for the web server, but first we need to change some defaults in
/etc/ssl/openssl.cnf
and create some initial files:
vi /etc/ssl/openssl.cnf #and change demoCA to CA
mkdir -p CA/newcerts
touch CA/index.txt
echo '100001' >CA/serial #this is the first serial number of the certificate you are going to generate

Next, generate the server certificate:
openssl genrsa -out ./server/keys/fresh.key 1024
openssl req -new -key ./server/keys/fresh.key -out ./server/requests/new_server.csr
openssl ca -days 3650 -in server/requests/new_server.csr -cert \
./CA/freshCA.crt -keyfile ./CA/freshCA.key \
-out ./server/certificates/new_server.crt

We can use this certificate in our apache config file right away:
ServerName new_server:443
SSLEngine on
SSLCertificateFile conf/certs/server/certificates/new_server.crt
SSLCertificateKeyFile conf/certs/server/keys/new_server.key
SSLCACertificateFile conf/certs/CA/freshCA.crt

Now we have apache listening on 443 for requests, using the new_server.crt certificate that is signed by freshCA.crt.
Now we can generate a client certificate for a user:
#first generate a key
openssl genrsa –des3 –out ./user/keys/simon.key 1024
#then the request
openssl req –new –key ./user/keys/simon.key –out ./user/requests/simon.csr
#then sign it
openssl ca -in ./user/requests/simon.csr \
–cert ./CA/freshCA.crt –keyfile ./CA/freshCA.key \
–out ./user/certificates/simon.crt

This generates a new, signed certificate that can be installed in the browser. Just put it somewhere on your server for the user to download.
Once the client certificate is installed on the browser side, we have to instruct apache to check it. Just add
SSLVerifyClient require
SSLVerifyDepth 2

to your config file, and nobody without a valid client certificate will be able to connect on 443 of your server. You may want to forward the email field, or the Common Name of the client certificate to your web application to be able to know who the user is:
#Forward Client certificate CN
RequestHeader set X_SSL_CLIENT_DN_Email "%{SSL_CLIENT_S_DN_Email}s" 
RequestHeader set X_SSL_CLIENT_DN_CN "%{SSL_CLIENT_S_DN_CN}s" 

This makes those fields available in the request headers for your app to consume.
If you ever need to revoke a cert, the steps are:
openssl ca –revoke ./user/certificates/simon.crt
#generate the certifacate revocation list
openssl ca –gencrl –out ./CA/freshCA.crl

and tell apache to check the revocation list with this line in your config file:
SSLCARevocationFile conf/certs/CA/freshCA.crl

Other random stuff:
- Strip the passphrase from a key file:
openssl rsa -in key.pem -out newkey.pem

- Convert a pfx file to a key and a crt file:
openssl pkcs12 -in filename.p12 -nocerts -out privatekey.key
openssl pkcs12 -in filename.p12 -clcerts -nokeys -out sslcert.crt

- Convert a pem file to a pkcs12 file:
openssl pkcs12 -export -in cert.pem -inkey key.pem -out cred.p12

- Generate a self-signed certificate
openssl req -new -newkey rsa:1024 -days 365 -nodes -x509 -keyout www.example.com.pem  -out www.example.com.pem

Benchmarking mod_rails against mongrel

I’d like to use mod_rails instead of the apache->haproxy->mongrel configuration, but before I do I wanted to make sure I don’t lose to much speed, so I decided to benchmark mod_rails against mongrel. mod_rails was already benchmarked, but I don’t believe it if I don’t see it :)
To benchmark it I created a new rails application with a single controller and a hello.html.erb view with only ‘Hello World’ in it.
For the test I took a very low end machine, because the virtual server I rent for the production site is not much better anyway:

s2@fresh:~$ cat /proc/cpuinfo
processor       : 0
vendor_id       : CentaurHauls
cpu family      : 6
model           : 9
model name      : VIA Nehemiah
stepping        : 8
cpu MHz         : 532.000
cache size      : 64 KB
fdiv_bug        : no
hlt_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 1
wp              : yes
flags           : fpu vme de pse tsc msr cx8 sep mtrr pge cmov pat mmx fxsr sse rng rng_en ace ace_en
bogomips        : 1067.72
clflush size    : 32

with 483608 bytes of RAM on Ubuntu 7.10 and ruby 1.8.6.
The mongrel setup consists of 3 mongrels running in production mode behind haproxy balancing the requests coming from apache.
This is the apache config:
<VirtualHost *:80>
  ServerName benchmark.fresh

  ProxyRequests Off

  ProxyPass / http://127.0.0.1:8010/
  ProxyPassReverse / http://127.0.0.1:8010/
</VirtualHost>

I keep things simple because I want to test the speed of the app running with mongrel against mod_rails. I don’t care about static stuff, url rewriting and other things in this test.
haproxy is configured like this:
...
listen rails :8010
  server rails-1 localhost:8011 maxconn 1
  server rails-2 localhost:8012 maxconn 1
  server rails-3 localhost:8013 maxconn 1

I left the sessions active, and the configured database is a postrges connection. As this will be the same in the two setups, I hope this will not make any difference.
Now I get the mongrels running and start the benchmark (after a dry run of 1000 requests):
$ ab -n 1000 -c 100 http://benchmark.fresh/hello/hello
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking benchmark.fresh (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Finished 1000 requests

Server Software:        Mongrel
Server Hostname:        benchmark.fresh
Server Port:            80

Document Path:          /hello/hello
Document Length:        12 bytes

Concurrency Level:      100
Time taken for tests:   25.184929 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      480000 bytes
HTML transferred:       12000 bytes
Requests per second:    39.71 [#/sec] (mean)
Time per request:       2518.493 [ms] (mean)
Time per request:       25.185 [ms] (mean, across all concurrent requests)
Transfer rate:          18.58 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0  107 375.1      0    1874
Processing:    47 2272 771.9   2414   11236
Waiting:        5 2264 740.8   2413   11236
Total:        483 2380 711.4   2415   13110

Percentage of the requests served within a certain time (ms)
  50%   2415
  66%   2456
  75%   2484
  80%   2562
  90%   2793
  95%   2863
  98%   3291
  99%   4396
 100%  13110 (longest request)

Now it’s time for mod_rails. I stopped the mongrels and haproxy to free up some precious RAM.
Apache config:
<VirtualHost *:80>
  ServerName benchmark.fresh
  DocumentRoot /home/s2/tmp/benchmark/public/
</VirtualHost>

LoadModule passenger_module /usr/lib/ruby/gems/1.8/gems/passenger-1.0.1/ext/apache2/mod_passenger.so
RailsSpawnServer /usr/lib/ruby/gems/1.8/gems/passenger-1.0.1/bin/passenger-spawn-server
RailsRuby /usr/bin/ruby1.8
RailsEnv production

and after a dry run of 1000 requests:
$ ab -n 1000 -c 100 http://benchmark.fresh/hello/hello
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright 2006 The Apache Software Foundation, http://www.apache.org/

Benchmarking benchmark.fresh (be patient)
Completed 100 requests
Completed 200 requests
Completed 300 requests
Completed 400 requests
Completed 500 requests
Completed 600 requests
Completed 700 requests
Completed 800 requests
Completed 900 requests
Finished 1000 requests

Server Software:        Apache/2.2.4
Server Hostname:        benchmark.fresh
Server Port:            80

Document Path:          /hello/hello
Document Length:        12 bytes

Concurrency Level:      100
Time taken for tests:   22.329544 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      514001 bytes
HTML transferred:       12000 bytes
Requests per second:    44.78 [#/sec] (mean)
Time per request:       2232.954 [ms] (mean)
Time per request:       22.330 [ms] (mean, across all concurrent requests)
Transfer rate:          22.44 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    2   4.9      0      41
Processing:    48 2069 2922.9   1445   22101
Waiting:       45 2064 2922.5   1443   22101
Total:         61 2071 2922.8   1445   22114

Percentage of the requests served within a certain time (ms)
  50%   1445
  66%   1589
  75%   1735
  80%   1808
  90%   2339
  95%   3249
  98%  17180
  99%  19692
 100%  22114 (longest request)

A pretty graph (because every serious benchmark has at least one):

mod_rails can handle 44,78 req/sec, and mongrel 39,71. I think I can ditch the complicated mongrel setup after I make sure mod_rails is as stable as the mongrels are.


Update: Dan and Sean pointed out in the comments that a
RailsMaxPoolSize 3
for mod_rails would be more appropriate. Here you go:
$ ab -n 1000 -c 100 http://benchmark.fresh/hello/hello
...
Concurrency Level:      100
Time taken for tests:   20.94626 seconds
Complete requests:      1000
Failed requests:        0
Write errors:           0
Total transferred:      514000 bytes
HTML transferred:       12000 bytes
Requests per second:    49.76 [#/sec] (mean)
Time per request:       2009.463 [ms] (mean)
Time per request:       20.095 [ms] (mean, across all concurrent requests)
Transfer rate:          24.93 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    8  28.1      0     113
Processing:   131 1902 1518.9   1240    6861
Waiting:      127 1900 1518.9   1238    6860
Total:        149 1910 1515.4   1253    6861

Percentage of the requests served within a certain time (ms)
  50%   1253
  66%   2038
  75%   3334
  80%   3656
  90%   4322
  95%   4644
  98%   5431
  99%   5696
 100%   6861 (longest request)

49.76. It’s actually quicker?! I could not believe this, so I repeated the test twice, with and without RailsMaxPoolSize 3.
With: 46.92, 46.51
Without: 10.22, 22.32
Ok. Without RailsMaxPoolSize set to 3 the box started to swap, that’s why it was slower.

Subdomain routing with rails

s2.diffuse.it, gpichler.diffuse.it, adanti.diffuse.it and so on. There are a few problems you have to overcome if you want your users to have their own subdomain:
1. first of all you need a dns wildcard
2. you need to configure apache
3. you need to differentiate between assets (javascripts, images, stylesheets) that are on the main site, and those from the subdomains
4. cookies
5. links to subdomains
The first problem is easy to solve: just tell your dns provider to route all traffic from *.yourdomain.tld to the same ip.
The second problem is easily solved too, with a catch all vhost in apache:

<VirtualHost *:80>
  ServerName yourdomain.tld
  DocumentRoot /opt/myapp/public/
  RequestHeader set SERVER_NAME yourdomain.tld
  ProxyRequests Off

  ProxyPass / http://127.0.0.1:8000/
  ProxyPassReverse / http://127.0.0.1:8000/
</VirtualHost>

If this is your first vhost in apache, then all requests going to the ip of your web server will end up in that vhost definition, this means that whatever.yourdomain.tld will end up using that vhost definition (I am assuming we have apache in front of a mongrel listening on 8000 here, but of course this will work with fcgi or mod_rails too).
Ok, now we have all requests to whatever subdomain proxyed to our mongrels on 8000. The RequestHeader set SERVER_NAME part is just a variable I added to the request. I will use this later in rails to get the current host name without any subdomain.
Now let’s move to the rails part. Groovie has very good support for subdomains with it’s routes, but rails (to my knowledge) does not, so I had to monkeypatch it a bit.
The first thing I did was to add a before_filter to get the current user by vhost:
class ApplicationController < ActionController::Base
  before_filter :get_appuser
  before_filter :get_vhostuser

  def get_appuser
    @appuser = User.find(session[:user_id])
  end

  def get_vhostuser
    @vhostuser = User.find_by_vhost(request.subdomains.first)
  end
end

This way we know what subdomain the current request is on, and who the logged in user is (if he is).
Next we have to make sure that static assets are served always from the root domain (yourdomain.tld). If we use
stylesheet_link_tag “style”
the generated url will be relative to the current host:
<link href=”/stylesheets/style.css” media=”screen” rel=”stylesheet” type=”text/css” />
but we want it to be always our root domain, so the browser can cache them and does not download the same stylesheets and javascripts each time for each subdomain. To do this we can just use
config.action_controller.asset_host = 'http://yourdomain.tld'

in environments/production.rb. This way all links pointing to static assets will be generated with http://yourdomain.tld in front of them.
Taken care of the static assets, we have to deal with the cookies. If a user logs in on http://yourdomain.tld/login, we want him to be logged in on http://username1.yourdomain.tld/ as well, and on http://username2.yourdomain.tld/ and so on. To archive this we have to set the domain of the session cookie to .yourdomain.tld.
Now we have almost everything in place. What we still need is some helpers to play nice with the subdomains. Wouldn’t it be nice if you could
<%= link_to 'Home', :controller => 'home', :action => 'index', :subdomain => 'user1' %>
?
Yes, it would. And here comes the ugly part. To do this I had to monkeypatch the UrlRewriter
#I took this from somewhere on the net,
#i don't remember who wrote it so I can't
#really give credit, but I did not write it!
module ActionController
  class UrlRewriter
    def rewrite(options = {})
      unless options[:subdomain].nil?
        if options[:subdomain] == false
          newhost = @request.server_name #domain(options[:tld_length] || 1)
        elsif @request.vhost != options[:subdomain]
          newhost = "#{options[:subdomain]}." + @request.server_name
        end
        unless newhost.nil?
          options[:host] = @request.port == @request.standard_port ? "#{newhost}" : "#{newhost}:#{@request.port}" 
          options[:only_path] = false
        end
      end
      options.delete(:subdomain)
      rewrite_url(options)
    end
  end
end

Just make a plugin out of this code, or put it in a file in the inizializers directory. But, where does that @request.server_name and @request.vhost come from I hear you all (the 2 users reading this post) scream? I patched the AbstractRequest too, to add this two methods:
module ActionController
  class AbstractRequest
     def server_name
       @env['HTTP_SERVER_NAME']
     end
     def vhost
       subdomains.first
     end
  end
end

Remember the
RequestHeader set SERVER_NAME yourdomain.tld
part in the apache config file? Here we use it to get the root domain name.
Well, done. Now we should have everything in place to get going.