タイトル
TOPJavaTIPS → This Page

multipart form 問題対応【Java の TIPS、小ネタ、注意点】

前提

Java開発におけるmultipart formを使う際の注意点を紹介します。
このページに記載している内容は2010/07/25に書かれたものです。
掲載している画面や方法が将来的に変更されている場合があります。

概要

<input type="file">タグを使ってファイルのアップロード機能を作る場合、
formタグに属性を追加して<form method="post" enctype="multipart/form-data">
のようにしてMIMEのマルチパート・データとしてリクエストを送信する必要があります。

しかし、こうするとサーバ側での処理がなかなか厄介なことになります。
具体的には以下のような問題が起こります。
・通常の Request.Parameter ではパラメータを読めない
・アップロードされたデータはストリーム(Stream)で読み込むことになるが、
 一度読み込むと中身が空っぽになる
 →これが原因で サーブレット・フィルタ(Servlet Filter)でパラメータを取得してしまうと
  アクション・クラス(Action)ではパラメータが空になってしまう
・アクション・クラスで別のアクションにフォワードしてしまうと
 パラメータが空になってしまう

対応策

独自のサーブレット・フィルタ(Servlet Filter)を作成し、
パラメータの中身を再配置させることで対応します。

以下のサーブレット・フィルタはmultipartのformのみパラメータを再配置し、
それ以外は何もせず処理を継続させます。

FileUploadFilter クラス
package jp.co.xxxxx.common.filter;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

/**
 * Multipartのformタグがある場合の対応フィルタ
 * 
 * @author mitchy
 */
public class FileUploadFilter implements Filter {

    /**
     * 初期化処理
     * 
     * @param obj
     *            FilterConfig
     * @throws ServletException
     */
    public void init(FilterConfig config) throws ServletException {
    }

    /**
     * 破棄処理
     */
    public void destroy() {
    }

    /**
     * Requestに対するフィルタ
     * 
     * @param request
     *            リクエスト
     * @param response
     *            レスポンス
     * @param chain
     *            FilterChain
     * @throws IOException
     * @throws ServletException
     */
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        if (!(request instanceof HttpServletRequest)) {
            chain.doFilter(request, response);
            return;
        }

        HttpServletRequest httpRequest = (HttpServletRequest) request;

        // Multipartではない場合は通常処理
        if (!ServletFileUpload.isMultipartContent(httpRequest)) {
            chain.doFilter(request, response);
            return;
        }

        // プロパティファイルから設定値取得
        // この処理は各自実装して下さい
        int uploadMaxSize = XXXXX.getNumber(XXXXX.KEY_UPLOAD_MAX_SIZE, 1);
        String encodingHeader = XXXXX.getValue(XXXXX.KEY_UPLOAD_ENCORDING_HEADER, "utf-8");
        String encodingForm = XXXXX.getValue(XXXXX.KEY_UPLOAD_ENCORDING_FORM, "utf-8");
        String tempDir = XXXXX.getValue(XXXXX.KEY_UPLOAD_TMP_PATH, "");

        // Create a factory for disk-based file items
        DiskFileItemFactory factory = new DiskFileItemFactory();
        factory.setSizeThreshold(50 * 1024);
        factory.setRepository(new File(tempDir));

        // Create a new file upload handler
        ServletFileUpload upload = new ServletFileUpload(factory);
        upload.setSizeMax(uploadMaxSize * 1024 * 1024);
        upload.setHeaderEncoding(encodingHeader);

        try {
            List list = upload.parseRequest(httpRequest);
            final Map map = new HashMap();
            for (int i = 0; i < list.size(); i++) {
                FileItem item = (FileItem) list.get(i);
                String str = item.getString(encodingForm);
                if (item.isFormField()) {
                    // 通常のパラメータ
                    Object oldValue = map.get(item.getFieldName());
                    if (oldValue != null
                            && Object[].class.isAssignableFrom(oldValue.getClass())
                            && (((Object[]) oldValue).length > 0)) {
                        String[] oldItemValues = (String[]) map.get(item.getFieldName());
                        int length = oldItemValues.length;
                        String[] itemValues = new String[length + 1];
                        for (int j = 0; j < length; j++) {
                            itemValues[j] = oldItemValues[j];
                        }
                        itemValues[length] = str;
                        map.put(item.getFieldName(), itemValues);
                    } else {
                        map.put(item.getFieldName(), new String[] {str});
                    }
                } else {
                    // アップロードされたファイル
                    httpRequest.setAttribute(item.getFieldName(), item);
                }
            }

            chain.doFilter(new HttpServletRequestWrapper(httpRequest) {
                public Map getParameterMap() {
                    return map;
                }

                // busywork follows ... should have been part of the wrapper
                public String[] getParameterValues(String name) {
                    Map map = getParameterMap();
                    return (String[]) map.get(name);
                }

                public String getParameter(String name) {
                    String[] params = getParameterValues(name);
                    if (params == null)
                        return null;
                    return params[0];
                }

                public Enumeration getParameterNames() {
                    Map map = getParameterMap();
                    return Collections.enumeration(map.keySet());
                }
            }, response);
        } catch (FileUploadException ex) {
            ServletException servletEx = new ServletException();
            servletEx.initCause(ex);
            throw servletEx;
        }
    }
}

次に作成したサーブレット・フィルタ(Servlet Filter)を使えるように設定します。
web.xml に以下の設定を追加しましょう。
・・・略・・・
<!-- ファイルアップロードフィルタ -->
<filter>
	<filter-name>fileUploadFilter</filter-name>
	<filter-class>jp.co.xxxxx.common.filter.FileUploadFilter</filter-class>
</filter>
・・・略・・・
<!-- ファイルアップロードフィルタ -->
<filter-mapping>
	<filter-name>fileUploadFilter</filter-name>
	<servlet-name>action</servlet-name>
</filter-mapping>
・・・略・・・

対応後のパラメータ

アップロードされたファイルはアクション・クラス(Action)で以下のように利用可能。
(例外処理などは省略)

アクション・クラス(Action)サンプル
・・・略・・・
    try {
        FileItem item = (FileItem) request.getAttribute("file");
        File file = new File(item.getName());

        // Cドライブ直下に保存
        FileOutputStream fileOutputStream = new FileOutputStream("C:\\" + file.getName());
        fileOutputStream.write(item.get());
        fileOutputStream.close();

    } catch (Exception e) {
        ・・・略・・・
    }
・・・略・・・

ファイル以外のパラメータはFormクラスに自動でセットされています。
request.getAttribute でも取得可能。

更新履歴

2010/07/25 新規作成

TOPJavaTIPS → This Page