ブラウザからリクエストがあった際にサーバーが巨大なデータを処理 (大量のデータの圧縮など) する必要がある場合、 ナイーブな実装では サーバー上ですべての処理が終わってからダウンロードが開始される という振る舞いになってしまい、 ダウンロード開始待ちの時間が長すぎてしまう可能性がある.

そのような場合に プロセス間通信 (IPC) を活用することで、ファイルに書き出すことなくプロセスが処理した結果を逐次ブラウザにダウンロードさせることができるかもしれない.

名前付きパイプの活用

名前付きパイプ (named pipe) を活用することで、ファイルに書き出すことなくプロセス間でデータを直接やり取りすることができる.

通常のパイプとは異なり、名前付きパイプはファイルシステムを利用する. mkfifo コマンドで名前付きパイプを作成することができる.

$ mkfifo mypipe

$ ls -l
total 0
prw-r--r--  1 annpin  staff  0 Nov  2 16:14 mypipe
^
 \
  "p" という見慣れないフラグがセットされる.

作成された名前付きパイプ mypipe に対し、2 つのプロセス (読み込み用プロセス、書き込み用プロセス) がアクセスすることができる.

名前付きパイプの動作

例えばシェルを 2 つ開き、それぞれを同じディレクトリへと cd したとする. 最初に左側 (読み込み用) のシェルで cat mypipe してみると、通常の cat とは異なりいつまで待ってもシェルの制御が戻ってこないということが確認できる. これは名前付きパイプに何らかのデータが流し込まれてくるのを待ち受けしている状態である. 次に右側 (書き込み用) のシェルで echo "Hello, named pipe!" > mypipe を実行してみるとこのコマンドはすぐに終了し、その直後に左側の読み込み用シェルの cat が結果 Hello, named pipe! を返して 制御がシェルに戻されることとなる.

$ ls -l                                                | $ ls -l
total 0                                                | total 0
prw-r--r--  1 annpin  staff  0 Nov  2 16:19 mypipe     | prw-r--r--  1 annpin  staff  0 Nov  2 16:19 mypipe
                                                       |
$ cat mypipe                                           |
                                                       |
                                                       | $ echo "Hello, named pipe!" > mypipe
Hello, named pipe!                                     | $
$                                                      |

このように、名前付きパイプは異なるプロセス間でのメッセージのやり取りができるようだ. mkfifo を用いてファイルの形でファイルシステム上に存在はするが、内部にデータの実体が格納されることはなく書き込み側が書き込んだ内容はそのまま保存されることなく読み込み側に渡される.

なお、逆に先に書き込み側で echo "Hello, named pipe!" > mypipe を実行した場合、今度はこの echo が制御を返さなくなり mypipe の読み込み側がデータを読み込むのを待ち続けることとなる. したがって、次に読み込み側で cat mypipe を実行すると今度は cat がすぐに Hello, named pipe! という出力をプリントして制御がシェルに戻ることになる.

名前付きパイプの利用例

例えば tar コマンドによる複数のファイルのアーカイブ生成結果を名前付きパイプに出力することで、アーカイブ生成処理が完了しないうちからブラウザにダウンロード処理を開始させることができる.

generate_named_pipe.sh
#!/bin/bash

pipe_path=...
dir_path=...

mkfifo $pipe_path
tar cvfh $pipe_path $dir_path
download.php
# 名前付きパイプを生成 (非同期)
exec('generate_named_pipe.sh > /dev/null &');

# 名前付きパイプ生成を完了させるために一定時間 sleep する.
sleep(1);

# ブラウザへのダウンロード処理を開始.
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="tar_name.tar"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Connection: close');

# バッファリングを OFF.
ob_end_clean();

# 名前付きパイプからデータを逐次取得して標準出力に出力し、ブラウザに送信.
readfile($pipe_path);

exit();

tar コマンドの場合、単なるアーカイブ生成なのでファイルの圧縮は行われない. 圧縮したい場合には、圧縮結果を標準出力に出力できるツールを使わないとパイプによるプロセス間通信には使えない.

通常の無名パイプも活用できそう

例えば (Linux における) zip コマンドの場合は、ファイル名として - を指定することで標準出力に圧縮結果を出力することができるようだ. 以下では名前付きパイプではない通常の (無名) パイプを使っているが、この場合でも名前付きパイプの場合と同様にファイルに出力することなく remotehost:8787 に圧縮結果を逐次送信するプロセス間通信となる. (nc コマンドは TCP/UDP 通信を行うためのコマンドのようだ.)

$ zip -r - files_to_be_archived | nc remotehost 8787