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になってしまう)。
フォワードさせるなら、値を受け渡す方法を別途使う必要があるだろう。