1. ホーム
  2. java

[解決済み] JSP/Servletを使用してサーバーにファイルをアップロードするにはどうすればよいですか?

2022-03-14 12:18:31

質問

JSP/Servletを使用してサーバーにファイルをアップロードするにはどうすればよいですか?

こんなことをやってみました。

<form action="upload" method="post">
    <input type="text" name="description" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

しかし、ファイル名だけが表示され、ファイルの内容は表示されません。私が enctype="multipart/form-data"<form> であれば request.getParameter() を返します。 null .

調査中に偶然見つけた Apache 共通 FileUpload . これを試してみました。

FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List items = upload.parseRequest(request); // This line is where it died.

残念なことに、サーブレットは明確なメッセージと原因なしに例外をスローしました。以下はそのスタックトレースです。

SEVERE: Servlet.service() for servlet UploadServlet threw exception
javax.servlet.ServletException: Servlet execution threw an exception
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:313)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
    at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:852)
    at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
    at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
    at java.lang.Thread.run(Thread.java:637)

解決方法は?

はじめに

アップロードするファイルを参照・選択するためには、HTMLの <input type="file"> フィールドをフォームに追加します。に記載されているように HTML仕様 を使用する必要があります。 POST メソッドと enctype 属性を設定する必要があります。 "multipart/form-data" .

<form action="upload" method="post" enctype="multipart/form-data">
    <input type="text" name="description" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

このようなフォームを送信した後、バイナリのマルチパートフォームデータはリクエストボディにある 別のフォーマット のときよりも enctype が設定されていない場合。

Servlet 3.0 より前のバージョンでは、Servlet API はネイティブに multipart/form-data . これは、デフォルトのフォームenctypeである application/x-www-form-urlencoded . その request.getParameter() とコンソートすると、すべて null マルチパートフォームデータを使用する場合 ここで、よく知られている Apache Commons FileUpload が登場した。

手動でパースしないでください

を元に自分でリクエストボディを解析することは理論的には可能です。 ServletRequest#getInputStream() . しかし、これは精密で面倒な作業で、以下のような正確な知識が必要です。 RFC2388 . これを自分でやろうとしたり、インターネット上のどこかにあるライブラリのない自作コードをコピーペーストするのはやめたほうがいいでしょう。多くのオンラインソースは、roseindia.netのように、これで大失敗しています。以下もご参照ください。 pdfファイルのアップロード . むしろ、何百万人ものユーザーによって何年も使われている(そして暗黙のうちにテストされている!)本物のライブラリを使うべきでしょう。そのようなライブラリは、その堅牢性を証明しています。

サーブレット3.0以降の場合は、ネイティブAPIを使用する。

少なくともServlet 3.0 (Tomcat 7, Jetty 9, JBoss AS 6, GlassFish 3, etc) を使用しているのであれば、以下の標準APIを使用することが可能です。 HttpServletRequest#getPart() を使用して、個々のマルチパートフォームデータアイテムを収集します (ほとんどの Servlet 3.0 実装では、このために Apache Commons FileUpload を実際に使用しています!)。また、通常のフォームフィールドは getParameter() は、通常の方法です。

まずサーブレットにアノテーションを付けます。 @MultipartConfig を認識しサポートするために multipart/form-data リクエストを取得し、その結果 getPart() を動作させることができます。

@WebServlet("/upload")
@MultipartConfig
public class UploadServlet extends HttpServlet {
    // ...
}

そして、その doPost() を次のようにする。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String description = request.getParameter("description"); // Retrieves <input type="text" name="description">
    Part filePart = request.getPart("file"); // Retrieves <input type="file" name="file">
    String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
    InputStream fileContent = filePart.getInputStream();
    // ... (do your job here)
}

注意 Path#getFileName() . これは、ファイル名の取得に関して、MSIEの修正です。このブラウザは、ファイル名だけでなく、ファイル名と一緒にフルパスを不正に送信します。

複数のファイルをアップロードしたい場合は multiple="true" ,

<input type="file" name="files" multiple="true" />

または、昔ながらの複数入力の方法です。

<input type="file" name="files" />
<input type="file" name="files" />
<input type="file" name="files" />
...

であれば、以下のように収集することができます。 request.getParts("files") ):

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // ...
    List<Part> fileParts = request.getParts().stream().filter(part -> "files".equals(part.getName()) && part.getSize() > 0).collect(Collectors.toList()); // Retrieves <input type="file" name="files" multiple="true">

    for (Part filePart : fileParts) {
        String fileName = Paths.get(filePart.getSubmittedFileName()).getFileName().toString(); // MSIE fix.
        InputStream fileContent = filePart.getInputStream();
        // ... (do your job here)
    }
}

Servlet 3.1でない場合は、手動でファイル名を取得します。

注意点 Part#getSubmittedFileName() は Servlet 3.1 (Tomcat 8, Jetty 9, WildFly 8, GlassFish 4, etc) で導入されました。もしまだ Servlet 3.1 でないなら、提出されたファイル名を取得するために追加のユーティリティメソッドが必要です。

