Apache Jakartaプロジェクトで作られた、ブラウザからサーバーへファイルをアップロードする際の、サーバー側の受信ライブラリ。
|
|
アーカイブには、「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を指定する)
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は、「ある程度まではデータをメモリに置くけれども、上限を超えたらディスク上に一時的に保存する」もの。
| setSizeThreshold() | メモリ上で管理するデータサイズ(上限)を設定する。 | この値を超えると、ディスク上に一時的に保存される。 ここで保存されたファイルは適当なタイミングで勝手に消される。 |
| setRepository() | ディスク上に一時的に保存する際のディレクトリを指定する。 | このディレクトリが存在しないと、実際に一時ファイルを作成するとき(parseRequest())にIOFileUploadExceptionが発生する。 |
次に、FileUploadインスタンスを用意し、リクエストを解析する。
| 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);
}
}
| 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);
}
}
| getFieldName() | HTML上のフィールド名(inputタグのname属性) | 例:text_field_name |
| getString("キャラセット") | フィールドの値(inputタグのvalue属性) キャラセット("MS932"とか)は、クライアント側で入力した文字コード体系と同じものを指定しないといけない。 |
実際のところ、ストリームデータに対してnew String()しているだけ。 |
“マルチパート/フォームデータ”ではフォワードはしない方がいい。[2007-08-27]
サーブレットにはフォワード(やインクルード)という仕組みがあり、サーブレットから別のサーブレットへ遷移することが出来る。
その場合、遷移先のサーブレットでもリクエストの処理を行うことになり、コンテンツタイプは変わっていないのでisMultipartContent()はtrueになる。(遷移元のサーブレットでも当然trueになっている)
しかし、最初のサーブレットでリクエストのストリームを全部読み込んでしまう為か、次のサーブレットではリクエストからファイルやフィールドの値を取得することが出来ない。
つまり、parseRequest()で戻ってくるアイテムが無くなってしまう(個数が0になってしまう)。
フォワードさせるなら、値を受け渡す方法を別途使う必要があるだろう。