Byteserving in mod_perl
Byteserving in mod_perl
Apache is an exceptionally good Web server. Part of what makes it so good is that it is fully HTTP/1.1 compliant. It properly handles all of the various HTTP protocol features designed to reduce the strain on the Internet--both the bandwidth consumed by users, and the servers that make the Internet possible. When Apache is in charge of serving static files, it manages all these features itself (HEAD requests, If-Modified-Since headers, entity tags, and so on). Unfortunately, for dynamic, mod_perl-based applications, much of HTTP/1.1 compliance is taken out of Apache's direct control and placed into the hands of developers, who typically let compliance fall by the wayside.
One place where HTTP/1.1 compliance is critical is when serving dynamically generated PDF documents. Typical PDF browser plug-ins support (and often demand) the concept of a Range request, or byteserving, as the resulting server response is known. A full implementation of byteserving by the client and server reduces data transfer for large documents where the end user is only interested in parts of the file, such as when viewing an outline on the first page of a large PDF document.
Because Apache comes with the capability to byteserve already built in to its default content handler, an API for handling byteserving in dynamic programs is made available through the mod_perl API. By utilizing this API, mod_perl developers can dynamically serve PDF documents properly.
The set_byterange() and each_byterange() methods are the keys to dynamically serving a PDF content successfully from your mod_perl program. set_byterange() does the job of examining the incoming headers, as well as setting the Content-Type and Content-Range, and returning true if the request is a Range request. If byteserving is required, calling each_byterange() will return a series of offsets and lengths corresponding to the client request, which you can then use to isolate fragments of the data and send it along.
A typical usage of these methods from a mod_perl handler might be as follows:
my $fh = Apache::File->new($r->filename); $r->headers_out->set('Accept-Ranges' => 'bytes'); $r->set_content_length(-s $r->finfo); my $range_request = $r->set_byterange; $r->send_http_header(); if ($range_request) { while( my($offset, $length) = $r->each_byterange) { seek $fh, $offset, 0; $r->send_fd($fh, $length); } } else { $r->send_fd($fh); }