node-canvas でアクセスカウンタを作った

古のホームページに必ず設置されていた、アクセスカウンタ。当サイトも XREA がレンタルしていたアクセスカウンタを設置していたが、いつの間にかサービス終了してしまった。

そこで今回、当時設置していたアクセスカウンタのデザインを再現したモノを、node-canvas というパッケージを用いて作ってみた。

目次

デモ

2024-10-21 より、当サイトのトップページ下部にのみ、アクセスカウンタを配置している。スクリーンショットはこんな感じ。

デモ

右半分の3行のデジタル時計みたいな表示のヤツが、今回お話するアクセスカウンタ。

という形で表示してある。

左側の 88x31px のバナーは全く関係ないお遊びなのでお気になさらずw

ソースコード

ソースコードは以下。

実装内容

当時のアクセスカウンタの多くは、恐らく img[src] に指定した URL が CGI で、GET リクエストを検知したらアクセス数を集計し、レスポンスとして描画した画像を返す、という仕組みだったと思う。画像の生成には ImageMagick などを使用していたのだと思われる。

今回は「カウンタ画像の表示」と「PV のカウント処理」を分離してみたいなと思ったので、分離した。

カウント方法

このサイトは 2024-09-15 にダークテーマ対応した時から、サイト内の全ページで共通の JS ファイルを読み込むようにしている。その中に、以下のような単純な Fetch を書いた。

fetch('https://【カウンタ API】/ct/pv?id=【サイト ID】').catch(error => console.error(error));

ただの GET リクエスト。コレを API サーバが受け取ったら、SQLite DB にアクセス数を加算して保存する、という流れだ。

要はこの Fetch を呼び出したらインクリメントされるので、セッションやユーザの単位は考慮していない、本当に純粋な PV 数ということになる。

実際にはオマケで、リファラや開いたページのタイトルなどもクエリパラメータに乗せて、簡単なアクセスログも拾えるようにした。ココはお好みで。

カウンタ画像の描画方法

カウンタ画像の描画には、前述のとおり node-canvas というパッケージを使用した。コチラは Node.js 上で HTML5 の Canvas が作成できるというモノなのだが、特にブラウザやネイティブパッケージへの強い依存というのがなくて、単純な npm install ですぐに動かせたので、気軽に画像生成できて良さそうである。

デジタル時計のような表示は、以前「Neo's Timer」という簡単なアプリを作る時にも使った、ClockIcons という Web フォントを用いている。ココはお好みで。

node-canvascreateCanvas() をしたら、あとは Canvas#getContext('2d') を引っ張ってきて、図形を置いたり文字をレンダリングしたりできる。ココは実際のコードもご覧いただくと、HTML5 Canvas と違いなく扱えていることが分かるかと思う。

こうして作成した Canvas を PNG 形式に変換し、node:stream を用いて FileStream に変換してやることで、HTTP リクエストに対するレスポンスとして画像を流せるようにしている。

const fileBuffer = canvas.toBuffer('image/png');
const fileStream = stream.Readable.from(fileBuffer);

NestJS で画像をレスポンスするエンドポイントを作る

さて、今回 API サーバ部分は、個人的にお気に入りの NestJS で作っている。コチラは Express.js をラップしているモノなので、恐らく Express.js でも同等のことが出来ると思う。

GET リクエストに対して、先程生成した FileStream の画像データをレスポンスするには、次のように FileStream#pipe() を用いる。

export class CounterController {
  public responseImage(@Res() response: Response): void {
    // 前述の手順で node-canvas で画像を生成し、FileStream を用意する
    const fileStream = stream.Readable.from(fileBuffer);
    
    // Response に pipe() する
    fileStream.pipe(response.status(HttpStatus.OK));
  }
}

FileStream を取り扱ったのがほとんど初めてで、ちょっとおもしろい書き方だなーと思ったが、コレで良いらしい。

コレで、このエンドポイントにアクセスすれば画像が返されるようになったので、この URL を img[src] に設定してやれば良い。

以上

ちょっと雑な解説になってしまったが、詳しくはソースコードを見ていただきたい。そんなに文量ないので…。

懐かしのアクセスカウンタを、Node.js で再現できて楽しかったです。