HTTPのgzip圧縮コンテントをzlibで展開
ちょっとマニアックな内容ですが、個人的にちょっとはまったので、備忘録として書いておきます。
HTTPでは、Content-Encodingヘッダにgzipと書いてあると、コンテント部がgzip圧縮されたものだということになります。
apacheだと、次のような設定をすると、応答がgzip圧縮になります。
「
<Location />
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/x-js text/css
</Location>
」
ネットワークトラフィックを減らすためだと思いますが、当然、サーバのCPU負荷は高くなるでしょう。
いずれにしても、このようにgzip圧縮されたコンテントをプログラムで扱おうとすると、展開しなければ中身を見ることができません。
一度ファイルにコンテントを書き出して、gzipコマンドで展開させても良いのですが、コマンド呼び出しはオーバーヘッドも大きいですし、エラー処理もやりにくいので、やはりプログラム内で展開したいところです。幸いなことにzlibというライブラリが公開されています。
zlibの使い方はネットを探すといろいろとでていて、サンプルソースもあります。サンプルを参考に作るとざっと次のような感じになります。
「
int gzipUncompress(char **data,int *size)
{
#define INBUFSIZE 1024
#define OUTBUFSIZE 1024
z_stream z;
char inbuf[INBUFSIZE];
char outbuf[OUTBUFSIZE];
int count,status;
int lest;
char *ptr,*outp;
char *newdata=NULL;
int newsize=0;
char buf[80];
z.zalloc=Z_NULL;
z.zfree=Z_NULL;
z.opaque=Z_NULL;
z.next_in=Z_NULL;
z.avail_in=0;
/*
if(inflateInit(&z)!=Z_OK){
fprintf(stderr,"inflateInit:%s\n",(z.msg)?z.msg:"???");
return(-1);
}
*/
if(inflateInit2(&z,16+MAX_WBITS)!=Z_OK){
fprintf(stderr,"inflateInit:%s\n",(z.msg)?z.msg:"???");
return(-1);
}
z.next_out=outbuf;
z.avail_out=OUTBUFSIZE;
status=Z_OK;
ptr=(*data);
lest=(*size);
while(status!=Z_STREAM_END){
if(z.avail_in==0){
z.next_in=inbuf;
if(lest>INBUFSIZE){
memcpy(inbuf,ptr,INBUFSIZE);
z.avail_in=INBUFSIZE;
ptr+=INBUFSIZE;
lest-=INBUFSIZE;
}
else{
memcpy(inbuf,ptr,lest);
z.avail_in=lest;
ptr+=lest;
lest-=lest;
}
}
status=inflate(&z,Z_NO_FLUSH);
if(status==Z_STREAM_END){
break;
}
if(status!=Z_OK){
fprintf(stderr,"inflate:%s\n",(z.msg)?z.msg:"???");
inflateEnd(&z);
return(-1);
}
if(z.avail_out==0){
if(newdata==NULL){
newdata=(char *)malloc(OUTBUFSIZE);
outp=newdata;
newsize=OUTBUFSIZE;
}
else{
newdata=(char *)realloc(newdata,newsize+OUTBUFSIZE);
outp=newdata+newsize;
newsize+=OUTBUFSIZE;
}
memcpy(outp,outbuf,OUTBUFSIZE);
z.next_out=outbuf;
z.avail_out=OUTBUFSIZE;
}
}
if((count=OUTBUFSIZE-z.avail_out)!=0){
if(newdata==NULL){
newdata=(char *)malloc(count);
outp=newdata;
newsize=count;
}
else{
newdata=(char *)realloc(newdata,newsize+count);
outp=newdata+newsize;
newsize+=count;
}
memcpy(outp,outbuf,count);
}
if(inflateEnd(&z)!=Z_OK){
fprintf(stderr,"inflateEnd:%s\n",(z.msg)?z.msg:"???");
}
free(*data);
*data=newdata;
*size=newsize;
return(0);
}
」
ここで、ポイントは、前準備に、
「
inflateInit(&z)
」
ではなく、
「
inflateInit2(&z,16+MAX_WBITS)
」
を使うことです。多くのサンプルはinflateInit()を使っているのですが、これだとgzipファイル形式のヘッダに対応していないので、データを処理するところでエラーになってしまいます。より簡単な関数のuncompress()も駄目です。
HTTPのgzip圧縮形式はgzipファイルと全く同じものなので、inflateInit2()を使えば問題なく展開できます。
なお、HTTPではチャンク形式でさらにgzip圧縮、というのもあるので、その場合はまずチャンクをきちんと受け取り、それからgzip展開、という順に行えば大丈夫です。
ということで、プログラムでzlibを使ってgzipファイル形式を展開するときには、初期化関数に注意しましょう、という話題でした。