圧縮転送(GZIPによるHTMLテキストやファイルの転送)


 

 

Content-Lengthヘッダ行は、テキスト以外のコンテント、例えば圧縮ファイルなどにも有用であろう。以下に圧縮したデータをContent-Lengthヘッダ行つきで応答を返すサーブレットをお示しする。このサーブレットはクライアントがgzipで圧縮されたコンテントを受理できるかを調べ、もしgzip対応であればボディ部を圧縮してクライアントにわたす。その場合、HTTP/1.0クライアントも受理可能なようにContent-Lengthヘッダ行をつけ、また圧縮されていることをContent-Encodingヘッダ行でクライアントに通知する。圧縮の部分以外はAnotherHelloWorldの応用なので、詳細な説明は不要であろう。

読者はTelnetでどの程度の圧縮効果があるか調べると面白い。

package basic_servlets;

 

 

/**

 * このサーブレットは圧縮したコンテントの応答の見本である。

 * 作成日 : (01/06/05 14:53:21)

 * @author: Terry

 */

 

import java.io.*;

import java.lang.Math.*;

import javax.servlet.*;

import javax.servlet.http.*;

import java.util.zip.GZIPOutputStream;

 

public class CompressedHelloWorld extends HttpServlet {

 

    public void doGet(HttpServletRequest request, HttpServletResponse response)

        throws IOException, ServletException {

        performTask(request, response);

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)

        throws IOException, ServletException {

        performTask(request, response);

    }

/**

 * 本サーブレット情報の文字列を返す。

 */

public String getServletInfo() {

    return "CompressedHelloWorld, Version 1.0 by Terry";

}

/**

 * 本サーブレットの処理部分

 */

public void performTask(HttpServletRequest request, HttpServletResponse response)

        throws IOException, ServletException {

 

        response.setContentType("text/html; charaset=Shift_JIS");   //コンテント・エンコーディングの通知

        OutputStream os = response.getOutputStream();               //コンテナへのストリームを取得

        ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);   //バイトバッファを用意

        String zipped;                                              //zipの可否のフラグ

        OutputStreamWriter osw;

        String acceptedEncodings = request.getHeader("accept-encoding");//クライアントの受理可能エンコーディング

       

        if ((acceptedEncodings != null)

            && (acceptedEncodings.indexOf("gzip") != -1)) {         //クライアントがgzip受理可能の場合

            GZIPOutputStream gzipos = new GZIPOutputStream(baos);   //バイトバッファをgzipエンコーダでラップ

            osw = new OutputStreamWriter(gzipos, "Shift_JIS");      //これを文字コンバータでラップ

            zipped = " : gzip圧縮送信";

            response.setHeader("Content-Encoding", "gzip");         //クライアントにgzipを通知

            Writer out = new BufferedWriter(osw);                   //文字コンバータをバッファリング

            writeHtml(out, zipped);                                 //HTMLドキュメントの書き込み

            out.flush();                                            //これをgzipエンコーダまで流し込む

            gzipos.finish();                                        //エンコードを終了させ

            gzipos.close();                                         //バイトバッファに流しだす

        }

        else {                                                      //クライアントがgzip受理できないときは

            osw = new OutputStreamWriter(baos, "Shift_JIS");        //通常の文字コンバートのみ     

            zipped = " : 非圧縮送信";

            Writer out = new BufferedWriter(osw);

            writeHtml(out, zipped);

            out.flush();

        }

           

        response.setContentLength(baos.size());                     //HTTPボディのバイト長を通知

        baos.writeTo(os);                                           //ボディを出力ストリームに移す

        baos.close();                                               //明示的にバイトバッファを閉じる

        os.close();                                                 //明示的にこれをフラッシュして閉じる

        response.flushBuffer();                                     //明示的コミット

    }

/**

 * HTMLドキュメントの生成

 */

