如何使用 JSP/Servlet 将文件上传到服务器?

新手上路,请多包涵

如何使用 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" null <form> , - , - ,然后 request.getParameter()

在研究过程中,我偶然发现了 Apache Common FileUpload 。我试过这个:

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

不幸的是,servlet 抛出了一个没有明确消息和原因的异常。这是堆栈跟踪:

 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)

原文由 Thang Pham 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.3k
2 个回答

介绍

要浏览并选择要上传的文件,您需要在表单中添加 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(2009 年 12 月)之前,Servlet API 本身并不支持 multipart/form-data 。它仅支持 application/x-www-form-urlencoded 的默认形式 enctype。 request.getParameter() 和 consorts 在使用多部分表单数据时都会返回 null 。这就是著名的 Apache Commons FileUpload 出现的地方。

不要手动解析它!

理论上,您可以根据 ServletRequest#getInputStream() 解析请求正文。然而,这是一项精确而乏味的工作,需要对 RFC2388 有准确的了解。您不应该尝试自己执行此操作或复制粘贴在 Internet 其他地方找到的一些本地开发的无库代码。许多在线资源在这方面都失败了,例如 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() 通常的方式获得。

首先用 @MultipartConfig 注释你的servlet,以让它识别和支持 multipart/form-data 请求,从而得到 getPart() :8–

 @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(是不是该升级了?它是十多年前发布的!),通常的做法是使用 Apache Commons FileUpload 来解析多部分表单数据请求。它有出色的 用户指南常见问题解答(请仔细阅读)。还有 O’Reilly (” cos “) MultipartRequest ,但它有一些(小)错误,多年来不再积极维护。我不推荐使用它。 Apache Commons FileUpload 仍在积极维护,目前非常成熟。

为了使用 Apache Commons FileUpload,您的 webapp 的 /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);
    }

    // ...
}

It’s very important that you don’t call getParameter() , getParameterMap() , getParameterValues() , getInputStream() , getReader() , etc事先提出相同的要求。否则 servlet 容器将读取并解析请求主体,因此 Apache Commons FileUpload 将获得一个空请求主体。另见 servletFileUpload#parseRequest(request) returns an empty list

注意 FilenameUtils#getName() 。这是关于获取文件名的 MSIE 修复。此浏览器错误地发送完整的文件路径以及名称,而不是仅发送文件名。

或者,您也可以将所有内容包装在 Filter 中,它会自动解析所有内容并将这些内容放回请求的参数图中,以便您可以继续使用 request.getParameter() 通常的方式并检索 request.getAttribute() 上传的文件。 您可以在这篇博客文章中找到示例

GlassFish3 错误的解决方法 getParameter() 仍然返回 null

请注意,早于 3.1.2 的 Glassfish 版本有 一个错误,其中 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)上传的答案。请注意,不需要为此更改用于收集表单数据的 servlet 代码!只有您响应的方式可能会改变,但这是微不足道的(即不是转发到 JSP,而是打印一些 JSON 或 XML 甚至纯文本,具体取决于负责 Ajax 调用的脚本所期望的内容)。


希望这一切都有帮助:)

原文由 BalusC 发布,翻译遵循 CC BY-SA 4.0 许可协议

如果您碰巧使用 Spring MVC ,这就是方法(我将其留在此处以防有人发现它有用):

使用 enctype 属性设置为“ multipart/form-data ”的表单(与 BalusC 的答案 相同):

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

在您的控制器中,将请求参数 file 映射到 MultipartFile 输入如下:

 @RequestMapping(value = "/upload", method = RequestMethod.POST)
public void handleUpload(@RequestParam("file") MultipartFile file) throws IOException {
    if (!file.isEmpty()) {
            byte[] bytes = file.getBytes(); // alternatively, file.getInputStream();
            // application logic
    }
}

您可以使用 MultipartFilegetOriginalFilename()getSize() 获取文件名和大小。

我已经使用 Spring 版本 4.1.1.RELEASE 对此进行了测试。

原文由 Amila 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题