Support: 1-800-961-4454
1-800-961-2888

Add the Content-MD5 HTTP Header to nginx

The Content-MD5 HTTP Header was recently put in place on WordPress.org for WordPress releases to allow easier MD5 checksum verification of the download without requiring an extra HTTP request to grab an MD5 file. WordPress will soon start validating the MD5 checksum when downloading files based on this header.

I had recently implemented some code in a WordPress plugin, for downloading some source javascript files and then verifying them by requesting an MD5 file, and comparing the MD5 of the downloaded source file to that of what we expected. I had wanted to use the Content-MD5 header, but as I am running nginx, and nginx not supporting it, I decided for the time being to leave it, making the extra request.

I knew the solution to adding it was in using the nginx embedded perl module, and to make sure that I didn’t block the delivery of the content, that pulling an MD5 hex hash out of an MD5 file that lived along side the original file would be best.

I ended up writing two versions, one that would only pull the MD5 out of an MD5 file, and one that would fall back to compute the MD5 hex hash on the fly if an MD5 file didn’t exist.

Here is the finished product:

# nginx Embedded Perl module for adding a Content-MD5 HTTP header
#
# This perl module, will output an MD5 of a requested file using the
# Content-MD5 HTTP header, by pulling the hex hash from a file of the
# same name with .md5 appended to the end, if it exists.
#
# Author: Matt Martz <matt@sivel.net>
# Link: https://gist.github.com/1870822#file_content_md5_req_dot_md5.pm
# License: http://www.nginx.org/LICENSE

package ContentMD5;
use nginx;

sub handler {
    my $r = shift;
    my $filename = $r->filename;

    return DECLINED unless ( -f $filename && -f "$filename.md5" );

    my $content_length = -s $filename;

    open( MD5FILE, "$filename.md5" ) or return DECLINED;
    my $md5 = <MD5FILE>;
    close( MD5FILE );
    $md5 =~ s/^\s+//;
    $md5 =~ s/\s+$//;
    $md5 =~ s/\ .*//;

    $r->header_out( "Content-MD5", $md5 ) unless ! $md5;

    return DECLINED;
}

1;
__END__
# nginx Embedded Perl module for adding a Content-MD5 HTTP header
#
# This perl module, will output an MD5 of a requested file using the
# Content-MD5 HTTP header, by either pulling it from a file of the
# same name with .md5 appended to the end, if it exists, or will
# calculate the MD5 hex hash on the fly
#
# Author: Matt Martz <matt@sivel.net>
# Link: https://gist.github.com/1870822#file_content_md5.pm
# License: http://www.nginx.org/LICENSE

package ContentMD5;
use nginx;
use Digest::MD5;

sub handler {
    my $r = shift;
    my $filename = $r->filename;

    return DECLINED unless -f $filename;

    my $content_length = -s $filename;
    my $md5;

    if ( -f "$filename.md5" ) {
        open( MD5FILE, "$filename.md5" ) or return DECLINED;
        $md5 = <MD5FILE>;
        close( MD5FILE );
        $md5 =~ s/^\s+//;
        $md5 =~ s/\s+$//;
        $md5 =~ s/\ .*//;
    } else {
        open( FILE, $filename ) or return DECLINED;
        my $ctx = Digest::MD5->new;
        $ctx->addfile( *FILE );
        $md5 = $ctx->hexdigest;
        close( FILE );
    }

    $r->header_out( "Content-MD5", $md5 ) unless ! $md5;

    return DECLINED;
}

1;
__END__
# nginx sample configuration for adding a Content-MD5 HTTP header
# using the Embedded Perl module
#
# Author: Matt Martz <matt@sivel.net>
# Link: https://gist.github.com/1870822#file_nginx.conf

http {
    perl_modules perl/lib;
    perl_require ContentMD5.pm;

    server {
        location / {
            root /var/www;
            index index.html
        }

        location /download {
            perl ContentMD5::handler;
        }
    }
}

Here is an example of the resultant HTTP Headers:

$ curl -I 

HTTP/1.1 200 OK
Server: nginx/1.0.5
Date: Mon, 20 Feb 2012 20:19:04 GMT
Content-Type: application/octet-stream
Content-Length: 134217728
Last-Modified: Mon, 20 Feb 2012 20:18:25 GMT
Connection: keep-alive
Content-MD5: fde9e0818281836e4fc0edfede2b8762
Accept-Ranges: bytes
$ curl -i 

HTTP/1.1 200 OK
Server: nginx/1.0.5
Date: Mon, 20 Feb 2012 20:28:57 GMT
Content-Type: application/octet-stream
Content-Length: 42
Last-Modified: Mon, 20 Feb 2012 20:28:32 GMT
Connection: keep-alive
Content-MD5: 017d95ac06d4200bbcb2a5682e873fd7
Accept-Ranges: bytes

fde9e0818281836e4fc0edfede2b8762 128.bin
$ curl -I 

HTTP/1.1 200 OK
Server: nginx/1.0.5
Date: Mon, 20 Feb 2012 20:29:27 GMT
Content-Type: application/octet-stream
Content-Length: 134217728
Last-Modified: Mon, 20 Feb 2012 20:28:20 GMT
Connection: keep-alive
Content-MD5: fde9e0818281836e4fc0edfede2b8762
Accept-Ranges: bytes
$ curl -I 

HTTP/1.1 404 Not Found
Server: nginx/1.0.5
Date: Mon, 20 Feb 2012 20:29:36 GMT
Content-Type: text/html
Content-Length: 168
Connection: keep-alive

Learn more about how Rackspace can help you with hosting your WordPress site.

Tags: ,

About the Author

This is a post written and contributed by Matt Martz.

Matt Martz is a Senior Linux Systems Engineer working in Rackspace Enterprise, specializing in high performance web serving. Matt is also a Contributing Developer on the WordPress project.

Follow Matt on Twitter at @sivel or on his blog.


More
Racker Powered
©2014 Rackspace, US Inc.