March 23, 2011

PerlとApacheでChunked EncodingなResponseを返す





通信テストの目的でChunked EncodingなHTTP Response Trafficを流すために必要だったことを書き残します。


[Topology]
Client---Server

[Client]
Windows 7
Firefox 3.6.15

[Server]
Ubuntu 10.04
Apache: 2.2.14
Perl: 5.10.1


Chunked EncodingはResponseのContent-Lengthが決まっていない場合に用いるもので、CGIなどによって動的にコンテンツが変化する場合やストリーミング等のコンテンツはウェブサーバは通常Chunked EncodingでResponseを返そうとします。
コネクション接続時にコンテンツの終端(Content-Length)が決定していないので、持続接続を継続する必要があります。そのためにConnectionヘッダにkeep-aliveを指定する必要があります。
また、これらの条件をカバーするためにはHTTP 1.1でリクエストする必要があります。

私の環境では一点引っかかってChunkedで返さない状況がありました。
Apacheでmod_deflateが有効になっていて、Transfer-Encoding: gzipで返すようになっていました。
mod_deflateはテキストデータなど圧縮することで転送効率が高まるケースでは有効なmoduleで、クライアントのリクエストにもAccept-Encodingヘッダで許容しているケースが多いかと思います。

この場合、Apacheがレスポンスデータをgzipでアーカイブします。サーバ上の動的処理が完了しgzipで圧縮され、Content-Lengthが確定し、下記のようにgzip形式のデータがクライアントに返ってきます。例はWiresharkのFlow TCP Streamで、赤ハイライトがクライアントリクエスト、青ハイライトがサーバレスポンスです。Content-Lengthがついてレスポンスが返って来るので、Chunkedにはなりません。



GET /cgi-bin/comfort.pl HTTP/1.1
Host: 10.0.0.1
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; ja; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15 ( .NET CLR 3.5.30729; .NET4.0C)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: UTF-8,*
Keep-Alive: 115
Connection: keep-alive
Cache-Control: max-age=0


HTTP/1.1 200 OK
Date: Wed, 23 Mar 2011 07:59:17 GMT
Server: Apache/2.2.14 (Ubuntu)
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 38
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Content-Type: text/plain

..........3.R(I-..2..FP..J.@i....?(...




このgzip Encodingを無効にするため、Apacheの設定で除外させる必要があります。
一例として、User-Agentの文字列マッチングで条件を絞ります。変更するファイルは /etc/apache2/sites-available/default にあたる設定ファイルの、該当するDirectoryです。"Mozilla"から始まるUser-Agentにマッチするリクエストに対して、"no-gzip"を適用させます。(ファイルタイプでマッチングさせる方法等、いくつかの方法があります)

<Directory "/usr/lib/cgi-bin">
  AllowOverride None
  Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
  Order allow,deny
  Allow from all
  BrowserMatch ^Mozilla no-gzip
</Directory>


これで、私の環境ではChunked Encodingでレスポンスが返るようになりました。


GET /cgi-bin/comfort.pl HTTP/1.1
Host: 10.0.0.1
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; ja; rv:1.9.2.15) Gecko/20110303 Firefox/3.6.15 ( .NET CLR 3.5.30729; .NET4.0C)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: UTF-8,*
Keep-Alive: 115
Connection: keep-alive
Cache-Control: max-age=0


HTTP/1.1 200 OK
Date: Wed, 23 Mar 2011 07:56:23 GMT
Server: Apache/2.2.14 (Ubuntu)
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/plain

8
0: test

8
1: test

8
2: test

8
3: test

8
4: test

0




ちなみにサーバ側でPerlスクリプトを書きました。イテレータでChunkを好きな頻度で返してます。

#!/usr/bin/perl
$| = 1;

print "Content-type: text/plain\n\n";
for $x(0..4) {
print "$x: test\n";
sleep 5;
}

exit 0;

$| はPerlの特殊変数で、0以外の値の時にSTDOUTのAuto Flushが実施されます。0(Default)の場合はサーバ側でレスポンスがバッファリングされます。
テスト目的のためには継続的なレスポンスが欲しかったので、Perlでのバッファリングも回避させておきます。



余談ですが、Firefox(3.6.15)でHTTP Requestの仕方で、ヘッダに変化があります。

URLフォームにURLを入力してEnterを押す方法とは別に、Reloadを実行すると下記ヘッダが付与されます。
Cache-Control: max-age=0

Shiftを押しながらReloadボタンを押すと、下記ヘッダが付与されます。
Pragma: no-cache
Cache-Control: no-cache


Controlを押しながらでは何も変わりません。

参考: What's the difference between Cache-Control: max-age=0 and no-cache ?