24 November 2008

Your Apache May Be Cheating You

In a fit of premature optimisation I figured the java/jetty/mysql part of iconfu should not be serving images, even if the database is the primary point of reference for image content, and have apache serve these images instead. Here's the plan: the server gets a request for an icon. The corresponding file doesn't exist, so the java application creates the file and also serves the content. On subsequent requests for the same file, apache should serve the file directly without invoking the java application. Here's the VirtualHost configuration I ended up with:

NameVirtualHost *:80

<VirtualHost *:80>
        ServerName example.com
        ServerAlias www.example.com
        DocumentRoot /apps/example.com/
        RewriteEngine On

        RewriteCond "%{DOCUMENT_ROOT}%{REQUEST_FILENAME}" !-f
        RewriteRule "^/(.*)$" "http://127.0.0.1:8000/$1" [P,QSA,L]

        ProxyPassReverse / http://127.0.0.1:8000/
        ProxyPreserveHost on

        <Location />
          Order allow,deny
          Allow from all
        </Location>

        <Location /WEB-INF >
          Order allow,deny
          Deny from all
        </Location>

        ErrorLog /var/log/apache2/example.com.error.log
</VirtualHost>

You will notice that in this configuration, you don't need to specify all the static stuff you want apache to serve - star dot ping, star dot js, star dot css, star dot etc etc. The rule is quite simple: if the file exists, serve it, otherwise assume it's a request for a dynamic resource. The RewriteCond line does that for you. "!-f" is mod-rewrite-speak for "test that the file does not exist"

It took two days of twiddling and tweaking to get here, which was all the more shameful because I had thought this static-serving-apache stuff was all already working. I was so wrong. In particular, I had included the ProxyPass / http://127.0.0.1:8000/ directive, which apparently overrides the RewriteRule directive, causing everything to go through the proxy, regardless of whatever rewrite rules you have configured. I made this error as a result of reading other peoples' blogs, something you should never try at home. If I had written this entry before reading all those others, I would have been spared all the hassle. Fortunately however, now you are.

Another error I had made: the accursed trailing slash. Note carefully the difference between these two:

        DocumentRoot /apps/example.com/
        RewriteCond "%{DOCUMENT_ROOT}%{REQUEST_FILENAME}" !-f

and

        DocumentRoot /apps/example.com
        RewriteCond "%{DOCUMENT_ROOT}/%{REQUEST_FILENAME}" !-f

Both of these work, but the following doesn't:

        DocumentRoot /apps/example.com/
        RewriteCond "%{DOCUMENT_ROOT}/%{REQUEST_FILENAME}" !-f

In this last case, apache will always believe that the requested file does not exist, and will always forward to the proxy. And that sucketh, as Shakespeare might have said.

Here's an example of a blog entry you must never take rewriterule advice from (although the load-balancing suggestions appear to be accurate) - http://www.conandalton.net/2008/08/apache-2-modproxy-load-balancer.html. On the other hand, http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html is the fountain of apache-fu, read that before reading anything else.

17 November 2008

risible-db: database migrations in java: release 0.2

It is with joy and delight that I announce risible-db-0.2, our beloved risible migrations project, now with support for Oracle!

Risible-db is an ultra-simple database schema migration utility, not as fancy as Rails, you just write plain SQL (really, how many times do you change DB vendor mid-project and think "wow, it's lucky we used a platform-independent DB abstraction layer!" ?). By convention, it looks for SQL scripts in the migrations subdirectory of your WEB-INF directory, and will execute any that have not previously been executed.

So the only reason it needs Oracle support is to create the initial migrations table.

Usage has changed little, this is how to configure it with Spring (you can imagine the equivalent java code) -

applicationContext.xml
<bean id="migrations" class="risible.db.WebInfMigrations" init-method="start">
  <property name="dataSource" ref="mysqlDataSource"/>
  <property name="migrationTableName" value="migrations"/>
  <property name="migrationTable">
    <bean class="risible.db.MySqlMigrationTable"/>
  </property>
</bean>

The migrationTable property specifies the db-specific table creation strategy. You can use risible.db.MySqlMigrationTable, risible.db.OracleMigrationTable, or roll your own implementation of risible.db.MigrationTable - a one-method interface whose implementation creates a table if necessary.

download here!

Enjoy, and let us know how it works for you ...

11 November 2008

little javascript trap

Sometimes the Spartan simplicity of programming with java interfaces has a lot of appeal, especially when faced with the way Rails treats each class as an enormous hold-all for a gaggle of methods. Pros and cons yadda yadda yadda.

