【コラム】

Java API、使ってますか?

44 JARファイルを効率的にネットワーク転送するためのPack200形式

44/60

JSR 200: Network Transfer Format for Java Archives

Javaプログラムを公開する場合には、通常JAR(Java ARchive)を形式を利用するが、このJAR形式のファイルをネットワーク上で効率良く転送するための圧縮形式がある。それがJSR 200で定められている「Pack200」だ。Pack200はJARファイルのダウンロードサイズを減少させる目的で作られたファイル形式で、JAR形式の性質に特化して高い圧縮率を実現する。

Pack200形式はHTTP 1.1(RFC 2616)プロトコルによるHTTP圧縮でもサポートされており、クライアント側でJARファイルを要求するようなアプリケーションにおいて、Pack200を利用することによって転送量を最小限に抑えることができる。実際、J2SE 5.0以降のJava Web StartやJava Plug-inにおいてはRFC 2616準拠のHTTP圧縮によってPack200がサポートされている。

Pack200形式による圧縮と解凍

JSR 200の参照実装はJ2SE 5.0よりJava SEプラットフォームに統合されている。対象のクラス/インタフェースはjava.util.jar.Pack200およびその内部インタフェースであるjava.util.jar.Pack200.Packer、java.util.jar.Pack200.Unpackerの3つ。

Pack200.Packerはその名の通りPack200形式でファイルを圧縮するためのエンジンで、リスト1のように使用する。まずPack200クラスのnewPacker()メソッドでPack200.Packerインスタンスを取得し、pack()メソッドを用いて圧縮を実行する。pack()の第1引数には圧縮対象となるJarFileまたはJarInputStreamのインスタンスを、第2引数には圧縮結果を出力するOutputStreamのインスタンスを指定する。Pack200.Packerインタフェースに用意された各種定数を用いてプロパティを指定することにより、JSR 200仕様で定められているより高度な圧縮を行うこともできる。

リスト1 PackSample.java - Pack200形式でJARファイルを圧縮

package apisample;

import java.io.*;
import java.util.jar.Pack200;
import java.util.jar.Pack200.Packer;
import java.util.jar.JarFile;

