オルタナティブ・ブログ > プログラマー社長のブログ >

プログラミングでメシが食えるか!?

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ファイル形式を展開するときには、初期化関数に注意しましょう、という話題でした。

Comment(2)