private static String getSubmittedFileName(Part part) {
    for (String cd : part.getHeader("content-disposition").split(";")) {
        if (cd.trim().startsWith("filename")) {
            String fileName = cd.substring(cd.indexOf('=') + 1).trim().replace("\"", "");
            return fileName.substring(fileName.lastIndexOf('/') + 1).substring(fileName.lastIndexOf('\\') + 1); // MSIE fix.
        }
    }
    return null;
}

String fileName = getSubmittedFileName(filePart);

ファイル名の取得に関して、MSIEの修正に注意してください。このブラウザは、ファイル名だけでなく、ファイル名と一緒にフルパスを誤って送信します。

Servlet 3.0 になっていないときは、Apache Commons FileUpload を使ってください。

まだ Servlet 3.0 を使っていない場合 (そろそろアップグレードの時期ではないでしょうか?) は、一般的に アパッチコモンズファイルアップロード を使用して、マルチパートフォームデータリクエストを解析します。これには優れた ユーザーガイド よくある質問 (両方とも丁寧に見てください)。また、オライリー(" コス ") MultipartRequest しかし、それはいくつかの(マイナーな)バグを持っており、何年も積極的にメンテナンスされていません。私はそれを使用することをお勧めしません。Apache Commons FileUploadはまだ活発にメンテナンスされており、現在は非常に成熟しています。

Apache Commons FileUploadを使用するためには、少なくとも以下のファイルがWebアプリケーションに含まれている必要があります。 /WEB-INF/lib :

最初の試みは、コモンズIOを忘れたために失敗した可能性が高いです。

ここでは、キックオフの例として doPost()UploadServlet は、Apache Commons FileUploadを使用した場合、以下のようになります。

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try {
        List<FileItem> items = new ServletFileUpload(new DiskFileItemFactory()).parseRequest(request);
        for (FileItem item : items) {
            if (item.isFormField()) {
                // Process regular form field (input type="text|radio|checkbox|etc", select, etc).
                String fieldName = item.getFieldName();
                String fieldValue = item.getString();
                // ... (do your job here)
            } else {
                // Process form file field (input type="file").
                String fieldName = item.getFieldName();
                String fileName = FilenameUtils.getName(item.getName());
                InputStream fileContent = item.getInputStream();
                // ... (do your job here)
            }
        }
    } catch (FileUploadException e) {
        throw new ServletException("Cannot parse multipart request.", e);
    }

    // ...
}

を呼び出さないことが非常に重要です。 getParameter() , getParameterMap() , getParameterValues() , getInputStream() , getReader() などをあらかじめ同じリクエストに設定しておきます。そうしないと、サーブレットコンテナはリクエストボディを読んで解析してしまうので、Apache Commons FileUpload は空のリクエストボディを取得してしまうことになります。a.o も参照してください。 ServletFileUpload#parseRequest(request)は空のリストを返します。 .

なお FilenameUtils#getName() . これは、ファイル名の取得に関して、MSIEの修正です。このブラウザは、ファイル名だけでなく、ファイル名と一緒にフルパスを不正に送信します。

また、これをすべて Filter を使い続けられるように、リクエストのパラメターマップの中に戻してくれます。 request.getParameter() でアップロードされたファイルを取得することができます。 request.getAttribute() . このブログの記事で例が紹介されています .

のGlassFish3バグに対する回避策。 getParameter() を返したまま null

なお、Glassfishの3.1.2より前のバージョンでは バグ ここで getParameter() を返します。 null . このようなコンテナをターゲットにしていて、アップグレードできない場合は、値を getPart() このユーティリティ・メソッドの助けを借りてください。

private static String getValue(Part part) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(part.getInputStream(), "UTF-8"));
    StringBuilder value = new StringBuilder();
    char[] buffer = new char[1024];
    for (int length = 0; (length = reader.read(buffer)) > 0;) {
        value.append(buffer, 0, length);
    }
    return value.toString();
}

String description = getValue(request.getPart("description")); // Retrieves <input type="text" name="description">
    

アップロードされたファイルの保存( getRealPath() また part.write() !)

取得したデータを適切に保存するための詳細は、次の回答をご覧ください。 InputStream (その fileContent をディスクまたはデータベースに保存します。

アップロードされたファイルの処理

ディスクまたはデータベースからクライアントに保存されたファイルを適切に提供するための詳細については、次の回答を参照してください。

フォームのAjax化

Ajax(とjQuery)を使ってアップロードする方法については、次の回答をご覧ください。フォームデータを収集するサーブレットコードは、このために変更する必要がないことに注意してください! 応答する方法だけが変更されるかもしれませんが、これはむしろ些細なことです(すなわち、JSPに転送する代わりに、Ajax呼び出しの責任者が期待するものに応じて、JSONまたはXML、あるいはプレーンテキストを表示するだけです)。


これがすべて役に立つことを願っています :)