圧縮転送(GZIPによるHTMLテキストやファイルの転送)
|
Content-Lengthヘッダ行は、テキスト以外のコンテント、例えば圧縮ファイルなどにも有用であろう。以下に圧縮したデータをContent-Lengthヘッダ行つきで応答を返すサーブレットをお示しする。このサーブレットはクライアントがgzipで圧縮されたコンテントを受理できるかを調べ、もしgzip対応であればボディ部を圧縮してクライアントにわたす。その場合、HTTP/1.0クライアントも受理可能なようにContent-Lengthヘッダ行をつけ、また圧縮されていることをContent-Encodingヘッダ行でクライアントに通知する。圧縮の部分以外はAnotherHelloWorldの応用なので、詳細な説明は不要であろう。
読者はTelnetでどの程度の圧縮効果があるか調べると面白い。
/** * このサーブレットは圧縮したコンテントの応答の見本である。 * 作成日
: (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); } /** *
Returns the servlet info string. */ 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(); //明示的にこれをフラッシュして閉じる } /** *
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>"); } } |
もうひとつ圧縮転送の事例を示そう。この事例では、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 * * @param request Object that encapsulates the request to
the servlet * @param response Object that encapsulates the response
from the servlet */ public void doGet(HttpServletRequest
request, HttpServletResponse response) throws ServletException, IOException { performTask(request, response); } /** * Process incoming HTTP POST requests * * @param request Object that encapsulates the request to
the servlet * @param response Object that encapsulates the response
from the servlet */ public void doPost(HttpServletRequest
request, HttpServletResponse response) throws ServletException, IOException { performTask(request, response); } /** * Returns the servlet info string. */ 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();
//明示的にこれをフラッシュして閉じる } 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();} } } } |