Seaside Laboratory

Posts

意外と奥が深い CGI でのページング

うちの掲示板はあまり使われていないので負荷は無いに等しいと思っていたが、スパムボットがクローリングと投稿を繰り返すので、思いのほか負荷が高い。そこで掲示板をより高速に処理できる静的 HTML 版に作り変えることにした。

掲示板は投稿より閲覧の方が多いという性質があるので、投稿が行われたときにログを HTML ファイルとして書き出しておけば、閲覧は HTML の転送だけで済み、サーバの負荷を軽減することが出来る。投稿時の負荷が少し高くなってしまうというデメリットはあるが、頻度は低いのでトータルで見れば問題はない。

静的 HTML 化

CGI が動作するのは投稿時のみになり、動的に行っていたページング処理は一切行えなくなるので、トップページだけでなく過去ログも HTML 化しておく必要がある。プログラム的にはトップページも過去ログも連続したページのひとつに過ぎないので、全てのページを出力しておけばいい。

# ページ数の算出
総ページ数 = ceil(投稿件数 / ページ当たりの件数);

# 全ページを出力
for ($page = 1; $page <= 総ページ数; $page++)
{
    # 対象ページの HTML を出力
    output($page);
}

一見、問題ないように見えるが、投稿件数が 0 だった場合は総ページ数も 0 になり、プログラムは一度も HTML を出力することなく終了してしまう。トップページが無いということは投稿フォームも無い、つまり投稿が行えないという困った事態になってしまう。

掲示板のようなプログラムでは投稿が無い状態でも総ページ数が 1 になる計算の方が都合が良い。ページ処理に関するノウハウはライブラリから学ぶのが手っ取り早いだろうということで、PHP と Perl 向けに作られたライブラリのソースを解析してみることにした。

PHP の Pager

まずは、PEAR にある Pager というライブラリ。

function _generatePageData()
{
    // Been supplied an array of data?
    if (!is_null($this->_itemData)) {
        $this->_totalItems = count($this->_itemData);
    }
    $this->_totalPages = ceil((float)$this->_totalItems / (float)$this->_perPage);
    $i = 1;
    if (!empty($this->_itemData)) {
        foreach ($this->_itemData as $key => $value) {
            $this->_pageData[$i][$key] = $value;
            if (count($this->_pageData[$i]) >= $this->_perPage) {
                $i++;
            }
        }
    } else {
        $this->_pageData = array();
    }

    //prevent URL modification
    $this->_currentPage = min($this->_currentPage, $this->_totalPages);
}

$this->_totalPages が総ページ数を表す変数で、計算方法は先程の例と大した違いはなく、投稿がないときは 0 になる。

Perl の Data::Page

次は、CPAN にある Data::Page というライブラリ。

sub last_page {
    my $self = shift;

    my $pages = $self->total_entries / $self->entries_per_page;
    my $last_page;

    if ( $pages == int $pages ) {
        $last_page = $pages;
    } else {
        $last_page = 1 + int($pages);
    }

    $last_page = 1 if $last_page < 1;
    return $last_page;
}

$last_page が総ページ数を表す変数だが、最後の方で総ページ数が 1 未満の場合は 1 に補正する処理があるので、どんな場合でも 1 ページが保証される作りになっている。

最後に

ライブラリは多くの人が使うもので、言語は違えど同じような内容になると思っていただけに、この方針の違いは興味深かった。

perldoc の Data::Page に書かれている覚書が印象的だったので引用しておく。

CPAN モジュールにしてはこのコードは "単純すぎやしないか" とは、以前から言われてきたことだ。だが、私は同意しかねる。この種のコードを何度も何度も書いては、いつも間違えている人々を見てきた。おそらく、こうしている今も彼らはコードを直すために、さらなる時間を 費やしているだろう……