Having got that out of the way: Rails' to_json rocks - instant serialisation of your Ruby objects into Javascript objects. We need a JSR to do this to java.lang.Object (I mean, how many times have you needed wait() and notify()?), so Java may become Web-2.0 enabled/compliant.

Anyway, there I was merrily scripting freemarker to do what Rails gives us for free, and everything's honkey-dorey on Firefox and Safari, but Opera is choking. After the requisite banging-head-on-wall, I spot a trailing comma.

<html>
  <head>
  </head>
  <body>
    <script type="text/javascript">
      var x = {a:1, b:2, c:3, d:4,}; // trailing comma here. This isn't the original code, btw, in case you're thinking, "duh!"
      for (var i in x) {
        document.write("<div>" + i + " is " + x[i] + "</div>");
      }
    </script>
  </body>
</html>

Warning: Opera don't like trailing commaz. Solution: switch to a language that provides to_json, or keep suffering. Or drop support for Opera. Speaking of not supporting stuff, I've no idea what IE does. How dangerous is that? Here are my browser stats for the last 30 days:

FF64%
IE24%
Chrome4%
Safari3.9%
Opera2.7%

Chrome? Google so pwns you dudes lol

cheers, and good luck with the code generation ...

07 November 2008

Acknowledge Feelings

Acknowledging the feelings of another person is a powerful technique we learned from Faber-Mazlish.

One day Sapphire, our delightful daughter, about a year old at the time, was climbing over Bingo the Dog (a noisy plastic toy), fell flat on her face, and started screaming. I picked her up to comfort her, but she struggled and screamed even more. I thought, hopeless father, why did I ever have children, I'm not cut out for this, etc ... and all of a sudden realised she wasn't crying out of pain, she wanted to get back to her toy and I was preventing her! Her tears dryed up pretty fast once I put her back on the floor. But my understanding of crying was forever changed. It wasn't simply "crying => pain" anymore, crying is now an ambiguous message calling for emotionally-sensitive context-dependent interpretation. This wasn't going to be easy for my INTP personality.

Fast forward 18 months. Sometimes she trips over a toy and bashes her face on the floor - actually hurting herself. As usual, my (hopelessly wrong) intuition would suggest I say it's nothing, don't worry about it, you're not really hurt, it'll get better, stop whining, hey, get over it, it's only a bruise, it's not the end of the world, for god's sake! Here's the updated alternative:

Conan(picks Sapphire up, cradles her in arms, adopts a concerned, caring-parent expression)
Sapphire(screaming in agony)
ConanSapphire, are you hurt?
Sapphire(screams some more)
ConanIs it the end of the world?
Sapphire(screams continue, but she appears to be nodding in agreement)
ConanDid you bash your face on the floor?
Sapphire(screaming) Yes!!
ConanThat must hurt a lot!
Sapphire(screaming less) Yes!!
ConanDid you crack your skull open?
Sapphire(a little calmer) Yes!!
ConanIs your nose all squashed?
Sapphire(crying, still) Yes!!
Conanouch!
Sapphire(sobs gently)
Conan(points to the floor where she fell) Is this where you hit the floor?
Sapphire(sobbing gently) Yes
ConanOh dear, it must have hurt you a lot.
Sapphire(moaning) Yes. Urting.
Conan(pointing to the totally wrong place, her shoulder for example) Does it hurt here?
Sapphire(points to her head) No, here. Ed ache.
ConanOh, your head hurts?
SapphireYes, ed ache. My ed ache.
ConanSapphire, tell me, what do the wheels on the bus do?
Sapphire(sniff) Go wound and wound.
ConanAnd the horn on the bus?
SapphireGo beep-beep-beep.
ConanIndeed ... do you want to play some more?
SapphireYes (Gets down. Surveys the situation. Wipes her nose in her sleeve. Resumes activity. All is well)

NLP teaches us that 90% of our communication is non-verbal, which is why you can get away with "did you crack your skull open?". Empathise, let your tone of voice and facial expression reflect how you feel, and the words don't really matter so much. Once you establish empathy, you can change the tone of the conversation, and her mood will follow. The wheels on the bus, or whatever her favourite daytime song is, makes a great distraction, and then she's ready to get back to clambering over Bingo.

It doesn't seem to work so well among adults, at least not if you're unsubtle or the victim knows what you're up to. Sincerity is really the key here.

 (late one evening)
Conan(despairingly) This PayPal developer API is really complicated, I don't know how I'll ever make money :(
Sabrina(tongue firmly in cheek) Oh, Conan, you must be really frustrated, it sounds so difficult ...
ConanDon't try your Faber-Mazlish stuff on me, dammit!
 (all burst out laughing)