private void writeHtml(Writer out, String zipped) throws IOException {

        out.write("<HTML><HEAD>");

        out.write("<TITLE>CompressedHelloWorld" + zipped + "</TITLE></HEAD>");

        out.write("<BODY><H1>Compressed Hello World from 潟Nレス" + zipped+ "</H1>");

        out.write("<HR>");

        String helloworld = new String("Compressed Hello World from 潟Nレス ");

        int line = 0;

        int space = 0;

        for (int i = 0; i < 1000; ++i) {

            if ((i % 34) == 0) {

                out.write("<BR>");

                line++;

                space = (int)(20. * (1. + Math.sin((double)line / 10. * Math.PI)));

                for (int j = 0; j < space; j++) out.write(" ");        //全角のスペースでないといけない

            }

            out.write(helloworld.charAt(i % helloworld.length()));

        }

        out.write("<BR>*** Done! ***</BODY></HTML>");

}

}

 

下図はこのサーブレットをTelnetで呼び出した例である。gzip指定をして呼び出すと応答にはContent-Encoding: gzipというヘッダ行つきで311バイトの情報が返されてくる。gzip指定なしで呼び出した場合は2836バイトとなり、9倍以上の圧縮が達成されている。

 

圧縮指定つき


 


圧縮指定せず


 


もうひとつ圧縮転送の事例を示そう。この事例では、MicrosoftInternet ExplorerWordドキュメントをそのまま受信できることを使って、Wordファイルを圧縮してHTTPでダウンロードするものである。ユーザはファイル名(拡張子の.docは入れない)をクエリとしてこのサーブレットをIEから呼び出す。比較的大きなファイルの転送には圧縮の効果は大きい。特にダイヤルアップのクライアントを対象とするアプリケーションでは、これを活用すべきである。ファイルの拡張子を変えればPowerPointExelなどのファイルの転送もFTPを使わないで送れるので、試みることをお勧めする。

 

なおこのサーブレットはWebspere Test Environmentでしか動作しない。なぜならここで使われているgetRealPathメソッドは20016月現在Tomcatのどちらの版も正しい動作をしないためである。Tomcatのユーザは、目的のファイルの絶対ディレクトリをサーブレットの初期化パラメタから取得するなどの変更が必要である。

 

この事例では、IEhttp://localhost:8080/CompressedFileTransfer?Helloなるクエリ文字つきURLでこのサーブレットを呼び出すと、Hello.docというファイルが圧縮転送されてきてブラウザに表示される。システム・コンソールには以下のような3行が出力される。

File: E:\Program_Files\IBM\VisualAge_for_Java\ide\project_resources\IBM_WebSphere_Test_Environment\hosts\default_host\default_app\web\CompressedFileTransfer\Hello.doc(スペースはアンダースコアで示してある)

Original byte size: 19456

Compressed byte size: 2134

 

つまり目的のファイルは\default_app\web\CompressedFileTransferというディレクトリ下におかれている。圧縮率は1/9.1である。

 

ポイントとなるHTTPヘッダ行の設定は:

-      response.setHeader("Content-Type","application/msword");

-      response.setHeader("Accept-Ranges","bytes");

-      response.setHeader("Content-Encoding", "gzip");

-      response.setContentLength(baos.size());

である。

 

package basic_servlets;

 

import java.io.*;

import java.util.zip.*;

import javax.servlet.*;

import javax.servlet.http.*;

/**

 * CompressedFileTransferサーブレットは、クライアントから以下のようなURLを受け付ける

 * http://localhost:8080/CompressedFileTransfer?Hello

 * サーブレットはCompressedFileTransfer\にあるHello.docファイルを探し

 * これを圧縮してクライアントに送る。

 * 注: 現在対応するブラウザはIEのみである。

 *    また、getRealPathを正しく処理するのはWebsphereサーバのみであるので、

 *    それ以外のエンジンの場合はファイルの指定法に手を加える必要がある。

 *    このような使い方の場合はセキュリティに注意すること。

 * 作成日 : (01/06/12 15:43:52)

 * @author: Terry

 */

public class CompressedFileTransfer extends HttpServlet {

/**

 * Process incoming HTTP GET requests

 */

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    performTask(request, response);

}

/**

 * Process incoming HTTP POST requests

 */

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    performTask(request, response);

}

/**

 * 本サーブレット情報の文字列を返す。

 */

