圧縮転送(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倍以上の圧縮が達成されている。
 
圧縮指定つき

 
圧縮指定せず

 
もうひとつ圧縮転送の事例を示そう。この事例では、MicrosoftのInternet ExplorerがWordドキュメントをそのまま受信できることを使って、Wordファイルを圧縮してHTTPでダウンロードするものである。ユーザはファイル名(拡張子の.docは入れない)をクエリとしてこのサーブレットをIEから呼び出す。比較的大きなファイルの転送には圧縮の効果は大きい。特にダイヤルアップのクライアントを対象とするアプリケーションでは、これを活用すべきである。ファイルの拡張子を変えればPowerPointやExelなどのファイルの転送もFTPを使わないで送れるので、試みることをお勧めする。
 
なおこのサーブレットはWebspere Test Environmentでしか動作しない。なぜならここで使われているgetRealPathメソッドは2001年6月現在Tomcatのどちらの版も正しい動作をしないためである。Tomcatのユーザは、目的のファイルの絶対ディレクトリをサーブレットの初期化パラメタから取得するなどの変更が必要である。
 
この事例では、IEでhttp://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();}     }     } } |