public class PackSample {
    public static void main(String[] args) {
        // Packerを生成
        Packer packer = Pack200.newPacker();

        try {
            // ストリームを用意
            JarFile jarFile = new JarFile("sample.jar");
            FileOutputStream output = new FileOutputStream("sample.jar.pack");

            // 圧縮
            packer.pack(jarFile, output);
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Pack200.UnpackerはPack200形式で圧縮されたファイルをJARファイルに変換するエンジンで、リスト2のように使用する。Packerの場合と同様に、まずPack200クラスのnewUnacker()メソッドでPack200.Unpackerインスタンスを取得し、unpack()メソッドを用いて変換する。第1引数にはPack200ファイルのFileインスタンスかまたはInputStreamインスタンスを、第2引数にはJARファイルを出力するJarOutputStreamのインスタンスを指定する。

リスト2 UnpackSample.java - Pack200ファイルからJARファイルへの展開

package apisample;

import java.io.*;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.jar.Pack200.Unpacker;

public class UnpackSample {
    public static void main(String[] args) {
        // Unpackerを生成
        Unpacker unpacker = Pack200.newUnpacker();

        try {
            // ストリームを用意
            File packedFile = new File("sample.jar.pack");
            JarOutputStream output = new JarOutputStream(
                    new FileOutputStream("sample.jar"));

            // 展開
            unpacker.unpack(packedFile, output);
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Pack200を使ってJARファイルを転送する

Pack200はもともとJARファイルをネットワークで効率良く転送するために作られた圧縮形式である。pack()メソッドやunpack()メソッドは入力元および出力先としてストリームを指定できるようになっており、ネットワーク通信との相性がいいように設計されている。リスト1およびリスト3.2は、Pack200形式で圧縮したファイルをソケットを用いて送受信するプログラムの例である。pack()の出力側、unpack()の入力側に、それぞれソケットから取得したストリームを渡してあげるだけでよい (ただし、この例のようにリクエストを受けてから圧縮するやり方は実用的ではない)。

リスト3 PackSendSample.java - Pack200ファイルの送信

package apisample;

import java.net.*;
import java.io.*;
import java.util.jar.JarFile;
import java.util.jar.Pack200;
import java.util.jar.Pack200.Packer;

public class PackSendSample {
    public PackSendSample() {
        try {
            // 接続待ち
            ServerSocket serverSocket = new ServerSocket(8080);
            Socket socket = serverSocket.accept();

            // ファイルを圧縮して転送
            JarFile jarFile = new JarFile("sample.jar");
            OutputStream output = socket.getOutputStream();

            Packer packer = Pack200.newPacker();
            packer.pack(jarFile, output);

            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        PackSendSample sample = new PackSendSample();
    }
}

リスト4 PackReceiveSample.java - Pack200ファイルの受信

package apisample;

import java.net.Socket;
import java.io.*;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.jar.Pack200.Unpacker;

public class PackReceiveSample {
    public PackReceiveSample() {
        try {
            // 接続
            Socket socket = new Socket("localhost", 8080);

            // ファイルの受信と展開
            InputStream receiveStream = socket.getInputStream();
            JarOutputStream jarOut = new JarOutputStream(new FileOutputStream("sample.jar"));

            Unpacker unpacker = Pack200.newUnpacker();
            unpacker.unpack(receiveStream, jarOut);

            jarOut.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        PackReceiveSample sample = new PackReceiveSample();
    }
}

Pack200ファイルをWebサーバに配備する

続いて、WebアプリケーションにおいてPack200形式の圧縮ファイルを利用する例を紹介する。HTTP圧縮ではgzipおよびpack200-gzipがサポートされており、クライアントアプリケーションはHTTPリクエストのaccept-encodingフィールドにこれらの形式を指定することができる。サーバ・アプリケーション側では、リクエスト・ヘッダにこれらの値が指定されている場合にはクライアントがPack200やGZIP形式を処理できると判断し、content-encordingフィールドを設定して対応するファイルを送信すればよい。

この仕組みをサーブレットで実現する場合、手順は次のようになる。ただし、今回は簡略化のため (Pack200を使用しない) GZIP形式は省略している。

  1. クライアントがaccept-encodingにpack200-gzipを指定してsample.jarを要求する
  2. サーブレットはsample.jar.pack.gzを検索する
  3. sample.jar.pack.gzが存在する場合にはレスポンスヘッダのcontent-encordingフィールドにpack200-gzip、content-typeフィールドにapplication/java-archiveを指定して返す
  4. 存在しない場合にはcontent-encordingフィールドにnull、content-typeフィールドにapplication/java-archiveを指定して、sample.jarをそのまま返す

実際のサーブレットをリスト5に示す。

リスト5 Pack200Servlet.java - Pack200形式をサポートするサーブレットの例

package apisample;

import java.io.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class Pack200Servlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        // リクエストヘッダの取得
        String encoding = request.getHeader("accept-encoding");
        String contentType = request.getContentType();
        String pathInfo = request.getPathInfo();
        String pathTranslated = request.getPathTranslated();

        String outFile = pathInfo;
        String contentEncoding = null;

        // 圧縮ファイルの検索
        if (encoding != null && contentType != null &&
                contentType.compareTo("application/x-java-archive") == 0 &&
                encoding.toLowerCase().indexOf("pack200-gzip") > -1) {
            contentEncoding = "pack200-gzip";
            File file = new File(pathTranslated.concat(".pack.gz"));
            if (file.exists()) {
                outFile = pathInfo.concat(".pack.gz") ;
            }
        } 

        // レスポンスヘッダの設定
        response.setHeader("content-encoding", contentEncoding);
        response.setContentType(getServletContext().getMimeType(pathInfo));

        // ファイルの転送
        InputStream input = getServletContext().getResourceAsStream(outFile);
        OutputStream output = response.getOutputStream();
        if (input != null) {
            byte buf[] = new byte[1024];
            int readsize;
            while ((readsize = input.read(buf)) > 0) {
                output.write(buf,0,readsize);
            }
            output.flush();
            output.close();
            input.close();
        }
    } 
}

web.xmlには、JARファイルに対する要求をこのサーブレットで処理するように記述すればよい。クライアントプログラムではリスト6のように、受信したファイルをJAR形式に戻して利用することになる (content-encordingがnullだった場合の処理は省略)。

リスト6 ConnectionSample.java - Pack200形式をサポートするクライアントプログラムの例

package apisample;

import java.io.*;
import java.net.*;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.jar.Pack200.Unpacker;
import java.util.zip.GZIPInputStream;

public class ConnectionSample {
    public static void main(String[] args) {
        try {
            // HTTPによる接続
            URL url = new URL("http://localhost:8080/Pack200Servlet/sample.jar");
            HttpURLConnection conn = (HttpURLConnection)url.openConnection();
            conn.setRequestMethod("GET");
            conn.setRequestProperty("accept-encoding", "pack200-gzip");
            conn.setRequestProperty("content-type", "application/x-java-archive");
            conn.connect();

            // ファイルの取得と展開
            GZIPInputStream input = new GZIPInputStream(conn.getInputStream());
            JarOutputStream jarOut = new JarOutputStream(new FileOutputStream("sample.jar"));

            Unpacker unpacker = Pack200.newUnpacker();
            unpacker.unpack(input, jarOut);

            jarOut.close(); 
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

今回紹介したのは非常に単純な利用方法でしかないが、うまく活用すればネットワークの効率的な利用を実現することができるはず。すでにコアプラットフォームに統合されているので、必要に応じて利用を検討してみるといいだろう。

44/60

インデックス

連載目次
第60回 どうなる? 今後のJavaプラットフォーム(Java SE編)
第59回 どうなる? 今後のJavaプラットフォーム(Java EE編)
第58回 Java SE 7の要注目機能"クロージャ"はどうなるのか その6
第57回 Java SE 7の要注目機能"クロージャ"はどうなるのか その5
第56回 Java SE 7の要注目機能"クロージャ"はどうなるのか その4
第55回 Java SE 7の要注目機能"クロージャ"はどうなるのか その3
第54回 Java SE 7の要注目機能"クロージャ"はどうなるのか その2
第53回 Java SE 7の要注目機能"クロージャ"はどうなるのか
第52回 Early Draftが公開されたJSF 2.0
第51回 EJBから独立したJava Persistence 2.0
第50回 モバイルJavaの新しい潮流となるか - MSA 2.0のドラフト公開
第49回 やっぱり基本はServlet - Servlet 3.0のEarly Draftを読む
第48回 JOGLで3Dプログラミング その4
第47回 JOGLで3Dプログラミング その3
第46回 JOGLで3Dプログラミング その2
第45回 JOGLで3Dプログラミング
第44回 JARファイルを効率的にネットワーク転送するためのPack200形式
第43回 Early Draftで把握するEJB 3.1の新機能
第42回 次世代の携帯端末向けJava仕様"MIDP 3.0"はどうなるか その2
第41回 次世代の携帯端末向けJava仕様"MIDP 3.0"はどうなるか その1
第40回 リソースアダプタによる接続の仕組み
第39回 JCAを利用したシステム間接続
第38回 Java EEと外部システムの接続性を支えるJCAがバージョンアップ
第37回 Javaのモジュラリティ強化を担う"スーパーパッケージ"とは
第36回 JSR 308対応のコンパイラを試す
第35回 公開されたJSR 308のEarly Draftを検証する
第34回 スクリプト言語とJavaを結びつけるJSR 223
第33回 Java EE環境に統一されたコンポーネントモデルを提供するJSR 299 その2
第32回 Java EE環境に統一されたコンポーネントモデルを提供するJSR 299 その1
第31回 Javaの文法がそのまま使えるスクリプト言語"BeanShell"
第30回 Javaアプリケーションにオブジェクトのキャッシュ機構を提供するJCache API
第29回 Javaアプリケーションからのリソース管理を可能にするJSR 284
第28回 XMLデータソースへの問い合わせはJSR 225で
第27回 Portlet Specification 2.0をもっと手軽に利用する
第26回 次期Javaポートレット仕様となるJSR 286
第25回 JSFとポートレットをつなげるJSR 301
第24回 Webサービス向けのポートレット仕様「WSRP」
第23回 高い相互運用性を実現するポートレットAPI - JSR 168
第22回 Java EE環境でタスクのスケジューリングを可能にするJSR 236
第21回 Java EE環境でのスレッドプログラミングを可能にするJSR 237
第20回 音声認識/合成のためのAPI - Java Speech APIとJSR 113
第19回 JSR 291でJavaプラットフォームにダイナミックコンポーネントモデルを導入
第18回 JAX-RSで簡単RESTful - JSR 311
第17回 待望のServlet 3.0がJSRに登場 - JSR 315
第16回 アノテーションを使ってバグ退治 - JSR 305
第15回 アノテーションをさらに広い範囲で利用可能にするJSR 308
第14回 Webアプリケーション開発の要となるか - JSF 2.0がJSRに登場
第13回 Webサービス経由でのJMX Agentへの接続を可能にするJSR 262
第12回 Javaアプリケーションのモジュール化をサポートするJava Module System
第11回 "NIO.2"がやってきた - JSR 203: More New I/O APIs for the Java Platform
第10回 JSR 295: Beans Bindingの参照実装を試す
第9回 けっこう便利! 単位を扱うAPI -- JSR 275: Units Specification
第8回 アノテーションでバリデーション - JSR 303: Bean Validator
第7回 Swing開発の救世主となるか - Swing Application Framework
第6回 JavaBeansのプロパティを同期させるバインディングAPI
第5回 誰よりも早く"Java SE 7"を睨む
第4回 日時情報の取り扱いを改善する JSR 310: Date and Time API
第3回 古いAPIも進化している!? - JSR 919: JavaMail 1.4
第2回 JSR 1 リアルタイムJava仕様
第1回 JCPによって進められるJava関連技術の標準化

もっと見る

提供:マイナビ

会員登録はこちら

大学・大学院・短大・専門学生向けの就職情報サイト「マイナビ2010」「マイナビ2009」に今すぐ登録しよう!  大手企業からベンチャー企業までの約13,000社の企業情報を公開、エントリーが可能です。2010年卒予定の方は「マイナビ2010」に、2009年卒予定の方は「マイナビ2009」に登録してください。

毎日コミュニケーションズはプライバシーマークを取得しています。

関連キーワード


人気記事

一覧

イチオシ記事

新着記事