public String getServletInfo() {

    return "CompressedFileTransfer for zipped Word file transfer, Version 1.0 by Terry";

}

/**

 * このサーブレットはクエリで指定されたファイル名(.docは付けないこと)のWordファイルを探し

 * これを圧縮してHTTPパケットとしてユーザに返す

 */

public void performTask(HttpServletRequest request, HttpServletResponse response) {

 

    try

 

    {

        FileInputStream fis;

        OutputStream os;

        ByteArrayOutputStream baos;

        GZIPOutputStream gzipos;

 

        String accepttypes = request.getHeader("Accept");   //AcceptヘッダでブラウザがMSword対応か調べる

        if ((accepttypes != null)

            && (accepttypes.indexOf("application/msword") != -1)) {

        response.setHeader("Content-Type","application/msword");

            //Content-Typeでword文書であることをブラウザに通知

        response.setHeader("Accept-Ranges","bytes");            //なぜだかこのヘッダが必要である

        }

        else {

            throw new Exception("あなたのブラウザはMS-Wordをサポートしていません");

        }

               

        String fileName = request.getQueryString() + ".doc";   

            //クエリ文字列からのファイル名とファイルタイプを作る

        ServletContext context = getServletContext();           //コンテキストを取得

        String fileDirectory = context.getRealPath(request.getRequestURI());  

            //このメソッドはWTEでは正しく機能する

        String file = fileDirectory + "\\" + fileName;      //ディレクトリもつけた実際のファイルアドレス

 

        try {

            fis = new FileInputStream(file);                    //ファイルを開く

        }catch(IOException noSuchFile) {

            throw new FileNotFoundException("ファイル名が違います。クエリ文字を入れなおしてください(例:?HelloWorld)");

        }

 

        String acceptedEncodings = request.getHeader("Accept-Encoding");

        if ((acceptedEncodings != null)                         //ブラウザがGZIP対応か調べる

            && (acceptedEncodings.indexOf("gzip") != -1)) {

            os = response.getOutputStream();                    //エンジンへの出力ストリーム

            baos = new ByteArrayOutputStream();                 //バイト数を知るためのバイトバッファ

            gzipos = new GZIPOutputStream(baos);                //GZIP圧縮のエンコーダ

            response.setHeader("Content-Encoding", "gzip");     //ブラウザにgzipであることの通知

        }

        else {

            throw new Exception("あなたのブラウザはgzip圧縮をサポートしていません");

        }

 

        int totalBytesRead = 0;

        byte[] buffer = new byte[4096];                         //4096バイトのバイトバッファを用意

        while (true) {

            int bytesRead = fis.read(buffer);                   //バッファの最大容量まで読み込み

            if (bytesRead == -1) break;                         //EOFに達したかのチェック

            gzipos.write(buffer, 0, bytesRead);                 //バッファからGZIP出力ストリームへ書き出し

            totalBytesRead = totalBytesRead + bytesRead;

        }       

        fis.close();                                            //ファイルの内容を全て読み出した

        gzipos.finish();                                        //GZIPエンコードを終了させ

        gzipos.close();                                         //CRCを含めバイトバッファに流しだす

 

           

        response.setContentLength(baos.size());                 //HTTPボディのバイト長を通知

        System.out.println("File: " + file);

        System.out.println("Original byte size: " + totalBytesRead);

        System.out.println("Compressed byte size: " + baos.size());

        baos.writeTo(os);                                       //ボディを出力ストリームに移す

        baos.close();                                           //明示的にバイトバッファを閉じる

        os.close();                                             //明示的にこれをフラッシュして閉じる

        response.flushBuffer();                                 //明示的コミット

    }

    catch(FileNotFoundException e)

    {

        e.printStackTrace();

        try{

        response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());

        }

        catch (IOException anotherException){anotherException.printStackTrace();}

    }

    catch(Exception e)

    {

        e.printStackTrace();

        try{

        response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, e.getMessage());

        }

        catch (IOException anotherException){anotherException.printStackTrace();}

    }

    }

}

 

 

前節     目次     次節