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

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

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

私はこれを試しました:

<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 共通ファイルアップロード私はこれを試しました:

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)

ベストアンサー1

導入

アップロードするファイルを参照して選択するには、<input type="file">フォームにHTMLフィールドが必要です。HTML仕様メソッドを使用しPOSTenctypeフォームの属性を に設定する必要があります"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 (2009年12月) より前では、Servlet API は をネイティブにサポートしていませんでしたmultipart/form-data。 のデフォルトのフォーム enctype のみサポートしていましたapplication/x-www-form-urlencodedrequest.getParameter()および コンソートはすべて、マルチパートフォームデータを使用する場合に返されますnull。ここで、よく知られているApache Commons ファイルアップロード写真に写りました。

手動で解析しないでください。

理論的には、リクエスト本文を自分で解析することができます。ServletRequest#getInputStream()しかし、これは正確で退屈な作業であり、RFC2388これを自分でやろうとしたり、インターネット上のどこかで見つけた自作のライブラリなしのコードをコピーペーストしたりしないでください。roseindia.net など、多くのオンライン ソースは、この方法では失敗しています。PDFファイルのアップロードむしろ、何百万ものユーザーによって何年も使用されている (そして暗黙的にテストされている!) 実際のライブラリを使用するべきです。そのようなライブラリは堅牢性が証明されています。

すでにServlet 3.0以降を使用している場合は、ネイティブAPIを使用してください。

少なくともServlet 3.0(Tomcat 7、Jetty 9、JBoss AS 6、GlassFish 3など、2010年からすでに存在)を使用している場合は、提供されている標準APIを使用できます。HttpServletRequest#getPart()個々のマルチパート フォーム データ項目を収集します (ほとんどの Servlet 3.0 実装では、実際にはこのために Apache Commons FileUpload が裏で使用されています)。また、通常のフォーム フィールドも通常の方法で利用できますgetParameter()

まずサーブレットに注釈を付けます@MultipartConfigmultipart/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 など、2013 年からすでに存在しています)。まだ 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を使用していない(10年以上前にリリースされたので、そろそろアップグレードする時期ではないでしょうか?)場合、一般的な方法は、Apache Commons ファイルアップロードマルチパートフォームデータリクエストを解析します。優れたユーザーガイドそしてよくある質問(両方を注意深く読んでください)。O'Reilly("コス")MultipartRequestですが、いくつかの (小さな) バグがあり、何年も積極的にメンテナンスされていません。使用はお勧めしません。Apache Commons FileUpload は、現在も積極的にメンテナンスされており、非常に成熟しています。

Apache Commons FileUpload を使用するには、Web アプリケーションに少なくとも次のファイルが必要です/WEB-INF/lib

最初の試みが失敗したのは、おそらく commons IO を忘れたためでしょう。

Apache Commons FileUpload を使用する場合のキックオフ例を次に示しますdoPost()UploadServlet

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()を呼び出さないことが非常に重要です。そうしないと、サーブレット コンテナがリクエスト本文を読み取って解析し、Apache Commons FileUpload は空のリクエスト本文を取得します。ao も参照してください。getParameterValues()getInputStream()getReader()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、あるいはプレーン テキストを印刷するだけです)。

おすすめ記事