Archive for September, 2006

How To Enable Client Side Cache For Static Images (in Apache)

Friday, September 22nd, 2006

I just read an interesting post (found on dzone) on client-side image caching, but unfortunately it was an ASP.NET solution for IIS. I wondered if there was an equivalent for Apache, and I was not let down.

It turns out it’s even easier in Apache since there’s already a module (mod_expires) for exactly this purpose. I configured the module and grabbed the headers for a random image on my site using the HEAD request code from this post and you can see from the before

Date: Fri, 22 Sep 2006 00:55:38 GMT
Server: Apache/2.2.3 (Unix) DAV/2 mod_jk/1.2.18 PHP/5.1.6
Last-Modified: Sat, 15 Jul 2006 16:25:02 GMT
ETag: "e5c1e3-6058-3d2cf380"
Accept-Ranges: bytes
Content-Length: 24664
Content-Type: image/png

and after

Date: Fri, 22 Sep 2006 01:32:03 GMT
Server: Apache/2.2.3 (Unix) DAV/2 mod_jk/1.2.18 PHP/5.1.6
Last-Modified: Sat, 15 Jul 2006 16:25:02 GMT
ETag: "e5c1e3-6058-3d2cf380"
Accept-Ranges: bytes
Content-Length: 24664
Cache-Control: max-age=2592000
Expires: Sun, 22 Oct 2006 01:32:03 GMT
Content-Type: image/png

that two headers were added:

Cache-Control: max-age=2592000
Expires: Sun, 22 Oct 2006 01:32:03 GMT

All I had to add to httpd.conf was

LoadModule expires_module modules/mod_expires.so
ExpiresActive On
ExpiresByType image/png A2592000

to cache png images for a month. While I was at it (since it works by mime type) I added caching for jpegs, gifs, and even JavaScript, CSS, and plain text (1 day for text files since they’re more likely to change), plus a global 5-minute default:

LoadModule expires_module modules/mod_expires.so
ExpiresDefault A300
ExpiresActive On
ExpiresByType image/gif A2592000
ExpiresByType image/jpeg A2592000
ExpiresByType image/png A2592000
ExpiresByType text/css A86400
ExpiresByType text/plain A86400
ExpiresByType application/x-javascript A86400

On Lesser HTTP Methods

Monday, September 18th, 2006

I was checking out my web server logs and noticed lots of HEAD and OPTIONS requests. Everybody knows that there are more HTTP methods than just GET and POST, but I realized that I only had a high-level understanding of what the others are for.

The HTTP 1.1 RFC defines 8 methods:

  • OPTIONS
  • GET
  • HEAD
  • POST
  • PUT
  • DELETE
  • TRACE
  • CONNECT

We all know about GET and POST; PUT and DELETE are use to upload and delete resources, and CONNECT is used for “a proxy that can dynamically switch to being a tunnel”, e.g. for SSL. So that leaves OPTIONS, HEAD, TRACE.

HEAD is like GET except that only headers and metadata are returned, no body content. This could be used to check for updated URLs, but in practice all of the web crawlers I’ve seen in my logs do GETs, most likely since the “Last-Modified” header isn’t required and isn’t guaranteed to be correct even if it exists.

So here’s some Jakarta Commons HttpClient code to test out the HEAD method on a sample URL:

HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
HttpMethod method = new HeadMethod("http://burtbeckwith.com/blog/?p=35");
client.executeMethod(method);
for (Header header : method.getResponseHeaders()) {
  System.out.println(header);
}
System.out.println("\nResponse:\n" + method.getResponseBodyAsString());

and the output:

Date: Mon, 18 Sep 2006 04:35:18 GMT
Server: Apache/2.2.3 (Unix) DAV/2 mod_jk/1.2.18 PHP/5.1.6
X-Powered-By: PHP/5.1.6
Pragma: no-cache
X-Pingback: http://burtbeckwith.com/blog/xmlrpc.php
Status: 200 OK
Content-Type: text/html; charset=UTF-8

Response:
null

The code for an OPTIONS request is very similar:

HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
HttpMethod method = new OptionsMethod("http://burtbeckwith.com/blog/?p=35");
client.executeMethod(method);
for (Header header : method.getResponseHeaders()) {
  System.out.println(header);
}

and the output:

Date: Mon, 18 Sep 2006 04:37:48 GMT
Server: Apache/2.2.3 (Unix) DAV/2 mod_jk/1.2.18 PHP/5.1.6
X-Powered-By: PHP/5.1.6
Pragma: no-cache
X-Pingback: http://burtbeckwith.com/blog/xmlrpc.php
Status: 200 OK
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8

In this case, the response does have a body, the content of the URL (the same response body as with a GET request). Also note that there’s one more header than from the HEAD request, Transfer-Encoding.

TRACE is used to loopback your request, e.g. for testing:

HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams().setConnectionTimeout(5000);
HttpMethod method = new TraceMethod("http://burtbeckwith.com/blog/?p=35");
method.setRequestHeader("foo", "bar");
client.executeMethod(method);
for (Header header : method.getResponseHeaders()) {
   System.out.println(header);
}
System.out.println('\n' + method.getResponseBodyAsString());

Here the headers are more brief:

Date: Mon, 18 Sep 2006 04:39:17 GMT
Server: Apache/2.2.3 (Unix) DAV/2 mod_jk/1.2.18 PHP/5.1.6
Transfer-Encoding: chunked
Content-Type: message/http

and the response body echoes back our request. Note that the fake “foo” header is there:

TRACE /blog/?p=35 HTTP/1.1
foo: bar
User-Agent: Jakarta Commons-HttpClient/3.0
Host: burtbeckwith.com

When I was looking for information I stumbled across the original specification for HTTP (v0.9) written in 1991 by Tim Berners-Lee. Cool stuff.

Joel Spolsky Must Be Stopped

Thursday, September 14th, 2006

Joel has been a hot topic of discussion in the blogosphere lately. It started with his Language Wars post where he ended a perfectly rational discussion about the risks of choosing between C#, Java, PHP, or Python with a highly incongruous paragraph that introduced Wasabi, the proprietary language they wrote for FogBugz development. It was so unusual and generated so much discussion that it prompted a response where Joel had to explain that he wasn’t kidding – Wasabi is real.

Along the way he disparaged Ruby and RoR for performance issues and for being too risky. Sure, it seemed strange to brag about taking on the risk of a proprietary domain-specific language at the end of a discussion about avoiding risk. That and the Ruby disrespect prompted several interesting posts (e.g. Joel, you have got to be kidding (which cites David Heinemeier Hansson’s Fear, Uncertain, and Doubt by Joel Spolsky), Has Joel Spolsky Jumped the Shark?, Joel On Ruby Performance, etc.)

But that’s not what’s got me frustrated with Joel.

(more…)

Don’t Be Afraid of Procedural Java

Wednesday, September 13th, 2006

Consider this simple parsing class:

public class Parser {

  private String s;

  public Parser(String s) {
    this.s = s;
  }

  public Result parse() {
    ...
  }

  // other private methods
}

A typical usage might be:

String toParse = ...
Parser parser = new Parser(toParse);
Result result = parser.parse();

Since the instance is only used to call a single method and then discarded, we can rewrite as:

String toParse = ...
Result result = new Parser(toParse).parse();

Since the class doesn’t implement any interfaces and is unlikely to be subclassed – it’s a utility class – it’s pretty clear that there’s no need for the class to maintain state, and that there’s no need to be instantiable. The method(s) should be static:

String toParse = ...
Result result = Parser.parse(toParse);

Now this doesn’t feel like OO programming, and that’s fine. Object-oriented code is very appropriate for many tasks but sometimes you just need to call a global method.

Autodiscovery of Hibernate Mapping Files in Spring

Wednesday, September 06th, 2006

I’m a big fan of autodiscovery – I dislike having to specify information that can be inferred programmatically. So along those lines, here’s a subclass of Spring’s LocalSessionFactoryBean (AutodiscoverSessionFactoryBean) that finds Hibernate hbm.xml mapping files in your classpath.
(more…)

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 License.