タイトル
 メニューにないコーナーはTopからいけます
TOPJavaWeb App Tips → This Page

multipart form 問題対応

概要

<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 新規作成

TOPJavaWeb App Tips → This Page
Valid CSS!