S-JIS[2007-07-08/2007-08-27] 変更履歴

Jakarta commons fileUpload

Apache Jakartaプロジェクトで作られた、ブラウザからサーバーへファイルをアップロードする際の、サーバー側の受信ライブラリ。


インストール

  1. commons fileUploadダウンロードページからアーカイブをダウンロードする。(commons-fileupload-1.2-bin.zip)
  2. 適当な場所にアーカイブを展開する。
  3. 「展開したディレクトリ/lib」のjarファイルを、所定の場所に置く。
  4. Commons IOダウンロードページからアーカイブをダウンロードする。(commons-io-1.3.2-bin.zip)
    IOに入っているいくつかのクラスがfileUploadで使われる。fileUpload1.0まではfileUploadのjarに含まれていたようだが、1.1以降はIOに分離したっぽい。
  5. 適当な場所にアーカイブを展開する。
  6. 「展開したディレクトリ」直下のjarファイルを、所定の場所に置く。

アーカイブには、「classが入っているjarファイル」と「Javadocのjarファイル」「ソースが入っているjarファイル」が含まれている。
コンパイル時と実行時に必要なのは「classが入っているjarファイル」のみだが、開発にはJavadocとソースもあった方が便利。Javadocは当然英語だけど…^^;

コンパイル用には、クラスパスが通っている場所にjarファイルを置くか、クラスパス(ビルドパス)にjarファイルの指定を追加する。
実行時には、J2EEサーバーが認識できる場所(各アプリのWEB-INF/libの下や、Tomcatの場合だとshared/libの下)に「classが入っているjarファイル」を置く。

Eclipse3を使っていれば、「ソースを添付」することが出来る。
(commons-fileupload-1.2.jarのプロパティーでcommons-fileupload-1.2-sources.jarを指定する)


使用方法(HTML側)

fileUploadを使うには、html(jsp)ファイル側のformタグのエンコードタイプを“マルチパート/フォームデータ”に指定する必要がある。
その上で、そのformの中にtype="file"のinputタグを入れてやる。 (このファイルタイプには色々と癖があるので要注意)

sample.html:

〜
<form action="URL" method="POST" enctype="multipart/form-data">

	ファイル:<input type="file" name="file_field_name">
<br>
	普通の値:<input type="text" name="text_field_name">

</form>
〜

使用方法(サーブレット側)

ネットでサンプルを探してみるとDiskFileUploadクラスを使っているものがよく見つかるが、fileUpload1.1.1以降ではServletFileUploadが推奨されているようだ。(DiskFileUploadは非推奨クラスになっている)

public class SampleServlet extends HttpServlet {

	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		if (ServletFileUpload.isMultipartContent(request)) {

			// ファクトリー生成
			DiskFileItemFactory factory = new DiskFileItemFactory();
			factory.setSizeThreshold(1426);
			factory.setRepository(new File("C:\\temp\\file_up")); //一時的に保存する際のディレクトリ

			ServletFileUpload upload = new ServletFileUpload(factory);
			upload.setSizeMax(20 * 1024);
			upload.setFileSizeMax(10 * 1024);

			List items;
			try {
				items = upload.parseRequest(request);
			} catch (FileUploadException e) {
				// エラー処理
				throw new ServletException(e);
			}

			// 全フィールドに対するループ
			for (Object val : items) {
				FileItem item = (FileItem) val;
				if (item.isFormField()) {
					// type="file"以外のフィールド
					processFormField(item);
				} else {
					// type="file"のフィールド
					processUploadedFile(item);
				}
			}
		}
	}
〜
}

一番最初に、リクエストが“マルチパート/フォームデータ”であるかどうかをチェックする。
マルチフォームデータでない場合は、通常のデータ処理を行うようにする(fileUploadを使わない)。

マルチフォームデータであれば、fileUploadを使用してデータを取得する。
まず、ファクトリーインスタンスを用意する。「アップロードしたデータをどう扱うか」を決めるものらしい。
DiskFileItemFactoryは、「ある程度まではデータをメモリに置くけれども、上限を超えたらディスク上に一時的に保存する」もの。

