오래전 이야기/Squid

Etag and Expire headers in Django, Apache, and Lighttpd

리눅스 엔지니어였던 2008. 12. 8. 20:46

 

I mentioned YSlow in my last post in reference to speeding up the sites you develop. Depending on your skill level, however, you may or may not be aware of ways in which you can implement a lot of those changes.

Things like ETags and expire headers can be a little esoteric to people who don’t deal with that kind of stuff every day, so I thought I’d put together a few links where you can learn more about how you can quickly implement those changes to speed up your sites.

In this article I talk about Etags and Expire headers only. I also focus my explainations on how to implement those items in three environments: Django, Apache, and Lighttpd. If you want to implement these in other frameworks and servers, I’m sorry but I can’t hit ‘em all! :)

Any corrections should be added in the comments or emailed to me directly: clintecker@gmail.com

ETags

An Etag is an HTTP header that was introduced in HTTP 1.1. The tag is sent along with an item (CSS file, image, javscript file) that uniquely identifies that file in time. The Etag is a string that’s generated by the server and changes when the file changes. So the idea is that clients will only grab that resource if the Etag changes.

Django Etags

If you’re a Django developer, you can get Etags all going by adding the django.middleware.http.ConditionalGetMiddleware to your MIDDLEWARE_CLASSES section of your settings.py file. Link

Apache Etags

If you serve your static files using Apache and not Django (good job!) you can tell Apache to send along Etag headers by adding the following line to an .htaccess file in the directory that contains those files:

FileETag MTime Size

This bit of code instructs Apache to send along an Etag value that is calculated by combining the modification time and file size of the file being transmitted. Documentation

Lighttpd Etags

If you’re serving your static files using lighttpd (even better!) you should know about the following four lines in your configuration file

  • etag.use-inode: Determines if inode-value is used in ETag generation
  • etag.use-mtime: Determines if mtime-value is used in ETag generation
  • etag.use-size: Determines if size-value is used in ETag generation
  • static-file.etags: Determines if ETags are generated or not

You should probably set any combination of those first three to “enabled” and you should definitely set the fourth option to “enable”. Those options may or may not already be in your configuration file, so you may have to add them manually.

Expires headers

Django expiration

There are a number of methods of setting the Expires header on your Django views. The two most popular methods are applying a single expiration date to every item that your Django project serves up. This is accomplished by adding django.middleware.cache.CacheMiddleware to the top of your MIDDLEWARE_CLASSES setting in your settings.py file. The Django documentation lists the following effects of adding this:

Additionally, CacheMiddleware automatically sets a few headers in each HttpResponse:

  • Sets the Last-Modified header to the current date/time when a fresh (uncached) version of the page is requested.
  • Sets the Expires header to the current date/time plus the defined CACHE_MIDDLEWARE_SECONDS.
  • Sets the Cache-Control header to give a max age for the page — again, from the CACHE_MIDDLEWARE_SECONDS setting.

Important! It should also be noted that you need to add a CACHE_BACKEND setting to your Django configuration for these caching mechanisms to take effect.

Alternatively, you can specify different views to expire after different lengths of time. In this configuration, you do not add the CacheMiddleware. Instead you use the cache_page decorator in django.views.decorators.cache like so (taken from the Djangoproject.com documentation):


from django.views.decorators.cache import cache_page
def slashdot_this(request):
    ...
slashdot_this = cache_page(slashdot_this, 60 * 15)

The value, 60*15 corresponds to the length of time (in seconds) that should elapse before the content expires. Not only does this set the Expires HTTP header, the content of your view will be cached by whatever method you specified in your settings and will not expend computational or database resources until that time you specified—15 minutes in the above example.

Apache Expiration

For Apache to send along Expires with your staticly served files, you will need to make sure that mod_expires has been installed on your server. It is fairly standard and none of my servers I checked (that includes OS X’s Apache installation), didn’t have mod_expired on the system. You may have to search through your httpd.conf file and uncomment (remove the pound-signs) from the following lines, however:

#LoadModule expires_module libexec/httpd/mod_expires.so

#AddModule mod_expires.c

Once you’ve uncommented these lines, you should restart Apache. It differs depending on your system, but generally you can issue the following command:

sudo apachectl restart

If that didn’t work, look through your system’s manual or documentation for more information on how to restart Apache.

Once that’s done, go to the directory where your statically served files are contained and add or edit the .htaccess file in that directory. Add the following lines:


<ifmodule mod_expires.c>
  <filesmatch "\.(jpg|gif|png|css|js)$">
       ExpiresActive on
       ExpiresDefault "access plus 1 year"
   </filesmatch>
</ifmodule>

The following bit: jpg|gif|png|css|js specifies the file types you’d like to target and this other bit: ExpiresDefault "access plus 1 year" specifies how long into the future you’d like to set the expiration date. In this case it’s one year into the future.

Lighttpd Expiration

Adding Expires headers to your statically-served files from Lighttpd is done much like it was in Apache. First you uncomment the following line near the top of your configuration file:

# "mod_expire",

Once you’ve done that, you can add the following line of code to apply the Expires tag to all of the files in your images directory:

expire.url = ( "/images/" => "access 1 hours" )

And the following for /images and all its sub-directories:

$HTTP["url"] =~ "^/images/" {
     expire.url = ( "" => "access 1 hours" )
}

You could only apply it to certain file types like so:

$HTTP["url"] =~ "\.(jpg|gif|png|css|js)$" {
     expire.url = ( "" => "access 1 hours" )
}

In all of the above examples, the files will “expire” one hour after they were last accessed.

There’s an issue here.

The problem here is that if you update these files, most clients who have already accessed them once before, will refuse to re-download a copy until the date in the future you’ve specified (and just use their cached version). The question arises: “What happens if you want to change these files and have all clients re-download them?”

In Django proper you can manually expire the cache using the save() method of a module or manually expire the cache using the low-level cache interface or a special CacheManager

With the Apache and Lighttpd options, this is a bit difficult. The easier solution is to just change the name of the resource. One solution that I’ve seen deployed in the wild is one that’s used by Ars Technica. I didn’t implement this, but I know the guy who did. He basically appends a version string to the filename, like so:

<script src="http://media.arstechnica.com/shared.static/ jscripts/jquery.v2069609287.js" type="text/javascript"/>

I’m not 100% sure how that version number is incremented or what it refers to, but what it does do is change on each new iteration of those files. Clients will have never downloaded that file before, will grab it, and cache it for a bazillion years (or until he deploys a new version of that file). This may or may not be feasible for you, but you should keep this small snag in mind if you plan on implementing Expires on your static content.

That’s it for now, I hope this helps. This got a bit long, but I will also show in a future article how you can employ Gzip’ing to crunch down your static files so they download faster, as well as using sprite images to reduce the number of connections the client needs to make to your server.

=====================
<출처: http://phaedo.cx/archives/category/lighttpd/ >