DiskFileItemFactory
setSizeThreshold() メモリ上で管理するデータサイズ(上限)を設定する。 この値を超えると、ディスク上に一時的に保存される。
ここで保存されたファイルは適当なタイミングで勝手に消される。
setRepository() ディスク上に一時的に保存する際のディレクトリを指定する。 このディレクトリが存在しないと、実際に一時ファイルを作成するとき(parseRequest())にIOFileUploadExceptionが発生する。

次に、FileUploadインスタンスを用意し、リクエストを解析する。

ServletFileUpload
setSizeMax() 処理するリクエスト全体(?)(アップするファイルサイズの合計ではない)の上限を設定する。 全体サイズがこの値を超えている場合、parseRequest()でFileSizeLimitExceededExceptionが発生する。
setFileSizeMax() 処理するファイルサイズの上限を設定する。 いずれかのファイルのサイズがこの値を超えている場合、parseRequest()でFileSizeLimitExceededExceptionが発生する。
parseRequest() リクエストを解析する。 クライアントからの実際のアップロードもここで行われる。
クライアントから切断された(ブラウザの中止ボタンを押してキャンセルしたとかの)場合、「java.net.SocketException: Connection reset」を原因とするIOFileUploadExceptionが発生する。
getItemIterator()   parseRequest()の代わりに使うものっぽい。
メモリや一時ファイルに一旦全て取り込むのではなく、クライアントからの通信データを直接扱うのかな?

解析が成功したら、各フィールドに対する処理を行う。(Iteratorを使ってループ)


ファイルアップロード処理

ファイルのアップロードなので、中身はバイナリデータ(ストリーム)で取得する。
単純にファイルに出力するだけなら、専用のメソッドも用意されている。

ファイルへ出力する例:

	private void processUploadedFile(FileItem item) throws IOException, ServletException {
		File f = new File(item.getName());
		try {
			item.write(new File("C:\\temp", f.getName()));
		} catch (IOException e) {
			throw e;
		} catch (Exception e) {
			throw new ServletException(e);
		}
	}
FileItem(アップロード関連)
getFieldName() HTML上のフィールド名(inputタグのname属性) 例:file_field_name
getName() 指定されたパス(ファイル名)  
getSize() ファイルサイズ  
getInputStream() データのInputStream(これを使ってデータを読み込む)  
write(new File("ファイル名")) ファイルへデータを出力する。  

ブラウザ側で存在しないファイルを指定した場合、ファイルサイズが0となる。(エラーにはならない。)


通常フィールド処理

ファイルのアップロード以外の(テキストやコンボボックス・ボタン等)のフィールドは、“マルチパート/フォームデータ”のときには通常のサーブレットの方法では値を取得できない。
これらの値が必要であれば、ファイルアップロードと同じくFileItemを使う必要がある。

コンソールへ値を出力してみる例:

	private void processFormField(FileItem item) throws ServletException {

		System.out.println(item.getFieldName());
		try {
			System.out.println(item.getString("MS932"));
		} catch (UnsupportedEncodingException e) {
			throw new ServletException(e);
		}
	}
FileItem(通常フィールド関連)
getFieldName() HTML上のフィールド名(inputタグのname属性) 例:text_field_name
getString("キャラセット") フィールドの値(inputタグのvalue属性)
キャラセット("MS932"とか)は、クライアント側で入力した文字コード体系と同じものを指定しないといけない。
実際のところ、ストリームデータに対してnew String()しているだけ。

フォワードの注意

“マルチパート/フォームデータ”ではフォワードはしない方がいい。[2007-08-27]

サーブレットにはフォワード(やインクルード)という仕組みがあり、サーブレットから別のサーブレットへ遷移することが出来る。
その場合、遷移先のサーブレットでもリクエストの処理を行うことになり、コンテンツタイプは変わっていないのでisMultipartContent()はtrueになる。(遷移元のサーブレットでも当然trueになっている)

しかし、最初のサーブレットでリクエストのストリームを全部読み込んでしまう為か、次のサーブレットではリクエストからファイルやフィールドの値を取得することが出来ない。
つまり、parseRequest()で戻ってくるアイテムが無くなってしまう(個数が0になってしまう)。

フォワードさせるなら、値を受け渡す方法を別途使う必要があるだろう。


サーブレットへ戻る / Javaへ戻る / 技術メモへ戻る
メールの送信先:ひしだま